├── .travis.yml ├── LICENSE ├── README.md ├── go.mod ├── go.sum ├── otredis.go └── otredis_test.go /.travis.yml: -------------------------------------------------------------------------------- 1 | language: go 2 | 3 | go: 4 | - 1.11.x 5 | - 1.12.x 6 | 7 | env: 8 | - GO111MODULE=on 9 | 10 | install: 11 | - go get -t ./... 12 | 13 | script: 14 | - go test -v ./... 15 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Maxim Sukharev 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # opentracing go-redis 2 | 3 | [OpenTracing](http://opentracing.io/) instrumentation for [go-redis](https://github.com/go-redis/redis). 4 | 5 | ## Install 6 | 7 | ``` 8 | go get -u github.com/smacker/opentracing-go-redis 9 | ``` 10 | 11 | ## Usage 12 | 13 | Clone redis client `c := otredis.WrapRedisClient(ctx, c)` with a span. 14 | 15 | Example: 16 | 17 | ```go 18 | var client *redis.Client 19 | 20 | func Handler(ctx context.Context) { 21 | span, ctx := opentracing.StartSpanFromContext(ctx, "handler") 22 | defer span.Finish() 23 | 24 | // clone redis with proper context 25 | client := otredis.WrapRedisClient(ctx, client) 26 | 27 | // make requests to redis 28 | client.Get("foo") 29 | } 30 | ``` 31 | 32 | Call to the `Handler` function would create redis span as a child of handler span. 33 | 34 | ## License 35 | 36 | [MIT](LICENSE) 37 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/smacker/opentracing-go-redis 2 | 3 | go 1.12 4 | 5 | require ( 6 | github.com/alicebob/gopher-json v0.0.0-20180125190556-5a6b3ba71ee6 // indirect 7 | github.com/alicebob/miniredis v2.5.0+incompatible 8 | github.com/go-redis/redis v6.15.2+incompatible 9 | github.com/golang/protobuf v1.3.1 // indirect 10 | github.com/gomodule/redigo v2.0.0+incompatible // indirect 11 | github.com/onsi/ginkgo v1.8.0 // indirect 12 | github.com/onsi/gomega v1.5.0 // indirect 13 | github.com/opentracing/opentracing-go v1.1.0 14 | github.com/stretchr/objx v0.2.0 // indirect 15 | github.com/stretchr/testify v1.3.0 16 | github.com/yuin/gopher-lua v0.0.0-20190514113301-1cd887cd7036 // indirect 17 | golang.org/x/crypto v0.0.0-20190621222207-cc06ce4a13d4 // indirect 18 | golang.org/x/net v0.0.0-20190620200207-3b0461eec859 // indirect 19 | golang.org/x/sys v0.0.0-20190626221950-04f50cda93cb // indirect 20 | golang.org/x/text v0.3.2 // indirect 21 | golang.org/x/tools v0.0.0-20190628034336-212fb13d595e // indirect 22 | gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 // indirect 23 | gopkg.in/yaml.v2 v2.2.2 // indirect 24 | ) 25 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/alicebob/gopher-json v0.0.0-20180125190556-5a6b3ba71ee6 h1:45bxf7AZMwWcqkLzDAQugVEwedisr5nRJ1r+7LYnv0U= 2 | github.com/alicebob/gopher-json v0.0.0-20180125190556-5a6b3ba71ee6/go.mod h1:SGnFV6hVsYE877CKEZ6tDNTjaSXYUk6QqoIK6PrAtcc= 3 | github.com/alicebob/miniredis v2.5.0+incompatible h1:yBHoLpsyjupjz3NL3MhKMVkR41j82Yjf3KFv7ApYzUI= 4 | github.com/alicebob/miniredis v2.5.0+incompatible/go.mod h1:8HZjEj4yU0dwhYHky+DxYx+6BMjkBbe5ONFIF1MXffk= 5 | github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= 6 | github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= 7 | github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= 8 | github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= 9 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 10 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 11 | github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I= 12 | github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= 13 | github.com/go-redis/redis v6.15.2+incompatible h1:9SpNVG76gr6InJGxoZ6IuuxaCOQwDAhzyXg+Bs+0Sb4= 14 | github.com/go-redis/redis v6.15.2+incompatible/go.mod h1:NAIEuMOZ/fxfXJIrKDQDz8wamY7mA7PouImQ2Jvg6kA= 15 | github.com/golang/protobuf v1.2.0 h1:P3YflyNX/ehuJFLhxviNdFxQPkGK5cDcApsge1SqnvM= 16 | github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 17 | github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 18 | github.com/gomodule/redigo v2.0.0+incompatible h1:K/R+8tc58AaqLkqG2Ol3Qk+DR/TlNuhuh457pBFPtt0= 19 | github.com/gomodule/redigo v2.0.0+incompatible/go.mod h1:B4C85qUVwatsJoIUNIfCRsp7qO0iAmpGFZ4EELWSbC4= 20 | github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI= 21 | github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= 22 | github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= 23 | github.com/onsi/ginkgo v1.8.0 h1:VkHVNpR4iVnU8XQR6DBm8BqYjN7CRzw+xKUbVVbbW9w= 24 | github.com/onsi/ginkgo v1.8.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= 25 | github.com/onsi/gomega v1.5.0 h1:izbySO9zDPmjJ8rDjLvkA2zJHIo+HkYXHnf7eN7SSyo= 26 | github.com/onsi/gomega v1.5.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= 27 | github.com/opentracing/opentracing-go v1.1.0 h1:pWlfV3Bxv7k65HYwkikxat0+s3pV4bsqf19k25Ur8rU= 28 | github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= 29 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 30 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 31 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 32 | github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE= 33 | github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q= 34 | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= 35 | github.com/yuin/gopher-lua v0.0.0-20190514113301-1cd887cd7036 h1:1b6PAtenNyhsmo/NKXVe34h7JEZKva1YB/ne7K7mqKM= 36 | github.com/yuin/gopher-lua v0.0.0-20190514113301-1cd887cd7036/go.mod h1:gqRgreBUhTSL0GeU64rtZ3Uq3wtjOa/TB2YfrtkCbVQ= 37 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 38 | golang.org/x/crypto v0.0.0-20190621222207-cc06ce4a13d4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 39 | golang.org/x/net v0.0.0-20180906233101-161cd47e91fd h1:nTDtHvHSdCn1m6ITfMRqtOd/9+7a3s8RBNOZ3eYZzJA= 40 | golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 41 | golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 42 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 43 | golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 44 | golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f h1:wMNYb4v58l5UBM7MYRLPG6ZhfOqbKu7X5eyFl8ZhKvA= 45 | golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 46 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 47 | golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 48 | golang.org/x/sys v0.0.0-20190204203706-41f3e6584952 h1:FDfvYgoVsA7TTZSbgiqjAbfPbK47CNHdWl3h/PJtii0= 49 | golang.org/x/sys v0.0.0-20190204203706-41f3e6584952/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 50 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 51 | golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 52 | golang.org/x/sys v0.0.0-20190626221950-04f50cda93cb/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 53 | golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg= 54 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 55 | golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= 56 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 57 | golang.org/x/tools v0.0.0-20190628034336-212fb13d595e/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= 58 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= 59 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 60 | gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 61 | gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4= 62 | gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= 63 | gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= 64 | gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= 65 | gopkg.in/yaml.v2 v2.2.1 h1:mUhvW9EsL+naU5Q3cakzfE91YhliOondGd6ZrsDBHQE= 66 | gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 67 | gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 68 | -------------------------------------------------------------------------------- /otredis.go: -------------------------------------------------------------------------------- 1 | package otredis 2 | 3 | import ( 4 | "context" 5 | "strings" 6 | 7 | "github.com/go-redis/redis" 8 | "github.com/opentracing/opentracing-go" 9 | "github.com/opentracing/opentracing-go/ext" 10 | ) 11 | 12 | // WrapRedisClient adds opentracing measurements for commands and returns cloned client 13 | func WrapRedisClient(ctx context.Context, client *redis.Client) *redis.Client { 14 | if ctx == nil { 15 | return client 16 | } 17 | parentSpan := opentracing.SpanFromContext(ctx) 18 | if parentSpan == nil { 19 | return client 20 | } 21 | // clone using context 22 | ctxClient := client.WithContext(ctx) 23 | opts := ctxClient.Options() 24 | ctxClient.WrapProcess(process(parentSpan, opts)) 25 | ctxClient.WrapProcessPipeline(processPipeline(parentSpan, opts)) 26 | return ctxClient 27 | } 28 | 29 | func process(parentSpan opentracing.Span, opts *redis.Options) func(oldProcess func(cmd redis.Cmder) error) func(cmd redis.Cmder) error { 30 | return func(oldProcess func(cmd redis.Cmder) error) func(cmd redis.Cmder) error { 31 | return func(cmd redis.Cmder) error { 32 | dbMethod := formatCommandAsDbMethod(cmd) 33 | span := getSpan(parentSpan, opts, "redis-cmd", dbMethod) 34 | defer span.Finish() 35 | return oldProcess(cmd) 36 | } 37 | } 38 | } 39 | 40 | func processPipeline(parentSpan opentracing.Span, opts *redis.Options) func(oldProcess func(cmds []redis.Cmder) error) func(cmds []redis.Cmder) error { 41 | return func(oldProcess func(cmds []redis.Cmder) error) func(cmds []redis.Cmder) error { 42 | return func(cmds []redis.Cmder) error { 43 | dbMethod := formatCommandsAsDbMethods(cmds) 44 | span := getSpan(parentSpan, opts, "redis-pipeline-cmd", dbMethod) 45 | defer span.Finish() 46 | return oldProcess(cmds) 47 | } 48 | } 49 | } 50 | 51 | func formatCommandAsDbMethod(cmd redis.Cmder) string { 52 | return cmd.Name() 53 | } 54 | 55 | func formatCommandsAsDbMethods(cmds []redis.Cmder) string { 56 | cmdsAsDbMethods := make([]string, len(cmds)) 57 | for i, cmd := range cmds { 58 | dbMethod := formatCommandAsDbMethod(cmd) 59 | cmdsAsDbMethods[i] = dbMethod 60 | } 61 | return strings.Join(cmdsAsDbMethods, " -> ") 62 | } 63 | 64 | func getSpan(parentSpan opentracing.Span, opts *redis.Options, operationName, dbMethod string) opentracing.Span { 65 | tracer := parentSpan.Tracer() 66 | span := tracer.StartSpan(operationName, opentracing.ChildOf(parentSpan.Context())) 67 | ext.DBType.Set(span, "redis") 68 | ext.PeerAddress.Set(span, opts.Addr) 69 | ext.SpanKind.Set(span, ext.SpanKindEnum("client")) 70 | span.SetTag("db.method", dbMethod) 71 | return span 72 | } 73 | -------------------------------------------------------------------------------- /otredis_test.go: -------------------------------------------------------------------------------- 1 | package otredis 2 | 3 | import ( 4 | "context" 5 | "os" 6 | "testing" 7 | 8 | "github.com/alicebob/miniredis" 9 | "github.com/go-redis/redis" 10 | "github.com/opentracing/opentracing-go" 11 | "github.com/opentracing/opentracing-go/ext" 12 | "github.com/opentracing/opentracing-go/mocktracer" 13 | "github.com/stretchr/testify/assert" 14 | ) 15 | 16 | var redisAddr string 17 | var client *redis.Client 18 | var tracer *mocktracer.MockTracer 19 | 20 | func init() { 21 | tracer = mocktracer.New() 22 | opentracing.SetGlobalTracer(tracer) 23 | } 24 | 25 | func TestMain(m *testing.M) { 26 | // in-memory redis 27 | miniRedis, err := miniredis.Run() 28 | if err != nil { 29 | panic(err) 30 | } 31 | defer miniRedis.Close() 32 | 33 | redisAddr = miniRedis.Addr() 34 | 35 | client = redis.NewClient(&redis.Options{ 36 | Addr: redisAddr, 37 | }) 38 | 39 | os.Exit(m.Run()) 40 | } 41 | 42 | // SET 43 | 44 | func TestSet(t *testing.T) { 45 | ctx := context.Background() 46 | assert := assert.New(t) 47 | 48 | span, ctx := opentracing.StartSpanFromContext(ctx, "test-set") 49 | ctxClient := WrapRedisClient(ctx, client) 50 | callSet(assert, ctxClient, "with span") 51 | span.Finish() 52 | 53 | spans := tracer.FinishedSpans() 54 | assert.Len(spans, 2, "the number of finished spans is invalid") 55 | 56 | redisSpan := spans[0] 57 | assert.Equal("redis-cmd", redisSpan.OperationName) 58 | 59 | expectedTags := buildExpectedTags("set") 60 | assertTags(assert, redisSpan, expectedTags) 61 | 62 | tracer.Reset() 63 | } 64 | 65 | func TestSetPipeline(t *testing.T) { 66 | ctx := context.Background() 67 | assert := assert.New(t) 68 | 69 | span, ctx := opentracing.StartSpanFromContext(ctx, "test-set-pipeline") 70 | ctxClient := WrapRedisClient(ctx, client) 71 | setPipelineParams := make(map[string]string) 72 | setPipelineParams["foo"] = "with span on foo pipeline" 73 | setPipelineParams["bar"] = "with span on bar pipeline" 74 | callSetPipeline(assert, ctxClient, setPipelineParams) 75 | span.Finish() 76 | 77 | spans := tracer.FinishedSpans() 78 | assert.Len(spans, 2, "the number of finished spans is invalid") 79 | 80 | redisSpan := spans[0] 81 | assert.Equal("redis-pipeline-cmd", redisSpan.OperationName) 82 | 83 | expectedTags := buildExpectedTags("set -> set") 84 | assertTags(assert, redisSpan, expectedTags) 85 | 86 | tracer.Reset() 87 | } 88 | 89 | func callSet(assert *assert.Assertions, client *redis.Client, value string) { 90 | _, err := client.Set("foo", value, 0).Result() 91 | assert.Nil(err, "Redis returned error: %v", err) 92 | } 93 | 94 | func callSetPipeline(assert *assert.Assertions, client *redis.Client, setPipelineParams map[string]string) { 95 | pipeline := client.Pipeline() 96 | for key, value := range setPipelineParams { 97 | pipeline.Set(key, value, 0) 98 | } 99 | _, err := pipeline.Exec() 100 | assert.Nil(err, "Redis returned error: %v", err) 101 | } 102 | 103 | // GET 104 | 105 | func TestGet(t *testing.T) { 106 | ctx := context.Background() 107 | assert := assert.New(t) 108 | 109 | span, ctx := opentracing.StartSpanFromContext(ctx, "test-get") 110 | ctxClient := WrapRedisClient(ctx, client) 111 | callGet(assert, ctxClient) 112 | span.Finish() 113 | 114 | spans := tracer.FinishedSpans() 115 | assert.Len(spans, 2, "the number of finished spans is invalid") 116 | 117 | redisSpan := spans[0] 118 | assert.Equal("redis-cmd", redisSpan.OperationName) 119 | 120 | expectedTags := buildExpectedTags("get") 121 | assertTags(assert, redisSpan, expectedTags) 122 | 123 | tracer.Reset() 124 | } 125 | 126 | func TestGetPipeline(t *testing.T) { 127 | ctx := context.Background() 128 | assert := assert.New(t) 129 | 130 | span, ctx := opentracing.StartSpanFromContext(ctx, "test-get-pipeline") 131 | ctxClient := WrapRedisClient(ctx, client) 132 | getPipelineParams := []string{"foo", "bar"} 133 | callGetPipeline(assert, ctxClient, getPipelineParams) 134 | span.Finish() 135 | 136 | spans := tracer.FinishedSpans() 137 | assert.Len(spans, 2, "the number of finished spans is invalid") 138 | 139 | redisSpan := spans[0] 140 | assert.Equal("redis-pipeline-cmd", redisSpan.OperationName) 141 | 142 | expectedTags := buildExpectedTags("get -> get") 143 | assertTags(assert, redisSpan, expectedTags) 144 | 145 | tracer.Reset() 146 | } 147 | 148 | func callGet(assert *assert.Assertions, client *redis.Client) { 149 | _, err := client.Get("foo").Result() 150 | assert.Nil(err, "Redis returned error: %v", err) 151 | } 152 | 153 | func callGetPipeline(assert *assert.Assertions, client *redis.Client, getPipelineParams []string) { 154 | pipeline := client.Pipeline() 155 | for _, key := range getPipelineParams { 156 | pipeline.Get(key) 157 | } 158 | _, err := pipeline.Exec() 159 | assert.Nil(err, "Redis returned error: %v", err) 160 | } 161 | 162 | // MISC 163 | 164 | func buildExpectedTags(expectedDbMethod string) map[string]interface{} { 165 | expectedTags := make(map[string]interface{}) 166 | expectedTags["db.type"] = "redis" 167 | expectedTags["db.method"] = expectedDbMethod 168 | expectedTags["peer.address"] = redisAddr 169 | expectedTags["span.kind"] = ext.SpanKindEnum("client") 170 | return expectedTags 171 | } 172 | 173 | func assertTags(assert *assert.Assertions, redisSpan *mocktracer.MockSpan, expectedTags map[string]interface{}) map[string]interface{} { 174 | actualTags := redisSpan.Tags() 175 | assert.Len(actualTags, len(expectedTags), "redis span tags number is invalid") 176 | for expectedTagKey, expectedTagValue := range expectedTags { 177 | actualTagValue, ok := actualTags[expectedTagKey] 178 | assert.True(ok, "redis span doesn't have tag '%s'", expectedTagKey) 179 | assert.Equal(expectedTagValue, actualTagValue, "redis span tag '%s' is invalid", expectedTagKey) 180 | } 181 | return actualTags 182 | } 183 | --------------------------------------------------------------------------------