From 617aca00eeb70539be24ad3e5f0c91959752b1b6 Mon Sep 17 00:00:00 2001 From: lucawol Date: Fri, 15 Aug 2025 18:36:12 +0200 Subject: [PATCH 1/2] Add option to configure command exlusions for OTEL Tracing (#3479) --- extra/redisotel/config.go | 26 +++++++++++++++++--------- 1 file changed, 17 insertions(+), 9 deletions(-) diff --git a/extra/redisotel/config.go b/extra/redisotel/config.go index 6d90abfd0..32387078c 100644 --- a/extra/redisotel/config.go +++ b/extra/redisotel/config.go @@ -19,8 +19,9 @@ type config struct { tp trace.TracerProvider tracer trace.Tracer - dbStmtEnabled bool - callerEnabled bool + dbStmtEnabled bool + callerEnabled bool + commandExclusions []string // Metrics options. @@ -54,13 +55,13 @@ func (fn option) metrics() {} func newConfig(opts ...baseOption) *config { conf := &config{ - dbSystem: "redis", - attrs: []attribute.KeyValue{}, - - tp: otel.GetTracerProvider(), - mp: otel.GetMeterProvider(), - dbStmtEnabled: true, - callerEnabled: true, + dbSystem: "redis", + attrs: []attribute.KeyValue{}, + commandExclusions: []string{}, + tp: otel.GetTracerProvider(), + mp: otel.GetMeterProvider(), + dbStmtEnabled: true, + callerEnabled: true, } for _, opt := range opts { @@ -124,6 +125,13 @@ func WithCallerEnabled(on bool) TracingOption { }) } +// WithCommandExclusions tells the tracing hook to exclude the specified redis commands. +func WithCommandExclusions(exclusions []string) TracingOption { + return tracingOption(func(conf *config) { + conf.commandExclusions = exclusions + }) +} + //------------------------------------------------------------------------------ type MetricsOption interface { From 62cc00465bb87a05133b8d3eec0e1ac7ba5ed723 Mon Sep 17 00:00:00 2001 From: lucawol Date: Sun, 17 Aug 2025 14:39:41 +0200 Subject: [PATCH 2/2] Dont create OTEL trace of to be executed redis command, when it was part of the commandExclusions (#3479) --- extra/redisotel/tracing.go | 9 +++- extra/redisotel/tracing_test.go | 77 +++++++++++++++++++++++++++++++++ 2 files changed, 85 insertions(+), 1 deletion(-) diff --git a/extra/redisotel/tracing.go b/extra/redisotel/tracing.go index 40df5a202..e15513c5c 100644 --- a/extra/redisotel/tracing.go +++ b/extra/redisotel/tracing.go @@ -8,6 +8,8 @@ import ( "strconv" "strings" + "slices" + "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/codes" semconv "go.opentelemetry.io/otel/semconv/v1.24.0" @@ -102,6 +104,12 @@ func (th *tracingHook) DialHook(hook redis.DialHook) redis.DialHook { func (th *tracingHook) ProcessHook(hook redis.ProcessHook) redis.ProcessHook { return func(ctx context.Context, cmd redis.Cmder) error { + cmdString := rediscmd.CmdString(cmd) + + if slices.Contains(th.conf.commandExclusions, cmdString) { + return nil + } + attrs := make([]attribute.KeyValue, 0, 8) if th.conf.callerEnabled { fn, file, line := funcFileLine("github.com/redis/go-redis") @@ -113,7 +121,6 @@ func (th *tracingHook) ProcessHook(hook redis.ProcessHook) redis.ProcessHook { } if th.conf.dbStmtEnabled { - cmdString := rediscmd.CmdString(cmd) attrs = append(attrs, semconv.DBStatement(cmdString)) } diff --git a/extra/redisotel/tracing_test.go b/extra/redisotel/tracing_test.go index a3e3ccc62..2b0e6b863 100644 --- a/extra/redisotel/tracing_test.go +++ b/extra/redisotel/tracing_test.go @@ -415,6 +415,83 @@ func TestTracingHook_ProcessPipelineHook_LongCommands(t *testing.T) { } } +func TestTracingHook_ProcessHook_LongCommand_WithExlusions(t *testing.T) { + imsb := tracetest.NewInMemoryExporter() + provider := sdktrace.NewTracerProvider(sdktrace.WithSyncer(imsb)) + hook := newTracingHook( + "redis://localhost:6379", + WithTracerProvider(provider), + ) + longValue := strings.Repeat("a", 102400) + + tests := []struct { + name string + cmd redis.Cmder + expected string + }{ + { + name: "short command", + cmd: redis.NewCmd(context.Background(), "SET", "key", "value"), + expected: "SET key value", + }, + { + name: "set command with long key", + cmd: redis.NewCmd(context.Background(), "SET", longValue, "value"), + expected: "SET " + longValue + " value", + }, + { + name: "set command with long value", + cmd: redis.NewCmd(context.Background(), "SET", "key", longValue), + expected: "SET key " + longValue, + }, + { + name: "set command with long key and value", + cmd: redis.NewCmd(context.Background(), "SET", longValue, longValue), + expected: "SET " + longValue + " " + longValue, + }, + { + name: "short command with many arguments", + cmd: redis.NewCmd(context.Background(), "MSET", "key1", "value1", "key2", "value2", "key3", "value3", "key4", "value4", "key5", "value5"), + expected: "MSET key1 value1 key2 value2 key3 value3 key4 value4 key5 value5", + }, + { + name: "long command", + cmd: redis.NewCmd(context.Background(), longValue, "key", "value"), + expected: longValue + " key value", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + defer imsb.Reset() + + processHook := hook.ProcessHook(func(ctx context.Context, cmd redis.Cmder) error { + return nil + }) + + if err := processHook(context.Background(), tt.cmd); err != nil { + t.Fatal(err) + } + + assertEqual(t, 1, len(imsb.GetSpans())) + + spanData := imsb.GetSpans()[0] + + var dbStatement string + for _, attr := range spanData.Attributes { + if attr.Key == semconv.DBStatementKey { + dbStatement = attr.Value.AsString() + break + } + } + + if dbStatement != tt.expected { + t.Errorf("Expected DB statement: %q\nGot: %q", tt.expected, dbStatement) + } + }) + } +} + func assertEqual(t *testing.T, expected, actual interface{}) { t.Helper() if expected != actual {