diff --git a/example/otel/client.go b/example/otel/client.go index 984d83f09..7ea5fa86c 100644 --- a/example/otel/client.go +++ b/example/otel/client.go @@ -4,12 +4,15 @@ import ( "context" "fmt" "log" + "strconv" "sync" "time" "github.com/uptrace/uptrace-go/uptrace" "go.opentelemetry.io/otel" + "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/codes" + semconv "go.opentelemetry.io/otel/semconv/v1.4.0" "github.com/redis/go-redis/extra/redisotel/v9" "github.com/redis/go-redis/v9" @@ -17,6 +20,16 @@ import ( var tracer = otel.Tracer("github.com/redis/go-redis/example/otel") +func customAttrFn(ctx context.Context) []attribute.KeyValue { + + attributes := make([]attribute.KeyValue, 0) + + if method, ok := ctx.Value(semconv.RPCMethodKey).(string); ok { + attributes = append(attributes, semconv.RPCMethodKey.String(method)) + } + + return attributes +} func main() { ctx := context.Background() @@ -32,7 +45,8 @@ func main() { rdb := redis.NewClient(&redis.Options{ Addr: ":6379", }) - if err := redisotel.InstrumentTracing(rdb); err != nil { + + if err := redisotel.InstrumentTracing(rdb, redisotel.WithAttributesFunc(customAttrFn)); err != nil { panic(err) } if err := redisotel.InstrumentMetrics(rdb); err != nil { @@ -41,7 +55,8 @@ func main() { for i := 0; i < 1e6; i++ { ctx, rootSpan := tracer.Start(ctx, "handleRequest") - + ctx = context.WithValue(ctx, semconv.RPCMethodKey, "handleRequest "+ strconv.Itoa(i)) + if err := handleRequest(ctx, rdb); err != nil { rootSpan.RecordError(err) rootSpan.SetStatus(codes.Error, err.Error()) diff --git a/example/otel/go.mod b/example/otel/go.mod index 93b5d46c2..54a04dedd 100644 --- a/example/otel/go.mod +++ b/example/otel/go.mod @@ -12,14 +12,14 @@ require ( github.com/redis/go-redis/extra/redisotel/v9 v9.7.1 github.com/redis/go-redis/v9 v9.7.1 github.com/uptrace/uptrace-go v1.21.0 - go.opentelemetry.io/otel v1.22.0 + go.opentelemetry.io/otel v1.23.0 ) require ( github.com/cenkalti/backoff/v4 v4.2.1 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect - github.com/go-logr/logr v1.4.1 // indirect + github.com/go-logr/logr v1.4.2 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/golang/protobuf v1.5.3 // indirect github.com/grpc-ecosystem/grpc-gateway/v2 v2.19.0 // indirect @@ -29,13 +29,13 @@ require ( go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.21.0 // indirect go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.21.0 // indirect go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.21.0 // indirect - go.opentelemetry.io/otel/metric v1.22.0 // indirect - go.opentelemetry.io/otel/sdk v1.22.0 // indirect - go.opentelemetry.io/otel/sdk/metric v1.21.0 // indirect - go.opentelemetry.io/otel/trace v1.22.0 // indirect + go.opentelemetry.io/otel/metric v1.23.0 // indirect + go.opentelemetry.io/otel/sdk v1.23.0 // indirect + go.opentelemetry.io/otel/sdk/metric v1.23.0 // indirect + go.opentelemetry.io/otel/trace v1.23.0 // indirect go.opentelemetry.io/proto/otlp v1.0.0 // indirect golang.org/x/net v0.33.0 // indirect - golang.org/x/sys v0.28.0 // indirect + golang.org/x/sys v0.29.0 // indirect golang.org/x/text v0.21.0 // indirect google.golang.org/genproto v0.0.0-20240108191215-35c7eff3a6b1 // indirect google.golang.org/genproto/googleapis/api v0.0.0-20240108191215-35c7eff3a6b1 // indirect diff --git a/example/otel/go.sum b/example/otel/go.sum index 1a1729c6e..e8cba406c 100644 --- a/example/otel/go.sum +++ b/example/otel/go.sum @@ -8,8 +8,8 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78= github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= -github.com/go-logr/logr v1.4.1 h1:pKouT5E8xu9zeFC39JXRDukb6JFQPXM5p5I91188VAQ= -github.com/go-logr/logr v1.4.1/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= +github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= @@ -20,13 +20,13 @@ github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/grpc-ecosystem/grpc-gateway/v2 v2.19.0 h1:Wqo399gCIufwto+VfwCSvsnfGpF/w5E9CNxSwbpD6No= github.com/grpc-ecosystem/grpc-gateway/v2 v2.19.0/go.mod h1:qmOFXW2epJhM0qSnUUYpldc7gVz2KMQwJ/QYCDIa7XU= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= -github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= +github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= github.com/uptrace/uptrace-go v1.21.0 h1:oJoUjhiVT7aiuoG6B3ClVHtJozLn3cK9hQt8U5dQO1M= github.com/uptrace/uptrace-go v1.21.0/go.mod h1:/aXAFGKOqeAFBqWa1xtzLnGX2xJm1GScqz9NJ0TJjLM= go.opentelemetry.io/contrib/instrumentation/runtime v0.46.1 h1:m9ReioVPIffxjJlGNRd0d5poy+9oTro3D+YbiEzUDOc= go.opentelemetry.io/contrib/instrumentation/runtime v0.46.1/go.mod h1:CANkrsXNzqOKXfOomu2zhOmc1/J5UZK9SGjrat6ZCG0= -go.opentelemetry.io/otel v1.22.0 h1:xS7Ku+7yTFvDfDraDIJVpw7XPyuHlB9MCiqqX5mcJ6Y= -go.opentelemetry.io/otel v1.22.0/go.mod h1:eoV4iAi3Ea8LkAEI9+GFT44O6T/D0GWAVFyZVCC6pMI= +go.opentelemetry.io/otel v1.23.0 h1:Df0pqjqExIywbMCMTxkAwzjLZtRf+bBKLbUcpxO2C9E= +go.opentelemetry.io/otel v1.23.0/go.mod h1:YCycw9ZeKhcJFrb34iVSkyT0iczq/zYDtZYFufObyB0= go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v0.44.0 h1:jd0+5t/YynESZqsSyPz+7PAFdEop0dlN0+PkyHYo8oI= go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v0.44.0/go.mod h1:U707O40ee1FpQGyhvqnzmCJm1Wh6OX6GGBVn0E6Uyyk= go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.21.0 h1:cl5P5/GIfFh4t6xyruOgJP5QiA1pw4fYYdv6nc6CBWw= @@ -35,21 +35,21 @@ go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.21.0 h1:tIqhe go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.21.0/go.mod h1:nUeKExfxAQVbiVFn32YXpXZZHZ61Cc3s3Rn1pDBGAb0= go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.21.0 h1:VhlEQAPp9R1ktYfrPk5SOryw1e9LDDTZCbIPFrho0ec= go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.21.0/go.mod h1:kB3ufRbfU+CQ4MlUcqtW8Z7YEOBeK2DJ6CmR5rYYF3E= -go.opentelemetry.io/otel/metric v1.22.0 h1:lypMQnGyJYeuYPhOM/bgjbFM6WE44W1/T45er4d8Hhg= -go.opentelemetry.io/otel/metric v1.22.0/go.mod h1:evJGjVpZv0mQ5QBRJoBF64yMuOf4xCWdXjK8pzFvliY= -go.opentelemetry.io/otel/sdk v1.22.0 h1:6coWHw9xw7EfClIC/+O31R8IY3/+EiRFHevmHafB2Gw= -go.opentelemetry.io/otel/sdk v1.22.0/go.mod h1:iu7luyVGYovrRpe2fmj3CVKouQNdTOkxtLzPvPz1DOc= -go.opentelemetry.io/otel/sdk/metric v1.21.0 h1:smhI5oD714d6jHE6Tie36fPx4WDFIg+Y6RfAY4ICcR0= -go.opentelemetry.io/otel/sdk/metric v1.21.0/go.mod h1:FJ8RAsoPGv/wYMgBdUJXOm+6pzFY3YdljnXtv1SBE8Q= -go.opentelemetry.io/otel/trace v1.22.0 h1:Hg6pPujv0XG9QaVbGOBVHunyuLcCC3jN7WEhPx83XD0= -go.opentelemetry.io/otel/trace v1.22.0/go.mod h1:RbbHXVqKES9QhzZq/fE5UnOSILqRt40a21sPw2He1xo= +go.opentelemetry.io/otel/metric v1.23.0 h1:pazkx7ss4LFVVYSxYew7L5I6qvLXHA0Ap2pwV+9Cnpo= +go.opentelemetry.io/otel/metric v1.23.0/go.mod h1:MqUW2X2a6Q8RN96E2/nqNoT+z9BSms20Jb7Bbp+HiTo= +go.opentelemetry.io/otel/sdk v1.23.0 h1:0KM9Zl2esnl+WSukEmlaAEjVY5HDZANOHferLq36BPc= +go.opentelemetry.io/otel/sdk v1.23.0/go.mod h1:wUscup7byToqyKJSilEtMf34FgdCAsFpFOjXnAwFfO0= +go.opentelemetry.io/otel/sdk/metric v1.23.0 h1:u81lMvmK6GMgN4Fty7K7S6cSKOZhMKJMK2TB+KaTs0I= +go.opentelemetry.io/otel/sdk/metric v1.23.0/go.mod h1:2LUOToN/FdX6wtfpHybOnCZjoZ6ViYajJYMiJ1LKDtQ= +go.opentelemetry.io/otel/trace v1.23.0 h1:37Ik5Ib7xfYVb4V1UtnT97T1jI+AoIYkJyPkuL4iJgI= +go.opentelemetry.io/otel/trace v1.23.0/go.mod h1:GSGTbIClEsuZrGIzoEHqsVfxgn5UkggkflQwDScNUsk= go.opentelemetry.io/proto/otlp v1.0.0 h1:T0TX0tmXU8a3CbNXzEKGeU5mIVOdf0oykP+u2lIVU/I= go.opentelemetry.io/proto/otlp v1.0.0/go.mod h1:Sy6pihPLfYHkr3NkUbEhGHFhINUSI/v80hjKIs5JXpM= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= golang.org/x/net v0.33.0 h1:74SYHlV8BIgHIFC/LrYkOGIwL19eTYXQ5wc6TBuO36I= golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4= -golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA= -golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU= +golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo= golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= diff --git a/extra/redisotel/config.go b/extra/redisotel/config.go index c02ee0b31..1119780c1 100644 --- a/extra/redisotel/config.go +++ b/extra/redisotel/config.go @@ -1,6 +1,8 @@ package redisotel import ( + "context" + "go.opentelemetry.io/otel" "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/metric" @@ -11,8 +13,9 @@ import ( type config struct { // Common options. - dbSystem string - attrs []attribute.KeyValue + dbSystem string + attrs []attribute.KeyValue + attrsFunc func(context.Context) []attribute.KeyValue // Tracing options. @@ -51,8 +54,9 @@ func (fn option) metrics() {} func newConfig(opts ...baseOption) *config { conf := &config{ - dbSystem: "redis", - attrs: []attribute.KeyValue{}, + dbSystem: "redis", + attrs: []attribute.KeyValue{}, + attrsFunc: func(ctx context.Context) []attribute.KeyValue { return []attribute.KeyValue{} }, tp: otel.GetTracerProvider(), mp: otel.GetMeterProvider(), @@ -81,6 +85,14 @@ func WithAttributes(attrs ...attribute.KeyValue) Option { }) } +// WithAttributesFunc takes a function that returns additional attributes to be added using the context. +// This is executed only in ProcessPipelineHook and ProcessHook +func WithAttributesFunc(f func(context.Context) []attribute.KeyValue) Option { + return option(func(conf *config) { + conf.attrsFunc = f + }) +} + //------------------------------------------------------------------------------ type TracingOption interface { diff --git a/extra/redisotel/go.mod b/extra/redisotel/go.mod index ab6288dec..353a08a24 100644 --- a/extra/redisotel/go.mod +++ b/extra/redisotel/go.mod @@ -9,18 +9,24 @@ replace github.com/redis/go-redis/extra/rediscmd/v9 => ../rediscmd require ( github.com/redis/go-redis/extra/rediscmd/v9 v9.7.1 github.com/redis/go-redis/v9 v9.7.1 + github.com/stretchr/testify v1.10.0 go.opentelemetry.io/otel v1.22.0 go.opentelemetry.io/otel/metric v1.22.0 go.opentelemetry.io/otel/sdk v1.22.0 + go.opentelemetry.io/otel/sdk/metric v1.22.0 go.opentelemetry.io/otel/trace v1.22.0 + ) require ( github.com/cespare/xxhash/v2 v2.3.0 // indirect + github.com/davecgh/go-spew v1.1.1 // indirect github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect - github.com/go-logr/logr v1.4.1 // indirect + github.com/go-logr/logr v1.4.2 // indirect github.com/go-logr/stdr v1.2.2 // indirect - golang.org/x/sys v0.16.0 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect + golang.org/x/sys v0.29.0 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect ) retract v9.5.3 // This version was accidentally released. diff --git a/extra/redisotel/go.sum b/extra/redisotel/go.sum index 4b832c80f..21c863c34 100644 --- a/extra/redisotel/go.sum +++ b/extra/redisotel/go.sum @@ -3,24 +3,32 @@ github.com/bsm/gomega v1.27.10 h1:yeMWxP2pV2fG3FgAODIY8EiRE3dy0aeFYt4l7wh6yKA= github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78= github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= -github.com/go-logr/logr v1.4.1 h1:pKouT5E8xu9zeFC39JXRDukb6JFQPXM5p5I91188VAQ= -github.com/go-logr/logr v1.4.1/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= +github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= -github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= +github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= go.opentelemetry.io/otel v1.22.0 h1:xS7Ku+7yTFvDfDraDIJVpw7XPyuHlB9MCiqqX5mcJ6Y= go.opentelemetry.io/otel v1.22.0/go.mod h1:eoV4iAi3Ea8LkAEI9+GFT44O6T/D0GWAVFyZVCC6pMI= go.opentelemetry.io/otel/metric v1.22.0 h1:lypMQnGyJYeuYPhOM/bgjbFM6WE44W1/T45er4d8Hhg= go.opentelemetry.io/otel/metric v1.22.0/go.mod h1:evJGjVpZv0mQ5QBRJoBF64yMuOf4xCWdXjK8pzFvliY= go.opentelemetry.io/otel/sdk v1.22.0 h1:6coWHw9xw7EfClIC/+O31R8IY3/+EiRFHevmHafB2Gw= go.opentelemetry.io/otel/sdk v1.22.0/go.mod h1:iu7luyVGYovrRpe2fmj3CVKouQNdTOkxtLzPvPz1DOc= +go.opentelemetry.io/otel/sdk/metric v1.22.0 h1:ARrRetm1HCVxq0cbnaZQlfwODYJHo3gFL8Z3tSmHBcI= +go.opentelemetry.io/otel/sdk/metric v1.22.0/go.mod h1:KjQGeMIDlBNEOo6HvjhxIec1p/69/kULDcp4gr0oLQQ= go.opentelemetry.io/otel/trace v1.22.0 h1:Hg6pPujv0XG9QaVbGOBVHunyuLcCC3jN7WEhPx83XD0= go.opentelemetry.io/otel/trace v1.22.0/go.mod h1:RbbHXVqKES9QhzZq/fE5UnOSILqRt40a21sPw2He1xo= -golang.org/x/sys v0.16.0 h1:xWw16ngr6ZMtmxDyKyIgsE93KNKz5HKmMa3b8ALHidU= -golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU= +golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/extra/redisotel/metrics.go b/extra/redisotel/metrics.go index 915838f34..66a5da969 100644 --- a/extra/redisotel/metrics.go +++ b/extra/redisotel/metrics.go @@ -175,6 +175,7 @@ func addMetricsHook(rdb *redis.Client, conf *config) error { createTime: createTime, useTime: useTime, attrs: conf.attrs, + attrsFunc: conf.attrsFunc, }) return nil } @@ -183,6 +184,7 @@ type metricsHook struct { createTime metric.Float64Histogram useTime metric.Float64Histogram attrs []attribute.KeyValue + attrsFunc func(context.Context) []attribute.KeyValue } var _ redis.Hook = (*metricsHook)(nil) @@ -212,7 +214,10 @@ func (mh *metricsHook) ProcessHook(hook redis.ProcessHook) redis.ProcessHook { dur := time.Since(start) - attrs := make([]attribute.KeyValue, 0, len(mh.attrs)+2) + customAttrs := mh.attrsFunc(ctx) + + attrs := make([]attribute.KeyValue, 0, len(mh.attrs) + len(customAttrs) + 2) + attrs = append(attrs, customAttrs...) attrs = append(attrs, mh.attrs...) attrs = append(attrs, attribute.String("type", "command")) attrs = append(attrs, statusAttr(err)) @@ -233,7 +238,10 @@ func (mh *metricsHook) ProcessPipelineHook( dur := time.Since(start) - attrs := make([]attribute.KeyValue, 0, len(mh.attrs)+2) + customAttrs := mh.attrsFunc(ctx) + + attrs := make([]attribute.KeyValue, 0, len(mh.attrs) + len(customAttrs) + 2) + attrs = append(attrs, customAttrs...) attrs = append(attrs, mh.attrs...) attrs = append(attrs, attribute.String("type", "pipeline")) attrs = append(attrs, statusAttr(err)) diff --git a/extra/redisotel/metrics_test.go b/extra/redisotel/metrics_test.go new file mode 100644 index 000000000..aecf60da9 --- /dev/null +++ b/extra/redisotel/metrics_test.go @@ -0,0 +1,147 @@ +package redisotel + +import ( + "context" + "testing" + + "github.com/redis/go-redis/v9" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "go.opentelemetry.io/otel/attribute" + "go.opentelemetry.io/otel/metric" + "go.opentelemetry.io/otel/sdk/instrumentation" + sdkmetric "go.opentelemetry.io/otel/sdk/metric" + "go.opentelemetry.io/otel/sdk/metric/metricdata" + "go.opentelemetry.io/otel/sdk/metric/metricdata/metricdatatest" + semconv "go.opentelemetry.io/otel/semconv/v1.4.0" +) + +var instrumentationScope = instrumentation.Scope{ + Name: instrumName, + Version: "semver:" + redis.Version(), +} + +func setupMetrics(conf *config) (*sdkmetric.ManualReader, *redis.Client) { + reader := sdkmetric.NewManualReader() + mp := sdkmetric.NewMeterProvider(sdkmetric.WithReader(reader)) + conf.mp = mp + + rdb := redis.NewClient(&redis.Options{ + Addr: ":6379", + }) + if conf.meter == nil { + conf.meter = conf.mp.Meter( + instrumName, + metric.WithInstrumentationVersion("semver:"+redis.Version()), + ) + } + addMetricsHook(rdb, conf) + return reader, rdb +} + +func TestMetrics(t *testing.T) { + reader, rdb := setupMetrics(newConfig()) + rdb.Ping(context.Background()) + + want := metricdata.ScopeMetrics{ + Scope: instrumentationScope, + Metrics: []metricdata.Metrics{ + { + Name: "db.client.connections.create_time", + Description: "The time it took to create a new connection.", + Unit: "ms", + Data: metricdata.Histogram[float64]{ + Temporality: metricdata.CumulativeTemporality, + DataPoints: []metricdata.HistogramDataPoint[float64]{ + { + Attributes: attribute.NewSet( + semconv.DBSystemRedis, + attribute.String("status", "ok"), + ), + }, + }, + }, + }, + { + Name: "db.client.connections.use_time", + Description: "The time between borrowing a connection and returning it to the pool.", + Unit: "ms", + Data: metricdata.Histogram[float64]{ + Temporality: metricdata.CumulativeTemporality, + DataPoints: []metricdata.HistogramDataPoint[float64]{ + { + Attributes: attribute.NewSet( + semconv.DBSystemRedis, + attribute.String("type", "command"), + attribute.String("status", "ok"), + ), + }, + }, + }, + }, + }, + } + rm := metricdata.ResourceMetrics{} + err := reader.Collect(context.Background(), &rm) + assert.NoError(t, err) + require.Len(t, rm.ScopeMetrics, 1) + metricdatatest.AssertEqual(t, want, rm.ScopeMetrics[0], metricdatatest.IgnoreTimestamp(), metricdatatest.IgnoreValue()) +} + +func TestCustomAttributes(t *testing.T) { + customAttrFn := func(ctx context.Context) []attribute.KeyValue { + return []attribute.KeyValue{ + attribute.String("custom", "value"), + } + } + config := newConfig(WithAttributesFunc(customAttrFn)) + reader, rdb := setupMetrics(config) + + rdb.Ping(context.Background()) + + want := metricdata.ScopeMetrics{ + Scope: instrumentationScope, + Metrics: []metricdata.Metrics{ + { + Name: "db.client.connections.create_time", + Description: "The time it took to create a new connection.", + Unit: "ms", + Data: metricdata.Histogram[float64]{ + Temporality: metricdata.CumulativeTemporality, + DataPoints: []metricdata.HistogramDataPoint[float64]{ + { + Attributes: attribute.NewSet( + semconv.DBSystemRedis, + attribute.String("status", "ok"), + ), + }, + }, + }, + }, + { + Name: "db.client.connections.use_time", + Description: "The time between borrowing a connection and returning it to the pool.", + Unit: "ms", + Data: metricdata.Histogram[float64]{ + Temporality: metricdata.CumulativeTemporality, + DataPoints: []metricdata.HistogramDataPoint[float64]{ + { + Attributes: attribute.NewSet( + semconv.DBSystemRedis, + attribute.String("type", "command"), + attribute.String("status", "ok"), + attribute.String("custom", "value"), + ), + }, + }, + }, + }, + }, + } + rm := metricdata.ResourceMetrics{} + err := reader.Collect(context.Background(), &rm) + assert.NoError(t, err) + require.Len(t, rm.ScopeMetrics, 1) + metricdatatest.AssertEqual(t, want, rm.ScopeMetrics[0], metricdatatest.IgnoreTimestamp(), metricdatatest.IgnoreValue()) +} diff --git a/extra/redisotel/tracing.go b/extra/redisotel/tracing.go index 33b7abac1..decfec3ee 100644 --- a/extra/redisotel/tracing.go +++ b/extra/redisotel/tracing.go @@ -103,7 +103,10 @@ func (th *tracingHook) ProcessHook(hook redis.ProcessHook) redis.ProcessHook { return func(ctx context.Context, cmd redis.Cmder) error { fn, file, line := funcFileLine("github.com/redis/go-redis") - attrs := make([]attribute.KeyValue, 0, 8) + + customAttrs := th.conf.attrsFunc(ctx) + + attrs := make([]attribute.KeyValue, 0, len(customAttrs) + 8) attrs = append(attrs, semconv.CodeFunction(fn), semconv.CodeFilepath(file), @@ -116,6 +119,7 @@ func (th *tracingHook) ProcessHook(hook redis.ProcessHook) redis.ProcessHook { } opts := th.spanOpts + opts = append(opts, trace.WithAttributes(customAttrs...)) opts = append(opts, trace.WithAttributes(attrs...)) ctx, span := th.conf.tracer.Start(ctx, cmd.FullName(), opts...) @@ -135,7 +139,9 @@ func (th *tracingHook) ProcessPipelineHook( return func(ctx context.Context, cmds []redis.Cmder) error { fn, file, line := funcFileLine("github.com/redis/go-redis") - attrs := make([]attribute.KeyValue, 0, 8) + customAttrs := th.conf.attrsFunc(ctx) + + attrs := make([]attribute.KeyValue, 0, len(customAttrs) + 8) attrs = append(attrs, semconv.CodeFunction(fn), semconv.CodeFilepath(file), @@ -149,6 +155,7 @@ func (th *tracingHook) ProcessPipelineHook( } opts := th.spanOpts + opts = append(opts, trace.WithAttributes(customAttrs...)) opts = append(opts, trace.WithAttributes(attrs...)) ctx, span := th.conf.tracer.Start(ctx, "redis.pipeline "+summary, opts...) diff --git a/extra/redisotel/tracing_test.go b/extra/redisotel/tracing_test.go index bbe828144..75b714662 100644 --- a/extra/redisotel/tracing_test.go +++ b/extra/redisotel/tracing_test.go @@ -117,12 +117,23 @@ func TestTracingHook_DialHook(t *testing.T) { } } +func customAttrFn(ctx context.Context) []attribute.KeyValue { + + attributes := make([]attribute.KeyValue, 0) + + if method, ok := ctx.Value(semconv.RPCMethodKey).(string); ok { + attributes = append(attributes, semconv.RPCMethodKey.String(method)) + } + + return attributes +} func TestTracingHook_ProcessHook(t *testing.T) { imsb := tracetest.NewInMemoryExporter() provider := sdktrace.NewTracerProvider(sdktrace.WithSyncer(imsb)) hook := newTracingHook( "redis://localhost:6379", WithTracerProvider(provider), + WithAttributesFunc(customAttrFn), ) tests := []struct { @@ -141,7 +152,9 @@ func TestTracingHook_ProcessHook(t *testing.T) { processHook := hook.ProcessHook(func(ctx context.Context, cmd redis.Cmder) error { return tt.errTest }) - assertEqual(t, tt.errTest, processHook(context.Background(), cmd)) + + ctx := context.WithValue(context.Background(), semconv.RPCMethodKey, "ping") + assertEqual(t, tt.errTest, processHook(ctx, cmd)) assertEqual(t, 1, len(imsb.GetSpans())) spanData := imsb.GetSpans()[0] @@ -151,6 +164,8 @@ func TestTracingHook_ProcessHook(t *testing.T) { assertAttributeContains(t, spanData.Attributes, semconv.DBSystemRedis) assertAttributeContains(t, spanData.Attributes, semconv.DBConnectionStringKey.String("redis://localhost:6379")) assertAttributeContains(t, spanData.Attributes, semconv.DBStatementKey.String("ping")) + // check for custom attribute + assertAttributeContains(t, spanData.Attributes, semconv.RPCMethodKey.String("ping")) if tt.errTest == nil { assertEqual(t, 0, len(spanData.Events))