redis/go-redis · error

failed to create operation duration histogram: %w

Error message

failed to create operation duration histogram: %w

What it means

This error is returned from createRecorder in the redisotel-native package when the OpenTelemetry meter fails to create the db.client.operation.duration histogram instrument (via dbconv.NewClientOperationDuration). Init wraps it as 'failed to create metrics recorder' and aborts initialization, so no Redis client metrics are collected. Instrument creation essentially only fails when the MeterProvider is misconfigured, shut down, or a global error handler reports an invalid instrument (bad name/unit/boundaries).

Source

Thrown at extra/redisotel-native/redisotel.go:194

	}
}

// createRecorder creates a metricsRecorder with all instruments based on config.
func (o *ObservabilityInstance) createRecorder(meter metric.Meter, cfg config) (*metricsRecorder, error) {
	var err error

	var operationDuration metric.Float64Histogram
	if cfg.isMetricGroupEnabled(MetricGroupCommand) {
		var operationDurationOpts []metric.Float64HistogramOption
		if cfg.histAggregation == HistogramAggregationExplicitBucket {
			operationDurationOpts = append(operationDurationOpts,
				metric.WithExplicitBucketBoundaries(cfg.bucketsOperationDuration...),
			)
		}
		var operationDurationConv dbconv.ClientOperationDuration
		operationDurationConv, err = dbconv.NewClientOperationDuration(meter, operationDurationOpts...)
		if err != nil {
			return nil, fmt.Errorf("failed to create operation duration histogram: %w", err)
		}
		operationDuration = operationDurationConv.Inst()
	}

	var connectionCount metric.Int64UpDownCounter // OTel semconv: UpDownCounter
	var connectionCreateTime metric.Float64Histogram
	var connectionRelaxedTimeout metric.Int64UpDownCounter
	var connectionHandoff metric.Int64Counter

	if cfg.isMetricGroupEnabled(MetricGroupConnectionBasic) {
		connectionCount, err = meter.Int64UpDownCounter(
			dbconv.ClientConnectionCount{}.Name(),
			metric.WithDescription(dbconv.ClientConnectionCount{}.Description()),
			metric.WithUnit(dbconv.ClientConnectionCount{}.Unit()),
		)
		if err != nil {
			return nil, fmt.Errorf("failed to create connection count metric: %w", err)
		}

View on GitHub (pinned to c5cad058c7)

Solutions

  1. Install and set a real metric SDK MeterProvider (e.g. sdkmetric.NewMeterProvider with a Reader) in cfg.MeterProvider, or via otel.SetMeterProvider, before calling Init
  2. Ensure the MeterProvider has not been Shutdown before Init; re-create the provider if it was
  3. Check the wrapped error (%w) with errors.Unwrap / otel's global error handler logs to identify the specific instrument-creation failure
  4. Validate BucketsOperationDuration values (positive, increasing) if using HistogramAggregationExplicitBucket
  5. Capture the error from Init at startup instead of ignoring it, and fail fast or fall back to disabled metrics

Example fix

// before
err := obs.Init(&redisotel.Config{Enabled: true}) // uses no-op global provider
// after
provider := sdkmetric.NewMeterProvider(sdkmetric.WithReader(sdkmetric.NewPeriodicReader(exp exporter)))
err := obs.Init(&redisotel.Config{Enabled: true, MeterProvider: provider})
Defensive patterns

Strategy: validation

Validate before calling

if cfg.Enabled && cfg.MeterProvider == nil && otel.GetMeterProvider() == noop.NewMeterProvider() {
    // no SDK installed; Init will fail or no-op
}
// prefer: pass an explicit sdkmetric.MeterProvider created and not yet Shutdown

Type guard

func hasLiveMeterProvider(p metric.MeterProvider) bool { return p != nil }

Try / catch

if err := obs.Init(cfg); err != nil {
    var root error = errors.Unwrap(err)
    otel.Handle(err)
    log.Printf("redisotel disabled: %v (root: %v)", err, root)
}

Prevention

When it happens

Trigger: Calling ObservabilityInstance.Init with a Config that enables MetricGroupFlagCommand while the MeterProvider is nil-and-global-otel is a no-op provider, the provider was already Shutdown(), a custom MeterProvider returns errors for instrument creation, or HistogramAggregationExplicitBucket is set with malformed BucketsOperationDuration.

Common situations: Init is called after otel metric provider shutdown in tests; app forgets to install an SDK MeterProvider so the global no-op provider is used; swapping to a Prometheus or custom reader that rejects the instrument config; upgrading go.opentelemetry.io/otel and hitting semconv/dbconv incompatibilities.

Related errors


AI-assisted analysis of redis/go-redis@c5cad058c7 (2026-09-01). Data as JSON: /api/errors/d482d54b9a38099e. Report an issue: GitHub.