microsoft/aspire · error

runtime metrics

Error message

runtime metrics: %w

What it means

setupOTel wraps the error from runtime.Start, which registers the Go runtime metrics (GC, memory, goroutines) with the given MeterProvider. Start fails only when called twice on the same provider or in an unsupported state; the wrap is "runtime metrics: %w".

Solutions

  1. Call setupOTel (and thus runtime.Start) exactly once per process.
  2. Use sync.Once around setupOTel to guarantee single initialization.
  3. If multiple providers are needed, register runtime metrics only on the primary one.
  4. Log the wrapped error; the message indicates double-start or internal registration failure.

Example fix

// before
if err := runtime.Start(runtime.WithMeterProvider(mp)); err != nil {
    return nil, fmt.Errorf("runtime metrics: %w", err)
}
// after
var setupOnce sync.Once
func setupOTelOnce() error {
    var err error
    setupOnce.Do(func() { err = setupOTel() })
    return err
}
Defensive patterns

Strategy: try-catch

Validate before calling

var otelOnce sync.Once
var otelErr error
func ensureOTelStarted() error {
    otelOnce.Do(func() { otelErr = setupOTel() })
    return otelErr
}

Try / catch

if err := ensureOTelStarted(); err != nil {
    log.Printf("runtime metrics not started: %v", err)
}

Prevention

When it happens

Trigger: Calling runtime.Start(runtime.WithMeterProvider(mp)) when runtime instrumentation was already started (double setupOTel invocation), since the runtime package only supports a single registration.

Common situations: Calling setupOTel twice (e.g. in init and main), or multiple importers each starting runtime metrics on their own provider.

Understand the failure class

Background: "Invalid state transition" errors: "status must be X, actually Y", "already rejected/charging/uninstalled", "cannot ... while running" — what they mean when a library rejects your call — this error's family across 31 libraries.

Related errors


AI-assisted analysis of microsoft/aspire@25830f84bd (2026-09-16). Data as JSON: /api/errors/6d3900e02797cc55. Report an issue: GitHub.

Appendix: source

Thrown at src/Aspire.Cli/Templating/Templates/go-starter/api/telemetry.go:79

	)
	otel.SetTracerProvider(tp)

	metricOpts := []otlpmetricgrpc.Option{}
	if len(headers) > 0 {
		metricOpts = append(metricOpts, otlpmetricgrpc.WithHeaders(headers))
	}
	metricExp, err := otlpmetricgrpc.New(ctx, metricOpts...)
	if err != nil {
		return nil, fmt.Errorf("metric exporter: %w", err)
	}
	mp := sdkmetric.NewMeterProvider(
		sdkmetric.WithReader(sdkmetric.NewPeriodicReader(metricExp)),
		sdkmetric.WithResource(res),
	)
	otel.SetMeterProvider(mp)

	if err := runtime.Start(runtime.WithMeterProvider(mp)); err != nil {
		return nil, fmt.Errorf("runtime metrics: %w", err)
	}

	logOpts := []otlploggrpc.Option{}
	if len(headers) > 0 {
		logOpts = append(logOpts, otlploggrpc.WithHeaders(headers))
	}
	logExp, err := otlploggrpc.New(ctx, logOpts...)
	if err != nil {
		return nil, fmt.Errorf("log exporter: %w", err)
	}
	lp := sdklog.NewLoggerProvider(
		sdklog.WithProcessor(sdklog.NewBatchProcessor(logExp)),
		sdklog.WithResource(res),
	)
	otellog.SetLoggerProvider(lp)

	// Route slog to OTel so structured logs appear in the dashboard.
	slog.SetDefault(slog.New(slog.NewJSONHandler(os.Stdout, nil)))

View on GitHub (pinned to 25830f84bd)