Skip to content

Commit 7706f88

Browse files
Sovietacedndyakov
andcommitted
Add support for filtering traces for certain commands (#3519)
* Add support for filtering commands when tracing Signed-off-by: Jason Parraga <[email protected]> * Filter sensitive data by default Signed-off-by: Jason Parraga <[email protected]> * Address comments Signed-off-by: Jason Parraga <[email protected]> --------- Signed-off-by: Jason Parraga <[email protected]> Co-authored-by: Nedyalko Dyakov <[email protected]>
1 parent 2da6ca0 commit 7706f88

File tree

3 files changed

+173
-0
lines changed

3 files changed

+173
-0
lines changed

extra/redisotel/config.go

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
package redisotel
22

33
import (
4+
"strings"
5+
6+
"github.com/redis/go-redis/v9"
47
"go.opentelemetry.io/otel"
58
"go.opentelemetry.io/otel/attribute"
69
"go.opentelemetry.io/otel/metric"
@@ -21,6 +24,7 @@ type config struct {
2124

2225
dbStmtEnabled bool
2326
callerEnabled bool
27+
filter func(cmd redis.Cmder) bool
2428

2529
// Metrics options.
2630

@@ -124,6 +128,37 @@ func WithCallerEnabled(on bool) TracingOption {
124128
})
125129
}
126130

131+
// WithCommandFilter allows filtering of commands when tracing to omit commands that may have sensitive details like
132+
// passwords.
133+
func WithCommandFilter(filter func(cmd redis.Cmder) bool) TracingOption {
134+
return tracingOption(func(conf *config) {
135+
conf.filter = filter
136+
})
137+
}
138+
139+
func BasicCommandFilter(cmd redis.Cmder) bool {
140+
if strings.ToLower(cmd.Name()) == "auth" {
141+
return true
142+
}
143+
144+
if strings.ToLower(cmd.Name()) == "hello" {
145+
if len(cmd.Args()) < 3 {
146+
return false
147+
}
148+
149+
arg, exists := cmd.Args()[2].(string)
150+
if !exists {
151+
return false
152+
}
153+
154+
if strings.ToLower(arg) == "auth" {
155+
return true
156+
}
157+
}
158+
159+
return false
160+
}
161+
127162
//------------------------------------------------------------------------------
128163

129164
type MetricsOption interface {

extra/redisotel/tracing.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,12 @@ func (th *tracingHook) DialHook(hook redis.DialHook) redis.DialHook {
102102
func (th *tracingHook) ProcessHook(hook redis.ProcessHook) redis.ProcessHook {
103103
return func(ctx context.Context, cmd redis.Cmder) error {
104104

105+
// Check if the command should be filtered out
106+
if th.conf.filter != nil && th.conf.filter(cmd) {
107+
// If so, just call the next hook
108+
return hook(ctx, cmd)
109+
}
110+
105111
attrs := make([]attribute.KeyValue, 0, 8)
106112
if th.conf.callerEnabled {
107113
fn, file, line := funcFileLine("github.com/redis/go-redis")

extra/redisotel/tracing_test.go

Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,138 @@ func TestWithoutCaller(t *testing.T) {
9595
}
9696
}
9797

98+
func TestWithCommandFilter(t *testing.T) {
99+
100+
t.Run("filter out ping command", func(t *testing.T) {
101+
provider := sdktrace.NewTracerProvider()
102+
hook := newTracingHook(
103+
"",
104+
WithTracerProvider(provider),
105+
WithCommandFilter(func(cmd redis.Cmder) bool {
106+
return cmd.Name() == "ping"
107+
}),
108+
)
109+
ctx, span := provider.Tracer("redis-test").Start(context.TODO(), "redis-test")
110+
cmd := redis.NewCmd(ctx, "ping")
111+
defer span.End()
112+
113+
processHook := hook.ProcessHook(func(ctx context.Context, cmd redis.Cmder) error {
114+
innerSpan := trace.SpanFromContext(ctx).(sdktrace.ReadOnlySpan)
115+
if innerSpan.Name() != "redis-test" || innerSpan.Name() == "ping" {
116+
t.Fatalf("ping command should not be traced")
117+
}
118+
119+
return nil
120+
})
121+
err := processHook(ctx, cmd)
122+
if err != nil {
123+
t.Fatal(err)
124+
}
125+
})
126+
127+
t.Run("do not filter ping command", func(t *testing.T) {
128+
provider := sdktrace.NewTracerProvider()
129+
hook := newTracingHook(
130+
"",
131+
WithTracerProvider(provider),
132+
WithCommandFilter(func(cmd redis.Cmder) bool {
133+
return false // never filter
134+
}),
135+
)
136+
ctx, span := provider.Tracer("redis-test").Start(context.TODO(), "redis-test")
137+
cmd := redis.NewCmd(ctx, "ping")
138+
defer span.End()
139+
140+
processHook := hook.ProcessHook(func(ctx context.Context, cmd redis.Cmder) error {
141+
innerSpan := trace.SpanFromContext(ctx).(sdktrace.ReadOnlySpan)
142+
if innerSpan.Name() != "ping" {
143+
t.Fatalf("ping command should be traced")
144+
}
145+
146+
return nil
147+
})
148+
err := processHook(ctx, cmd)
149+
if err != nil {
150+
t.Fatal(err)
151+
}
152+
})
153+
154+
t.Run("auth command filtered with basic command filter", func(t *testing.T) {
155+
provider := sdktrace.NewTracerProvider()
156+
hook := newTracingHook(
157+
"",
158+
WithTracerProvider(provider),
159+
WithCommandFilter(BasicCommandFilter),
160+
)
161+
ctx, span := provider.Tracer("redis-test").Start(context.TODO(), "redis-test")
162+
cmd := redis.NewCmd(ctx, "auth", "test-password")
163+
defer span.End()
164+
165+
processHook := hook.ProcessHook(func(ctx context.Context, cmd redis.Cmder) error {
166+
innerSpan := trace.SpanFromContext(ctx).(sdktrace.ReadOnlySpan)
167+
if innerSpan.Name() != "redis-test" || innerSpan.Name() == "auth" {
168+
t.Fatalf("auth command should not be traced by default")
169+
}
170+
171+
return nil
172+
})
173+
err := processHook(ctx, cmd)
174+
if err != nil {
175+
t.Fatal(err)
176+
}
177+
})
178+
179+
t.Run("hello command filtered with basic command filter when sensitive", func(t *testing.T) {
180+
provider := sdktrace.NewTracerProvider()
181+
hook := newTracingHook(
182+
"",
183+
WithTracerProvider(provider),
184+
WithCommandFilter(BasicCommandFilter),
185+
)
186+
ctx, span := provider.Tracer("redis-test").Start(context.TODO(), "redis-test")
187+
cmd := redis.NewCmd(ctx, "hello", 3, "AUTH", "test-user", "test-password")
188+
defer span.End()
189+
190+
processHook := hook.ProcessHook(func(ctx context.Context, cmd redis.Cmder) error {
191+
innerSpan := trace.SpanFromContext(ctx).(sdktrace.ReadOnlySpan)
192+
if innerSpan.Name() != "redis-test" || innerSpan.Name() == "hello" {
193+
t.Fatalf("auth command should not be traced by default")
194+
}
195+
196+
return nil
197+
})
198+
err := processHook(ctx, cmd)
199+
if err != nil {
200+
t.Fatal(err)
201+
}
202+
})
203+
204+
t.Run("hello command not filtered with basic command filter when not sensitive", func(t *testing.T) {
205+
provider := sdktrace.NewTracerProvider()
206+
hook := newTracingHook(
207+
"",
208+
WithTracerProvider(provider),
209+
WithCommandFilter(BasicCommandFilter),
210+
)
211+
ctx, span := provider.Tracer("redis-test").Start(context.TODO(), "redis-test")
212+
cmd := redis.NewCmd(ctx, "hello", 3)
213+
defer span.End()
214+
215+
processHook := hook.ProcessHook(func(ctx context.Context, cmd redis.Cmder) error {
216+
innerSpan := trace.SpanFromContext(ctx).(sdktrace.ReadOnlySpan)
217+
if innerSpan.Name() != "hello" {
218+
t.Fatalf("hello command should be traced")
219+
}
220+
221+
return nil
222+
})
223+
err := processHook(ctx, cmd)
224+
if err != nil {
225+
t.Fatal(err)
226+
}
227+
})
228+
}
229+
98230
func TestTracingHook_DialHook(t *testing.T) {
99231
imsb := tracetest.NewInMemoryExporter()
100232
provider := sdktrace.NewTracerProvider(sdktrace.WithSyncer(imsb))

0 commit comments

Comments
 (0)