Files
ofekshenawaandGitHub cd3949d1a0 fix: connection closure metrics and enable all metric groups by default (#3735)
* fix: connection closure metrics and enable all metric groups by default

This commit addresses three issues in the redisotel-native metrics implementation:

1. Fix connection closure metrics not appearing in dashboards
   - Root cause: CloseConn() in internal/pool/pool.go was not calling
     the metricConnectionClosedCallback when closing stale connections
   - Added callback invocation in CloseConn() to record 'stale' closures

2. Verify tracing and metrics compatibility
   - Added TestTracingAndMetricsCompatibility to document that
     redisotel (tracing via AddHook) and redisotel-native (metrics via
     SetOTelRecorder) can coexist without conflicts

3. Enable all metric groups by default
   - Changed default MetricGroups from connection-basic+resiliency
     to MetricGroupAll for comprehensive observability out of the box
2026-03-16 09:54:24 +02:00

268 lines
8.1 KiB
Go

package redisotel
import (
"strings"
"go.opentelemetry.io/otel/metric"
)
type MetricGroup string
const (
MetricGroupCommand MetricGroup = "command"
MetricGroupConnectionBasic MetricGroup = "connection-basic"
MetricGroupResiliency MetricGroup = "resiliency"
MetricGroupConnectionAdvanced MetricGroup = "connection-advanced"
MetricGroupPubSub MetricGroup = "pubsub"
MetricGroupStream MetricGroup = "stream"
)
type HistogramAggregation string
const (
HistogramAggregationExplicitBucket HistogramAggregation = "explicit_bucket_histogram"
HistogramAggregationBase2Exponential HistogramAggregation = "base2_exponential_bucket_histogram"
)
type config struct {
// Core settings
meterProvider metric.MeterProvider
enabled bool
// Metric group settings
enabledMetricGroups map[MetricGroup]bool
// Command filtering
includeCommands map[string]bool // nil means include all
excludeCommands map[string]bool // nil means exclude none
// Cardinality reduction
hidePubSubChannelNames bool
hideStreamNames bool
// Histogram settings
histAggregation HistogramAggregation
// Bucket configurations for different histogram metrics
bucketsOperationDuration []float64
bucketsStreamProcessingDuration []float64
bucketsConnectionCreateTime []float64
bucketsConnectionWaitTime []float64
}
func (c *config) isMetricGroupEnabled(group MetricGroup) bool {
return c.enabledMetricGroups[group]
}
func (c *config) isCommandIncluded(command string) bool {
command = strings.ToLower(command)
if c.excludeCommands != nil && c.excludeCommands[command] {
return false
}
if c.includeCommands != nil {
return c.includeCommands[command]
}
return true
}
// defaultHistogramBuckets returns the default histogram buckets for all duration metrics.
// These buckets are designed to capture typical Redis operation and connection latencies:
// - Sub-millisecond: 0.0001s (0.1ms), 0.0005s (0.5ms)
// - Milliseconds: 0.001s (1ms), 0.005s (5ms), 0.01s (10ms), 0.05s (50ms), 0.1s (100ms)
// - Sub-second: 0.5s (500ms)
// - Seconds: 1s, 5s, 10s
//
// This covers the range from 0.1ms to 10s, which is suitable for:
// - db.client.operation.duration (command execution time)
// - db.client.connection.create_time (connection establishment)
// - db.client.connection.wait_time (waiting for connection from pool)
// - redis.client.stream.processing_duration (stream message processing)
func defaultHistogramBuckets() []float64 {
return []float64{
0.0001, // 0.1ms
0.0005, // 0.5ms
0.001, // 1ms
0.005, // 5ms
0.01, // 10ms
0.05, // 50ms
0.1, // 100ms
0.5, // 500ms
1.0, // 1s
5.0, // 5s
10.0, // 10s
}
}
// MetricGroupFlags represents metric groups as bitwise flags
type MetricGroupFlags uint32
const (
MetricGroupFlagCommand MetricGroupFlags = 1 << 0
MetricGroupFlagConnectionBasic MetricGroupFlags = 1 << 1
MetricGroupFlagResiliency MetricGroupFlags = 1 << 2
MetricGroupFlagConnectionAdvanced MetricGroupFlags = 1 << 3
MetricGroupFlagPubSub MetricGroupFlags = 1 << 4
MetricGroupFlagStream MetricGroupFlags = 1 << 5
// MetricGroupAll enables all metric groups
MetricGroupAll MetricGroupFlags = MetricGroupFlagCommand |
MetricGroupFlagConnectionBasic |
MetricGroupFlagResiliency |
MetricGroupFlagConnectionAdvanced |
MetricGroupFlagPubSub |
MetricGroupFlagStream
)
// Use NewConfig() to create a new instance with defaults, then chain
// builder methods to customize.
//
// Example:
//
// config := redisotel.NewConfig().
// WithEnabled(true).
// WithMetricGroups(redisotel.MetricGroupAll).
// WithMeterProvider(myProvider)
//
// otel := redisotel.GetObservabilityInstance()
// otel.Init(config)
type Config struct {
// Core settings
Enabled bool
MeterProvider metric.MeterProvider
// Metric groups (bitwise flags)
MetricGroups MetricGroupFlags
// Command filtering
IncludeCommands map[string]bool // nil means include all
ExcludeCommands map[string]bool // nil means exclude none
// Cardinality reduction
HidePubSubChannelNames bool
HideStreamNames bool
// Histogram settings
HistogramAggregation HistogramAggregation
// Bucket configurations for different histogram metrics
BucketsOperationDuration []float64
BucketsStreamLag []float64
BucketsConnectionCreateTime []float64
BucketsConnectionWaitTime []float64
}
// NewConfig creates a new Config with default values.
// Default configuration:
// - Enabled: false (must explicitly enable)
// - MetricGroups: all metric groups (command, connection, resiliency, pubsub, stream)
// - HistogramAggregation: explicit bucket
// - Buckets: 0.1ms to 10s (suitable for Redis operations)
//
// Example:
//
// config := redisotel.NewConfig().
// WithEnabled(true)
//
// To disable specific metric groups, use WithMetricGroups:
//
// config := redisotel.NewConfig().
// WithEnabled(true).
// WithMetricGroups(redisotel.MetricGroupFlagConnectionBasic | redisotel.MetricGroupFlagResiliency)
func NewConfig() *Config {
return &Config{
Enabled: false,
MeterProvider: nil, // Will use global otel.GetMeterProvider() if nil
// Default metric groups: all groups enabled for comprehensive observability
MetricGroups: MetricGroupAll,
// No command filtering by default
IncludeCommands: nil,
ExcludeCommands: nil,
// Don't hide labels by default
HidePubSubChannelNames: false,
HideStreamNames: false,
// Use explicit bucket histogram by default
HistogramAggregation: HistogramAggregationExplicitBucket,
// Default buckets for all duration metrics
BucketsOperationDuration: defaultHistogramBuckets(),
BucketsStreamLag: defaultHistogramBuckets(),
BucketsConnectionCreateTime: defaultHistogramBuckets(),
BucketsConnectionWaitTime: defaultHistogramBuckets(),
}
}
// WithEnabled enables or disables metrics emission.
// Default: false (must explicitly enable)
func (c *Config) WithEnabled(enabled bool) *Config {
c.Enabled = enabled
return c
}
// WithMeterProvider sets the meter provider to use for creating metrics.
// If not provided, the global meter provider from otel.GetMeterProvider() will be used.
func (c *Config) WithMeterProvider(provider metric.MeterProvider) *Config {
c.MeterProvider = provider
return c
}
// WithMetricGroups sets which metric groups to register using bitwise flags.
// You can combine multiple groups with the | operator.
func (c *Config) WithMetricGroups(groups MetricGroupFlags) *Config {
c.MetricGroups = groups
return c
}
// WithIncludeCommands sets a command allow-list for metrics.
func (c *Config) WithIncludeCommands(commands []string) *Config {
c.IncludeCommands = make(map[string]bool)
for _, cmd := range commands {
c.IncludeCommands[strings.ToLower(cmd)] = true
}
return c
}
// WithExcludeCommands sets a command deny-list for metrics.
// Commands in this list will not have metrics recorded.
func (c *Config) WithExcludeCommands(commands []string) *Config {
c.ExcludeCommands = make(map[string]bool)
for _, cmd := range commands {
c.ExcludeCommands[strings.ToLower(cmd)] = true
}
return c
}
// WithHidePubSubChannelNames omits channel label from Pub/Sub metrics to reduce cardinality.
func (c *Config) WithHidePubSubChannelNames(hide bool) *Config {
c.HidePubSubChannelNames = hide
return c
}
// WithHideStreamNames omits stream label from stream metrics to reduce cardinality.
func (c *Config) WithHideStreamNames(hide bool) *Config {
c.HideStreamNames = hide
return c
}
// WithHistogramAggregation sets the histogram aggregation mode.
func (c *Config) WithHistogramAggregation(agg HistogramAggregation) *Config {
c.HistogramAggregation = agg
return c
}
// WithHistogramBuckets sets custom histogram buckets for ALL duration metrics.
// If not set, uses defaultHistogramBuckets() which covers 0.1ms to 10s.
// Buckets should be in seconds (e.g., 0.001 = 1ms, 0.1 = 100ms, 1.0 = 1s).
func (c *Config) WithHistogramBuckets(buckets []float64) *Config {
c.BucketsOperationDuration = buckets
c.BucketsStreamLag = buckets
c.BucketsConnectionCreateTime = buckets
c.BucketsConnectionWaitTime = buckets
return c
}