go-redis/redis · error
redisotel: already initialized, call Shutdown() before reini
Error message
redisotel: already initialized, call Shutdown() before reinitializing
What it means
Returned by ObservabilityInstance.Init when the global OpenTelemetry observability singleton has already been initialized and Init is called again without an intervening Shutdown(). The singleton (guarded by observabilityInstanceOnce) attaches a metrics recorder to all Redis clients via redis.SetOTelRecorder, so a second Init would race with in-flight clients.
Source
Thrown at extra/redisotel-native/redisotel.go:77
// GetObservabilityInstance returns the global observability singleton.
func GetObservabilityInstance() *ObservabilityInstance {
observabilityInstanceOnce.Do(func() {
observabilityInstance = &ObservabilityInstance{}
})
return observabilityInstance
}
// Init initializes OpenTelemetry observability globally for all Redis clients.
// This should be called once at application startup, BEFORE creating any Redis clients.
// After initialization, all Redis clients will automatically collect and export
// metrics without needing any additional configuration.
func (o *ObservabilityInstance) Init(cfg *Config) error {
o.mu.Lock()
defer o.mu.Unlock()
// If already initialized, return error
if o.initialized {
return errors.New("redisotel: already initialized, call Shutdown() before reinitializing")
}
o.config = cfg
if !cfg.Enabled {
return nil
}
// Get meter provider (use global if not provided)
meterProvider := cfg.MeterProvider
if meterProvider == nil {
meterProvider = otel.GetMeterProvider()
}
meter := meterProvider.Meter(
"github.com/redis/go-redis",
metric.WithInstrumentationVersion(redis.Version()),
)View on GitHub (pinned to 36d97525cd)
Solutions
- Call Init exactly once at process startup; guard with IsEnabled() before calling.
- In tests or reload paths, call GetObservabilityInstance().Shutdown() before re-Init.
- If you need isolated instances, refactor to avoid the global singleton for tests.
Example fix
// before
redisotel.GetObservabilityInstance().Init(cfg1)
redisotel.GetObservabilityInstance().Init(cfg2) // errors
// after
inst := redisotel.GetObservabilityInstance()
if !inst.IsEnabled() {
inst.Init(cfg)
} Defensive patterns
Strategy: validation
Validate before calling
inst := redisotel.GetObservabilityInstance()
if !inst.IsEnabled() {
if err := inst.Init(cfg); err != nil {
return err
}
} Type guard
func mustInitObservability(cfg *redisotel.Config) error {
inst := redisotel.GetObservabilityInstance()
if inst.IsEnabled() {
return nil
}
return inst.Init(cfg)
} Try / catch
if err := inst.Init(cfg); err != nil {
if strings.Contains(err.Error(), "already initialized") {
return nil // acceptable: already set up
}
return err
} Prevention
- Call Init exactly once at startup, guarded by IsEnabled().
- In tests, Shutdown() in TestMain or t.Cleanup before re-init.
- Avoid libraries calling Init defensively on import.
When it happens
Trigger: Calling redisotel.GetObservabilityInstance().Init(cfg) twice in the same process — common in hot-reload (e.g. Air/plug, Neko dev-mode), tests that re-init per case without Shutdown, or shared-library code that initializes on import and again in main().
Common situations: Test suites reusing the global instance, serverless warm-starts that re-run setup, or multiple modules each calling Init defensively.
Related errors
- redisotel: %T not supported
- redis: got %d elements in slowlog get, expected at least 4
- redis: got %d elements commands reply in slowlog get, expect
- redis: got %d elements in latency get, expected at least 4
- failed to create metrics recorder: %w
AI-assisted analysis of go-redis/redis@36d97525cd (2026-08-06).
Data as JSON: /data/errors/eb7272f4c2d65877.json.
Report an issue: GitHub.