Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
113 changes: 113 additions & 0 deletions example/otel/advanced_filtering.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
package main

import (
"context"
"fmt"
"strings"

"github.com/redis/go-redis/extra/redisotel/v9"
"github.com/redis/go-redis/v9"
)

func runAdvancedFilteringExamples() {
ctx := context.Background()

// Example 1: High-performance O(1) command exclusion
fmt.Println("=== Example 1: O(1) Command Exclusion ===")
rdb1 := redis.NewClient(&redis.Options{Addr: ":6379"})

if err := redisotel.InstrumentTracing(rdb1,
redisotel.WithExcludedCommands("PING", "INFO", "SELECT")); err != nil {
panic(err)
}

// These commands won't be traced
rdb1.Ping(ctx)
rdb1.Info(ctx)

// This command will be traced
rdb1.Set(ctx, "key1", "value1", 0)
fmt.Println("✓ O(1) exclusion configured")

// Example 2: Custom filtering logic
fmt.Println("\n=== Example 2: Custom Process Filter ===")
rdb2 := redis.NewClient(&redis.Options{Addr: ":6379"})

if err := redisotel.InstrumentTracing(rdb2,
redisotel.WithProcessFilter(func(cmd redis.Cmder) bool {
// Exclude commands that start with "DEBUG_" or "INTERNAL_"
name := strings.ToUpper(cmd.Name())
return strings.HasPrefix(name, "DEBUG_") ||
strings.HasPrefix(name, "INTERNAL_")
})); err != nil {
panic(err)
}
fmt.Println("✓ Custom process filter configured")

// Example 3: Pipeline filtering
fmt.Println("\n=== Example 3: Pipeline Filter ===")
rdb3 := redis.NewClient(&redis.Options{Addr: ":6379"})

if err := redisotel.InstrumentTracing(rdb3,
redisotel.WithProcessPipelineFilter(func(cmds []redis.Cmder) bool {
// Exclude large pipelines (>10 commands) from tracing
return len(cmds) > 10
})); err != nil {
panic(err)
}

// Small pipeline - will be traced
pipe := rdb3.Pipeline()
pipe.Set(ctx, "key1", "value1", 0)
pipe.Set(ctx, "key2", "value2", 0)
pipe.Exec(ctx)
fmt.Println("✓ Pipeline filter configured")

// Example 4: Dial filtering
fmt.Println("\n=== Example 4: Dial Filter ===")
rdb4 := redis.NewClient(&redis.Options{Addr: ":6379"})

if err := redisotel.InstrumentTracing(rdb4,
redisotel.WithDialFilterFunc(func(network, addr string) bool {
// Don't trace connections to localhost for development
return strings.Contains(addr, "localhost") ||
strings.Contains(addr, "127.0.0.1")
})); err != nil {
panic(err)
}
fmt.Println("✓ Dial filter configured")

// Example 5: Combined approach for optimal performance
fmt.Println("\n=== Example 5: Combined Filtering Approach ===")
rdb5 := redis.NewClient(&redis.Options{Addr: ":6379"})

if err := redisotel.InstrumentTracing(rdb5,
// Fast O(1) exclusion for common commands
redisotel.WithExcludedCommands("PING", "INFO", "SELECT"),
// Custom logic for additional filtering
redisotel.WithProcessFilter(func(cmd redis.Cmder) bool {
return strings.HasPrefix(strings.ToUpper(cmd.Name()), "DEBUG_")
})); err != nil {
panic(err)
}

// Test the combined approach
rdb5.Ping(ctx) // Excluded by O(1) set
rdb5.Set(ctx, "key", "value", 0) // Will be traced
fmt.Println("✓ Combined filtering approach configured")

// Example 6: Backward compatibility with legacy APIs
fmt.Println("\n=== Example 6: Legacy API Compatibility ===")
rdb6 := redis.NewClient(&redis.Options{Addr: ":6379"})

if err := redisotel.InstrumentTracing(rdb6,
// Legacy command filter still works
redisotel.WithCommandFilter(func(cmd redis.Cmder) bool {
return strings.ToUpper(cmd.Name()) == "AUTH"
})); err != nil {
panic(err)
}
fmt.Println("✓ Legacy API compatibility maintained")

fmt.Println("\n=== All filtering examples completed successfully! ===")
}
7 changes: 6 additions & 1 deletion example/otel/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,13 +33,18 @@ func main() {
rdb := redis.NewClient(&redis.Options{
Addr: ":6379",
})
if err := redisotel.InstrumentTracing(rdb); err != nil {
// Basic tracing with performance-optimized command exclusion
if err := redisotel.InstrumentTracing(rdb,
redisotel.WithExcludedCommands("PING", "INFO")); err != nil {
panic(err)
}
if err := redisotel.InstrumentMetrics(rdb); err != nil {
panic(err)
}

// Run advanced filtering examples
runAdvancedFilteringExamples()

for i := 0; i < 1e6; i++ {
ctx, rootSpan := tracer.Start(ctx, "handleRequest")

Expand Down
92 changes: 92 additions & 0 deletions extra/redisotel/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,98 @@ if err := redisotel.InstrumentMetrics(rdb); err != nil {
}
```

## Advanced Tracing Options

### High-Performance Command Filtering

For production systems, use O(1) command exclusion for optimal performance:

```go
// Recommended: O(1) command exclusion
err := redisotel.InstrumentTracing(rdb,
redisotel.WithExcludedCommands("PING", "INFO", "SELECT"))
if err != nil {
panic(err)
}
```

### Custom Filtering Logic

For complex filtering requirements, use custom filter functions:

```go
// Filter individual commands
err := redisotel.InstrumentTracing(rdb,
redisotel.WithProcessFilter(func(cmd redis.Cmder) bool {
// Return true to exclude the command from tracing
return strings.HasPrefix(cmd.Name(), "INTERNAL_")
}))
if err != nil {
panic(err)
}

// Filter pipeline commands
err := redisotel.InstrumentTracing(rdb,
redisotel.WithProcessPipelineFilter(func(cmds []redis.Cmder) bool {
// Return true to exclude pipelines with more than 10 commands
return len(cmds) > 10
}))
if err != nil {
panic(err)
}

// Filter dial operations
err := redisotel.InstrumentTracing(rdb,
redisotel.WithDialFilterFunc(func(network, addr string) bool {
// Return true to exclude connections to localhost
return strings.Contains(addr, "localhost")
}))
if err != nil {
panic(err)
}
```

### Combining Filtering Approaches

Exclusion sets are checked first for optimal performance:

```go
err := redisotel.InstrumentTracing(rdb,
// Fast O(1) exclusion for common commands
redisotel.WithExcludedCommands("PING", "INFO"),
// Custom logic for additional cases
redisotel.WithProcessFilter(func(cmd redis.Cmder) bool {
return strings.HasPrefix(cmd.Name(), "DEBUG_")
}))
if err != nil {
panic(err)
}
```

### Legacy API Compatibility

Original filtering APIs remain supported:

```go
// Legacy command filter
redisotel.WithCommandFilter(func(cmd redis.Cmder) bool {
return cmd.Name() == "AUTH" // Exclude AUTH commands
})

// Legacy pipeline filter
redisotel.WithCommandsFilter(func(cmds []redis.Cmder) bool {
for _, cmd := range cmds {
if cmd.Name() == "AUTH" {
return true // Exclude pipelines with AUTH commands
}
}
return false
})

// Legacy dial filter
redisotel.WithDialFilter(true) // Enable dial filtering
```

See [example](../../example/otel) and
[Monitoring Go Redis Performance and Errors](https://redis.uptrace.dev/guide/go-redis-monitoring.html)
for details.
60 changes: 55 additions & 5 deletions extra/redisotel/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,14 +19,21 @@ type config struct {

// Tracing options.

tp trace.TracerProvider
tracer trace.Tracer
tp trace.TracerProvider
tracer trace.Tracer
dbStmtEnabled bool
callerEnabled bool
excludedCommands map[string]struct{}

dbStmtEnabled bool
callerEnabled bool
// Legacy filters
filterDial bool
filterProcessPipeline func(cmds []redis.Cmder) bool
filterProcess func(cmd redis.Cmder) bool
filterProcessPipeline func(cmds []redis.Cmder) bool

// Unified filters
unifiedProcessFilter func(cmd redis.Cmder) bool
unifiedProcessPipelineFilter func(cmds []redis.Cmder) bool
unifiedDialFilter func(network, addr string) bool

// Metrics options.

Expand Down Expand Up @@ -76,6 +83,7 @@ func newConfig(opts ...baseOption) *config {
}
return false
},
excludedCommands: make(map[string]struct{}),
}

for _, opt := range opts {
Expand Down Expand Up @@ -163,6 +171,48 @@ func WithDialFilter(on bool) TracingOption {
})
}

// WithExcludedCommands provides O(1) command exclusion for performance.
// Command names are normalized to uppercase for case-insensitive matching.
func WithExcludedCommands(commands ...string) TracingOption {
return tracingOption(func(conf *config) {
if len(commands) == 0 {
return
}
// Merge into existing map to accumulate exclusions across multiple calls
for _, cmd := range commands {
if cmd != "" {
conf.excludedCommands[strings.ToUpper(cmd)] = struct{}{}
}
}
})
}

// WithProcessFilter allows filtering of individual commands with custom logic.
// The filter function should return true to exclude the command from tracing.
func WithProcessFilter(filter func(cmd redis.Cmder) bool) TracingOption {
return tracingOption(func(conf *config) {
conf.unifiedProcessFilter = filter
conf.filterProcess = nil
})
}

// WithProcessPipelineFilter allows filtering of pipeline commands with custom logic.
// The filter function should return true to exclude the entire pipeline from tracing.
func WithProcessPipelineFilter(filter func(cmds []redis.Cmder) bool) TracingOption {
return tracingOption(func(conf *config) {
conf.unifiedProcessPipelineFilter = filter
conf.filterProcessPipeline = nil
})
}

// WithDialFilterFunc allows filtering of dial operations with custom logic.
// The filter function should return true to exclude the dial operation from tracing.
func WithDialFilterFunc(filter func(network, addr string) bool) TracingOption {
return tracingOption(func(conf *config) {
conf.unifiedDialFilter = filter
})
}

// DefaultCommandFilter filters out AUTH commands from tracing.
func DefaultCommandFilter(cmd redis.Cmder) bool {
if strings.ToLower(cmd.Name()) == "auth" {
Expand Down
Loading
Loading