├── .github └── workflows │ └── go.yml ├── .gitignore ├── LICENSE ├── Makefile ├── README.md ├── go.mod ├── go.sum ├── hook.go └── hook_test.go /.github/workflows/go.yml: -------------------------------------------------------------------------------- 1 | name: Go 2 | 3 | on: 4 | push: 5 | branches: [ main ] 6 | pull_request: 7 | branches: [ main ] 8 | 9 | jobs: 10 | 11 | build: 12 | name: Build 13 | runs-on: ubuntu-latest 14 | steps: 15 | 16 | - name: Set up Go 1.x 17 | uses: actions/setup-go@v2 18 | with: 19 | go-version: ^1.14 20 | id: go 21 | 22 | - name: Check out code into the Go module directory 23 | uses: actions/checkout@v2 24 | 25 | - name: Get dependencies 26 | run: | 27 | go get -v -t -d ./... 28 | - name: Build 29 | run: go build -v . 30 | 31 | - name: Test 32 | run: make test 33 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea/ 2 | .vscode/ 3 | 4 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Globo.com 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 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | test: 2 | go test ./... 3 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # go-redis-opentracing 2 | 3 |

4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 |

13 | 14 | 15 | [go-redis](https://github.com/go-redis/redis) hook to collect OpenTracing spans. 16 | 17 | There are similar older libs that do not benefit from go-redis newer hooks feature. 18 | This is heavily inspired by https://github.com/go-redis/redis/blob/master/extra/redisotel/redisotel.go, 19 | but with support for OpenTracing instead of OpenTelemetry. 20 | 21 | Also check out our lib https://github.com/globocom/go-redis-prometheus. 22 | 23 | ## Installation 24 | 25 | go get github.com/globocom/go-redis-opentracing 26 | 27 | ## Usage 28 | 29 | ```golang 30 | package main 31 | 32 | import ( 33 | redisopentracing "github.com/globocom/go-redis-opentracing" 34 | "github.com/go-redis/redis/v8" 35 | jaegerConfig "github.com/uber/jaeger-client-go/config" 36 | ) 37 | 38 | func main() { 39 | cfg := &jaegerConfig.Configuration{ 40 | ServiceName: "my-service-name", 41 | } 42 | tracer, _, _ := cfg.NewTracer() 43 | 44 | hook := redisopentracing.NewHook(tracer) 45 | 46 | client := redis.NewClient(&redis.Options{ 47 | Addr: "localhost:6379", 48 | Password: "", 49 | }) 50 | client.AddHook(hook) 51 | 52 | // run redis commands... 53 | } 54 | ``` 55 | 56 | ## Note on pipelines 57 | 58 | Pipelines generate a single span. For each error that occurs on the pipeline, a tag `db.error` will be set. 59 | 60 | ## API stability 61 | 62 | The API is unstable at this point, and it might change before `v1.0.0` is released. 63 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/globocom/go-redis-opentracing 2 | 3 | go 1.14 4 | 5 | require ( 6 | github.com/go-redis/redis/extra/rediscmd v0.2.0 7 | github.com/go-redis/redis/v8 v8.4.0 8 | github.com/opentracing/opentracing-go v1.2.0 9 | github.com/stretchr/testify v1.6.1 10 | ) 11 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/cespare/xxhash/v2 v2.1.1 h1:6MnRN8NT7+YBpUIWxHtefFZOKTAPgGjpQSxqLNn0+qY= 2 | github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= 3 | github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= 4 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 5 | github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78= 6 | github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc= 7 | github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= 8 | github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4= 9 | github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= 10 | github.com/go-redis/redis/extra/rediscmd v0.2.0 h1:A3bhCsCKsedClEH9/jYlcKqOuBoeeV+H0yDie5t+a6w= 11 | github.com/go-redis/redis/extra/rediscmd v0.2.0/go.mod h1:Z5bP1EHl9PvWhx/DupfCdZwB0JgOO3aVxWc/PFux+BE= 12 | github.com/go-redis/redis/v8 v8.3.2/go.mod h1:jszGxBCez8QA1HWSmQxJO9Y82kNibbUmeYhKWrBejTU= 13 | github.com/go-redis/redis/v8 v8.4.0 h1:J5NCReIgh3QgUJu398hUncxDExN4gMOHI11NVbVicGQ= 14 | github.com/go-redis/redis/v8 v8.4.0/go.mod h1:A1tbYoHSa1fXwN+//ljcCYYJeLmVrwL9hbQN45Jdy0M= 15 | github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 16 | github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= 17 | github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= 18 | github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= 19 | github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= 20 | github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= 21 | github.com/golang/protobuf v1.4.2 h1:+Z5KGCizgyZCbGh1KZqA0fcLLkwbsjIzS4aV2v7wJX0= 22 | github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= 23 | github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= 24 | github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= 25 | github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 26 | github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 27 | github.com/google/go-cmp v0.5.3 h1:x95R7cp+rSeeqAMI2knLtQ0DKlaBhv2NrtrOvafPHRo= 28 | github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 29 | github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= 30 | github.com/nxadm/tail v1.4.4 h1:DQuhQpB1tVlglWS2hLQ5OV6B5r8aGxSrPc5Qo6uTN78= 31 | github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= 32 | github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= 33 | github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= 34 | github.com/onsi/ginkgo v1.14.2 h1:8mVmC9kjFFmA8H4pKMUhcblgifdkOIXPvbhN1T36q1M= 35 | github.com/onsi/ginkgo v1.14.2/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY= 36 | github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= 37 | github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= 38 | github.com/onsi/gomega v1.10.3 h1:gph6h/qe9GSUw1NhH1gp+qb+h8rXD8Cy60Z32Qw3ELA= 39 | github.com/onsi/gomega v1.10.3/go.mod h1:V9xEwhxec5O8UDM77eCW8vLymOMltsqPVYWrpDsH8xc= 40 | github.com/opentracing/opentracing-go v1.2.0 h1:uEJPy/1a5RIPAJ0Ov+OIO8OxWu77jEv+1B0VhjKrZUs= 41 | github.com/opentracing/opentracing-go v1.2.0/go.mod h1:GxEUsuufX4nBwe+T+Wl9TAgYrxe9dPLANfrWvHYVTgc= 42 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 43 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 44 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 45 | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= 46 | github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0= 47 | github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 48 | go.opentelemetry.io/otel v0.13.0/go.mod h1:dlSNewoRYikTkotEnxdmuBHgzT+k/idJSfDv/FxEnOY= 49 | go.opentelemetry.io/otel v0.14.0 h1:YFBEfjCk9MTjaytCNSUkp9Q8lF7QJezA06T71FbQxLQ= 50 | go.opentelemetry.io/otel v0.14.0/go.mod h1:vH5xEuwy7Rts0GNtsCW3HYQoZDY+OmBJ6t1bFGGlxgw= 51 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 52 | golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= 53 | golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 54 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 55 | golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= 56 | golang.org/x/net v0.0.0-20201006153459-a7d1128ccaa0 h1:wBouT66WTYFXdxfVdz9sVWARVd/2vfGcmI45D2gj45M= 57 | golang.org/x/net v0.0.0-20201006153459-a7d1128ccaa0/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= 58 | golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 59 | golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 60 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 61 | golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 62 | golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 63 | golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 64 | golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 65 | golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 66 | golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 67 | golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f h1:+Nyd8tzPX9R7BWHguqsrbFdRx3WQ/1ib8I44HXV5yTA= 68 | golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 69 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 70 | golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= 71 | golang.org/x/text v0.3.3 h1:cokOdA+Jmi5PJGXLlLllQSgYigAEfHXJAERHVMaCc2k= 72 | golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 73 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 74 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= 75 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 76 | google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= 77 | google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= 78 | google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= 79 | google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= 80 | google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= 81 | google.golang.org/protobuf v1.23.0 h1:4MY060fB1DLGMB/7MBTLnwQUY6+F09GEiz6SsrNqyzM= 82 | google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= 83 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= 84 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 85 | gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= 86 | gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= 87 | gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= 88 | gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 89 | gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU= 90 | gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 91 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= 92 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 93 | -------------------------------------------------------------------------------- /hook.go: -------------------------------------------------------------------------------- 1 | package redisopentracing 2 | 3 | import ( 4 | "context" 5 | "strconv" 6 | 7 | "github.com/go-redis/redis/v8" 8 | "github.com/opentracing/opentracing-go" 9 | "github.com/opentracing/opentracing-go/ext" 10 | ) 11 | 12 | type RedisTracingHook struct { 13 | tracer opentracing.Tracer 14 | } 15 | 16 | var _ redis.Hook = RedisTracingHook{} 17 | 18 | // NewHook creates a new go-redis hook instance and that will collect spans using the provided tracer. 19 | func NewHook(tracer opentracing.Tracer) redis.Hook { 20 | return &RedisTracingHook{ 21 | tracer: tracer, 22 | } 23 | } 24 | 25 | func (hook RedisTracingHook) createSpan(ctx context.Context, operationName string) (opentracing.Span, context.Context) { 26 | span := opentracing.SpanFromContext(ctx) 27 | if span != nil { 28 | childSpan := hook.tracer.StartSpan(operationName, opentracing.ChildOf(span.Context())) 29 | return childSpan, opentracing.ContextWithSpan(ctx, childSpan) 30 | } 31 | 32 | return opentracing.StartSpanFromContextWithTracer(ctx, hook.tracer, operationName) 33 | } 34 | 35 | func (hook RedisTracingHook) BeforeProcess(ctx context.Context, cmd redis.Cmder) (context.Context, error) { 36 | span, ctx := hook.createSpan(ctx, cmd.FullName()) 37 | span.SetTag("db.type", "redis") 38 | return ctx, nil 39 | } 40 | 41 | func (hook RedisTracingHook) AfterProcess(ctx context.Context, cmd redis.Cmder) error { 42 | span := opentracing.SpanFromContext(ctx) 43 | defer span.Finish() 44 | 45 | if err := cmd.Err(); err != nil { 46 | recordError(ctx, "db.error", span, err) 47 | } 48 | return nil 49 | } 50 | 51 | func (hook RedisTracingHook) BeforeProcessPipeline(ctx context.Context, cmds []redis.Cmder) (context.Context, error) { 52 | span, ctx := hook.createSpan(ctx, "pipeline") 53 | span.SetTag("db.type", "redis") 54 | span.SetTag("db.redis.num_cmd", len(cmds)) 55 | return ctx, nil 56 | } 57 | 58 | func (hook RedisTracingHook) AfterProcessPipeline(ctx context.Context, cmds []redis.Cmder) error { 59 | span := opentracing.SpanFromContext(ctx) 60 | defer span.Finish() 61 | 62 | for i, cmd := range cmds { 63 | if err := cmd.Err(); err != nil { 64 | recordError(ctx, "db.error"+strconv.Itoa(i), span, err) 65 | } 66 | } 67 | return nil 68 | } 69 | 70 | func recordError(ctx context.Context, errorTag string, span opentracing.Span, err error) { 71 | if err != redis.Nil { 72 | span.SetTag(string(ext.Error), true) 73 | span.SetTag(errorTag, err.Error()) 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /hook_test.go: -------------------------------------------------------------------------------- 1 | package redisopentracing_test 2 | 3 | import ( 4 | "context" 5 | "errors" 6 | "testing" 7 | 8 | "github.com/go-redis/redis/v8" 9 | "github.com/opentracing/opentracing-go" 10 | "github.com/opentracing/opentracing-go/ext" 11 | "github.com/opentracing/opentracing-go/mocktracer" 12 | "github.com/stretchr/testify/assert" 13 | 14 | redisopentracing "github.com/globocom/go-redis-opentracing" 15 | ) 16 | 17 | func TestHook(t *testing.T) { 18 | assert := assert.New(t) 19 | 20 | t.Run("create a new hook", func(t *testing.T) { 21 | // act 22 | sut := redisopentracing.NewHook(opentracing.NoopTracer{}) 23 | 24 | // assert 25 | assert.NotNil(sut) 26 | }) 27 | 28 | t.Run("starts a span before a command and finishes it after the command", func(t *testing.T) { 29 | // arrange 30 | tracer := mocktracer.New() 31 | sut := redisopentracing.NewHook(tracer) 32 | 33 | cmd := redis.NewStringCmd(context.Background(), "get") 34 | 35 | // act 36 | ctx, err1 := sut.BeforeProcess(context.Background(), cmd) 37 | 38 | // assert 39 | assert.Nil(err1) 40 | assert.Len(tracer.FinishedSpans(), 0) 41 | 42 | // act 43 | err2 := sut.AfterProcess(ctx, cmd) 44 | 45 | // assert 46 | assert.Nil(err2) 47 | assert.Len(tracer.FinishedSpans(), 1) 48 | span := tracer.FinishedSpans()[0] 49 | assert.Equal("get", span.OperationName) 50 | assert.Len(span.Tags(), 1) 51 | assert.Equal("redis", span.Tags()["db.type"]) 52 | assert.Equal(nil, span.Tags()["db.error"]) 53 | assert.Equal(nil, span.Tags()[string(ext.Error)]) 54 | }) 55 | 56 | t.Run("starts a span before a command and finishes it after the command, with error", func(t *testing.T) { 57 | // arrange 58 | tracer := mocktracer.New() 59 | sut := redisopentracing.NewHook(tracer) 60 | 61 | cmd := redis.NewStringCmd(context.Background(), "get") 62 | cmd.SetErr(errors.New("some error")) 63 | 64 | // act 65 | ctx, err1 := sut.BeforeProcess(context.Background(), cmd) 66 | 67 | // assert 68 | assert.Nil(err1) 69 | assert.Len(tracer.FinishedSpans(), 0) 70 | 71 | // act 72 | err2 := sut.AfterProcess(ctx, cmd) 73 | 74 | // assert 75 | assert.Nil(err2) 76 | assert.Len(tracer.FinishedSpans(), 1) 77 | span := tracer.FinishedSpans()[0] 78 | assert.Equal("get", span.OperationName) 79 | assert.Len(span.Tags(), 3) 80 | assert.Equal("redis", span.Tags()["db.type"]) 81 | assert.Equal("some error", span.Tags()["db.error"]) 82 | assert.Equal(true, span.Tags()[string(ext.Error)]) 83 | }) 84 | 85 | t.Run("starts a span before a pipeline and finishes it after the pipeline", func(t *testing.T) { 86 | // arrange 87 | tracer := mocktracer.New() 88 | sut := redisopentracing.NewHook(tracer) 89 | 90 | cmd1 := redis.NewStringCmd(context.Background(), "get") 91 | cmd2 := redis.NewStringCmd(context.Background(), "dbsize") 92 | cmd3 := redis.NewStringCmd(context.Background(), "set x y") 93 | cmds := []redis.Cmder{cmd1, cmd2, cmd3} 94 | 95 | // act 96 | ctx, err1 := sut.BeforeProcessPipeline(context.Background(), cmds) 97 | 98 | // assert 99 | assert.Nil(err1) 100 | assert.Len(tracer.FinishedSpans(), 0) 101 | 102 | // act 103 | err2 := sut.AfterProcessPipeline(ctx, cmds) 104 | 105 | // assert 106 | assert.Nil(err2) 107 | assert.Len(tracer.FinishedSpans(), 1) 108 | 109 | span := tracer.FinishedSpans()[0] 110 | assert.Equal("pipeline", span.OperationName) 111 | 112 | assert.Len(span.Tags(), 2) 113 | assert.Equal("redis", span.Tags()["db.type"]) 114 | assert.Equal(3, span.Tags()["db.redis.num_cmd"]) 115 | 116 | assert.Equal(nil, span.Tags()["db.error0"]) 117 | assert.Equal(nil, span.Tags()["db.error1"]) 118 | assert.Equal(nil, span.Tags()["db.error2"]) 119 | assert.Equal(nil, span.Tags()[string(ext.Error)]) 120 | }) 121 | 122 | t.Run("starts a span before a pipeline and finishes it after the pipeline, with error", func(t *testing.T) { 123 | // arrange 124 | tracer := mocktracer.New() 125 | sut := redisopentracing.NewHook(tracer) 126 | 127 | cmd1 := redis.NewStringCmd(context.Background(), "get") 128 | cmd2 := redis.NewStringCmd(context.Background(), "dbsize") 129 | cmd2.SetErr(errors.New("error 1 in pipeline cmd")) 130 | cmd3 := redis.NewStringCmd(context.Background(), "set x y") 131 | cmd3.SetErr(errors.New("error 2 in pipeline cmd")) 132 | cmds := []redis.Cmder{cmd1, cmd2, cmd3} 133 | // act 134 | ctx, err1 := sut.BeforeProcessPipeline(context.Background(), cmds) 135 | 136 | // assert 137 | assert.Nil(err1) 138 | assert.Len(tracer.FinishedSpans(), 0) 139 | 140 | // act 141 | err2 := sut.AfterProcessPipeline(ctx, cmds) 142 | 143 | // assert 144 | assert.Nil(err2) 145 | assert.Len(tracer.FinishedSpans(), 1) 146 | 147 | span := tracer.FinishedSpans()[0] 148 | assert.Equal("pipeline", span.OperationName) 149 | 150 | assert.Len(span.Tags(), 5) 151 | assert.Equal("redis", span.Tags()["db.type"]) 152 | assert.Equal(3, span.Tags()["db.redis.num_cmd"]) 153 | 154 | assert.Equal(nil, span.Tags()["db.error0"]) 155 | assert.Equal("error 1 in pipeline cmd", span.Tags()["db.error1"]) 156 | assert.Equal("error 2 in pipeline cmd", span.Tags()["db.error2"]) 157 | assert.Equal(true, span.Tags()[string(ext.Error)]) 158 | }) 159 | } 160 | --------------------------------------------------------------------------------