redis/extra/redisotel/redisotel.go

139 lines
3.2 KiB
Go
Raw Normal View History

2020-10-21 15:19:27 +03:00
package redisotel
import (
"context"
2020-11-21 10:56:52 +03:00
"go.opentelemetry.io/otel"
2021-03-04 04:43:10 +03:00
"go.opentelemetry.io/otel/attribute"
2020-11-21 10:56:52 +03:00
"go.opentelemetry.io/otel/codes"
2022-09-16 14:18:55 +03:00
semconv "go.opentelemetry.io/otel/semconv/v1.10.0"
2020-11-21 10:56:52 +03:00
"go.opentelemetry.io/otel/trace"
2021-09-08 16:00:52 +03:00
2022-06-04 17:39:21 +03:00
"github.com/go-redis/redis/extra/rediscmd/v9"
"github.com/go-redis/redis/v9"
2020-10-21 15:19:27 +03:00
)
const (
defaultTracerName = "github.com/go-redis/redis/extra/redisotel"
)
2020-10-21 15:19:27 +03:00
type TracingHook struct {
tracer trace.Tracer
attrs []attribute.KeyValue
}
2020-10-21 15:19:27 +03:00
func NewTracingHook(opts ...Option) *TracingHook {
cfg := &config{
tp: otel.GetTracerProvider(),
attrs: []attribute.KeyValue{
semconv.DBSystemRedis,
},
}
for _, opt := range opts {
opt.apply(cfg)
}
2021-04-16 15:00:10 +03:00
tracer := cfg.tp.Tracer(
defaultTracerName,
trace.WithInstrumentationVersion("semver:"+redis.Version()),
)
return &TracingHook{tracer: tracer, attrs: cfg.attrs}
2021-04-16 15:00:10 +03:00
}
2020-10-21 15:19:27 +03:00
func (th *TracingHook) BeforeProcess(ctx context.Context, cmd redis.Cmder) (context.Context, error) {
2020-10-21 15:19:27 +03:00
if !trace.SpanFromContext(ctx).IsRecording() {
return ctx, nil
}
opts := []trace.SpanStartOption{
trace.WithSpanKind(trace.SpanKindClient),
trace.WithAttributes(th.attrs...),
trace.WithAttributes(
semconv.DBStatementKey.String(rediscmd.CmdString(cmd)),
),
}
ctx, _ = th.tracer.Start(ctx, cmd.FullName(), opts...)
2020-10-21 15:19:27 +03:00
return ctx, nil
}
func (th *TracingHook) AfterProcess(ctx context.Context, cmd redis.Cmder) error {
2020-10-21 15:19:27 +03:00
span := trace.SpanFromContext(ctx)
if err := cmd.Err(); err != nil {
recordError(ctx, span, err)
}
span.End()
return nil
}
func (th *TracingHook) BeforeProcessPipeline(ctx context.Context, cmds []redis.Cmder) (context.Context, error) {
2020-10-21 15:19:27 +03:00
if !trace.SpanFromContext(ctx).IsRecording() {
return ctx, nil
}
summary, cmdsString := rediscmd.CmdsString(cmds)
opts := []trace.SpanStartOption{
trace.WithSpanKind(trace.SpanKindClient),
trace.WithAttributes(th.attrs...),
trace.WithAttributes(
semconv.DBStatementKey.String(cmdsString),
attribute.Int("db.redis.num_cmd", len(cmds)),
),
}
ctx, _ = th.tracer.Start(ctx, "pipeline "+summary, opts...)
2020-10-21 15:19:27 +03:00
return ctx, nil
}
func (th *TracingHook) AfterProcessPipeline(ctx context.Context, cmds []redis.Cmder) error {
2020-10-21 15:19:27 +03:00
span := trace.SpanFromContext(ctx)
if err := cmds[0].Err(); err != nil {
recordError(ctx, span, err)
}
span.End()
return nil
}
func recordError(ctx context.Context, span trace.Span, err error) {
if err != redis.Nil {
2020-11-21 10:56:52 +03:00
span.RecordError(err)
span.SetStatus(codes.Error, err.Error())
2020-10-21 15:19:27 +03:00
}
}
type config struct {
tp trace.TracerProvider
attrs []attribute.KeyValue
}
// 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
}
})
}
// WithAttributes specifies additional attributes to be added to the span.
func WithAttributes(attrs ...attribute.KeyValue) Option {
return optionFunc(func(cfg *config) {
cfg.attrs = append(cfg.attrs, attrs...)
})
}