caddyserver/caddy · error

creating OTLP metric reader: %w

Error message

creating OTLP metric reader: %w

What it means

When metrics are configured for export (metrics { ... } with OTLP/autoexport), AdminMetrics/InstrumentationMetrics provisioning calls autoexport.NewMetricReader(ctx), which reads OTEL_METRICS_EXPORTER and related OTEL_EXPORTER_OTLP_* environment variables. Failures — unknown exporter name, no exporter available for the requested protocol — are wrapped as 'creating OTLP metric reader'. Note the code first installs a fallback producer reading Caddy's own Prometheus registry; OTEL_METRICS_PRODUCERS can override it.

Source

Thrown at modules/caddyhttp/metrics.go:193

	}

	// Register a Prometheus -> OpenTelemetry bridge against the process-wide
	// Prometheus registry as the *default* source the NewMetricReader below
	// will read from.
	//
	// NB: despite the "With*" naming, autoexport.WithFallbackMetricProducer is
	// a package-level setter (it returns nothing) — it mutates autoexport's
	// internal producer registry and takes effect on the very next call to
	// NewMetricReader. It is NOT a MetricOption and must not be passed as one.
	// Users can still override the source by setting OTEL_METRICS_PRODUCERS.
	reg := ctx.GetMetricsRegistry()
	autoexport.WithFallbackMetricProducer(func(context.Context) (sdkmetric.Producer, error) {
		return otelprom.NewMetricProducer(otelprom.WithGatherer(reg)), nil
	})

	reader, err := autoexport.NewMetricReader(ctx)
	if err != nil {
		return fmt.Errorf("creating OTLP metric reader: %w", err)
	}

	version, _ := caddy.Version()
	res, err := resource.Merge(resource.Default(), resource.NewSchemaless(
		semconv.WebEngineName(ServerHeader),
		semconv.WebEngineVersion(version),
	))
	if err != nil {
		return fmt.Errorf("building OTLP metrics resource: %w", err)
	}

	m.meterProvider = sdkmetric.NewMeterProvider(
		sdkmetric.WithResource(res),
		sdkmetric.WithReader(reader),
	)

	return nil
}

View on GitHub (pinned to 50e54ee279)

Solutions

  1. Check OTEL_METRICS_EXPORTER — use a supported value (e.g. otlp, prometheus, none per your OTel SDK version) or unset it.
  2. Verify OTEL_EXPORTER_OTLP_PROTOCOL / OTEL_EXPORTER_OTLP_METRICS_PROTOCOL match a protocol the build supports (grpc vs http/protobuf).
  3. Unset conflicting OTEL_* variables in the service unit/entrypoint (env -u OTEL_METRICS_EXPORTER ...) if OTLP export was never intended.
  4. Confirm the endpoint env (OTEL_EXPORTER_OTLP_ENDPOINT / _METRICS_ENDPOINT) is reachable and well-formed.

Example fix

# before
export OTEL_METRICS_EXPORTER=otlpgrp

# after
export OTEL_METRICS_EXPORTER=otlp
export OTEL_EXPORTER_OTLP_PROTOCOL=grpc
Defensive patterns

Strategy: validation

Validate before calling

# Shell: verify exporter env before starting Caddy
export OTEL_METRICS_EXPORTER=otlp   # or prometheus / none, per your OTel build
export OTEL_EXPORTER_OTLP_PROTOCOL=grpc
env | grep '^OTEL_' # audit every variable for typos

Prevention

When it happens

Trigger: OTEL_METRICS_EXPORTER set to an unsupported value (e.g. 'otlp' when the build lacks it, or a typo like 'otlpgrp'); OTEL_EXPORTER_OTLP_PROTOCOL/OTEL_EXPORTER_OTLP_METRICS_PROTOCOL set to an unregistered protocol (e.g. 'http/protobuf' in a build without it); env vars set globally leaking into the Caddy process.

Common situations: Kubernetes pods inheriring OpenTelemetry operator sidecar env vars; CI shells with stray OTEL_* exports; upgrading OTel libraries that renamed valid exporter values; enabling `metrics` blocks on servers where ops intended Prometheus scrape only.

Related errors


AI-assisted analysis of caddyserver/caddy@50e54ee279 (2026-08-15). Data as JSON: /api/errors/eee99ecb71c68a41. Report an issue: GitHub.