diff --git a/extra/redisotel/go.mod b/extra/redisotel/go.mod index 48b7d36c..fd8aaa02 100644 --- a/extra/redisotel/go.mod +++ b/extra/redisotel/go.mod @@ -9,6 +9,6 @@ replace github.com/go-redis/redis/extra/rediscmd/v8 => ../rediscmd require ( github.com/go-redis/redis/extra/rediscmd/v8 v8.11.4 github.com/go-redis/redis/v8 v8.11.4 - go.opentelemetry.io/otel v1.0.0 - go.opentelemetry.io/otel/trace v1.0.0 + go.opentelemetry.io/otel v1.3.0 + go.opentelemetry.io/otel/trace v1.3.0 ) diff --git a/extra/redisotel/go.sum b/extra/redisotel/go.sum index 82aa0b0d..df8ba140 100644 --- a/extra/redisotel/go.sum +++ b/extra/redisotel/go.sum @@ -8,6 +8,11 @@ github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cu github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4= github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= +github.com/go-logr/logr v1.2.0/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/logr v1.2.1 h1:DX7uPQ4WgAWfoh+NGGlbJQswnYIVvz0SRlLS3rPZQDA= +github.com/go-logr/logr v1.2.1/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/stdr v1.2.0 h1:j4LrlVXgrbIWO83mmQUnK0Hi+YnbD+vzrE1z/EphbFE= +github.com/go-logr/stdr v1.2.0/go.mod h1:YkVgnZu1ZjjL7xTxrfm/LLZBfkhTqSR1ydtm6jTKKwI= github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= @@ -31,13 +36,14 @@ github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE= github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU= github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= -github.com/onsi/ginkgo v1.16.4 h1:29JGrr5oVBm5ulCWet69zQkzWipVXIol6ygQUe/EzNc= github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vvnwo0= +github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE= github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU= github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= -github.com/onsi/gomega v1.16.0 h1:6gjqkI8iiRHMvdccRJM8rVKjCWk6ZIm6FTm3ddIe4/c= github.com/onsi/gomega v1.16.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY= +github.com/onsi/gomega v1.17.0 h1:9Luw4uT5HTjHTN8+aNcSThgH1vdXnmdJ8xIfZ4wyTRE= +github.com/onsi/gomega v1.17.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= @@ -45,10 +51,10 @@ github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5 github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= -go.opentelemetry.io/otel v1.0.0 h1:qTTn6x71GVBvoafHK/yaRUmFzI4LcONZD0/kXxl5PHI= -go.opentelemetry.io/otel v1.0.0/go.mod h1:AjRVh9A5/5DE7S+mZtTR6t8vpKKryam+0lREnfmS4cg= -go.opentelemetry.io/otel/trace v1.0.0 h1:TSBr8GTEtKevYMG/2d21M989r5WJYVimhTHBKVEZuh4= -go.opentelemetry.io/otel/trace v1.0.0/go.mod h1:PXTWqayeFUlJV1YDNhsJYB184+IvAH814St6o6ajzIs= +go.opentelemetry.io/otel v1.3.0 h1:APxLf0eiBwLl+SOXiJJCVYzA1OOJNyAoV8C5RNRyy7Y= +go.opentelemetry.io/otel v1.3.0/go.mod h1:PWIKzi6JCp7sM0k9yZ43VX+T345uNbAkDKwHVjb2PTs= +go.opentelemetry.io/otel/trace v1.3.0 h1:doy8Hzb1RJ+I3yFhtDmwNc7tIyw1tNMOIsyPzp1NOGY= +go.opentelemetry.io/otel/trace v1.3.0/go.mod h1:c/VDhno8888bvQYmbYLqe41/Ldmr/KKunbvWM4/fEjk= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= diff --git a/extra/redisotel/redisotel.go b/extra/redisotel/redisotel.go index dff869ae..96ce7a6a 100644 --- a/extra/redisotel/redisotel.go +++ b/extra/redisotel/redisotel.go @@ -6,42 +6,57 @@ import ( "go.opentelemetry.io/otel" "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/codes" + semconv "go.opentelemetry.io/otel/semconv/v1.7.0" "go.opentelemetry.io/otel/trace" "github.com/go-redis/redis/extra/rediscmd/v8" "github.com/go-redis/redis/v8" ) -var tracer = otel.Tracer("github.com/go-redis/redis") +const ( + // todo: consider using the full module path "github.com/go-redis/redis/extra/redisotel" + defaultTracerName = "github.com/go-redis/redis" +) -type TracingHook struct{} - -var _ redis.Hook = (*TracingHook)(nil) - -func NewTracingHook() *TracingHook { - return new(TracingHook) +type TracingHook struct { + tracer trace.Tracer } -func (TracingHook) BeforeProcess(ctx context.Context, cmd redis.Cmder) (context.Context, error) { +func NewTracingHook(opts ...Option) *TracingHook { + cfg := &config{ + tp: otel.GetTracerProvider(), + } + for _, opt := range opts { + opt.apply(cfg) + } + + tracer := cfg.tp.Tracer( + defaultTracerName, + // todo: consider adding a version + // trace.WithInstrumentationVersion("semver:8.11.4"), + ) + return &TracingHook{tracer: tracer} +} + +func (th *TracingHook) BeforeProcess(ctx context.Context, cmd redis.Cmder) (context.Context, error) { if !trace.SpanFromContext(ctx).IsRecording() { return ctx, nil } - attrs := []attribute.KeyValue{ - attribute.String("db.system", "redis"), - attribute.String("db.statement", rediscmd.CmdString(cmd)), - } opts := []trace.SpanStartOption{ trace.WithSpanKind(trace.SpanKindClient), - trace.WithAttributes(attrs...), + trace.WithAttributes( + semconv.DBSystemRedis, + semconv.DBStatementKey.String(rediscmd.CmdString(cmd)), + ), } - ctx, _ = tracer.Start(ctx, cmd.FullName(), opts...) + ctx, _ = th.tracer.Start(ctx, cmd.FullName(), opts...) return ctx, nil } -func (TracingHook) AfterProcess(ctx context.Context, cmd redis.Cmder) error { +func (th *TracingHook) AfterProcess(ctx context.Context, cmd redis.Cmder) error { span := trace.SpanFromContext(ctx) if err := cmd.Err(); err != nil { recordError(ctx, span, err) @@ -50,29 +65,28 @@ func (TracingHook) AfterProcess(ctx context.Context, cmd redis.Cmder) error { return nil } -func (TracingHook) BeforeProcessPipeline(ctx context.Context, cmds []redis.Cmder) (context.Context, error) { +func (th *TracingHook) BeforeProcessPipeline(ctx context.Context, cmds []redis.Cmder) (context.Context, error) { if !trace.SpanFromContext(ctx).IsRecording() { return ctx, nil } summary, cmdsString := rediscmd.CmdsString(cmds) - attrs := []attribute.KeyValue{ - attribute.String("db.system", "redis"), - attribute.Int("db.redis.num_cmd", len(cmds)), - attribute.String("db.statement", cmdsString), - } opts := []trace.SpanStartOption{ trace.WithSpanKind(trace.SpanKindClient), - trace.WithAttributes(attrs...), + trace.WithAttributes( + semconv.DBSystemRedis, + semconv.DBStatementKey.String(cmdsString), + attribute.Int("db.redis.num_cmd", len(cmds)), + ), } - ctx, _ = tracer.Start(ctx, "pipeline "+summary, opts...) + ctx, _ = th.tracer.Start(ctx, "pipeline "+summary, opts...) return ctx, nil } -func (TracingHook) AfterProcessPipeline(ctx context.Context, cmds []redis.Cmder) error { +func (th *TracingHook) AfterProcessPipeline(ctx context.Context, cmds []redis.Cmder) error { span := trace.SpanFromContext(ctx) if err := cmds[0].Err(); err != nil { recordError(ctx, span, err) @@ -87,3 +101,28 @@ func recordError(ctx context.Context, span trace.Span, err error) { span.SetStatus(codes.Error, err.Error()) } } + +type config struct { + tp trace.TracerProvider +} + +// Option specifies instrumentation configuration options. +type Option interface { + apply(*config) +} + +type optionFunc func(*config) + +func (o optionFunc) apply(c *config) { + o(c) +} + +// WithTracerProvider specifies a tracer provider to use for creating a tracer. +// If none is specified, the global provider is used. +func WithTracerProvider(provider trace.TracerProvider) Option { + return optionFunc(func(cfg *config) { + if provider != nil { + cfg.tp = provider + } + }) +} diff --git a/extra/redisotel/redisotel_test.go b/extra/redisotel/redisotel_test.go new file mode 100644 index 00000000..546b3969 --- /dev/null +++ b/extra/redisotel/redisotel_test.go @@ -0,0 +1,37 @@ +package redisotel_test + +import ( + "testing" + + "github.com/go-redis/redis/extra/redisotel/v8" + "github.com/go-redis/redis/v8" + "go.opentelemetry.io/otel" + "go.opentelemetry.io/otel/trace" +) + +func TestNew(t *testing.T) { + // this also functions as a compile-time test that the + // TracingHook conforms to the Hook interface + var _ redis.Hook = redisotel.NewTracingHook() +} + +type providerFunc func(name string, opts ...trace.TracerOption) trace.Tracer + +func (fn providerFunc) Tracer(name string, opts ...trace.TracerOption) trace.Tracer { + return fn(name, opts...) +} + +func TestNewWithTracerProvider(t *testing.T) { + invoked := false + + tp := providerFunc(func(name string, opts ...trace.TracerOption) trace.Tracer { + invoked = true + return otel.GetTracerProvider().Tracer(name, opts...) + }) + + _ = redisotel.NewTracingHook(redisotel.WithTracerProvider(tp)) + + if !invoked { + t.Fatalf("did not call custom TraceProvider") + } +}