denoland/deno · error · anyhow::Error

failed to set otel globals

Error message

failed to set otel globals

What it means

Deno stores its initialized telemetry state (processors, meter provider, sampler, limits) in a global OnceCell. Setting that cell fails — yielding 'failed to set otel globals' — if telemetry globals were already populated, i.e. the OTEL setup path executed a second time in the same process. This is an internal double-initialization guard, not an environment-configuration problem.

Solutions

  1. Ensure the telemetry/global-OTel setup runs exactly once per process (guard it behind your own OnceCell or lazy static)
  2. If you embed Deno, don't call the OTel init API yourself and also pass --otel; pick one owner
  3. Update to the latest Deno patch release — double-init in standard `deno` CLI flows is a bug worth reporting at https://github.com/denoland/deno/issues
  4. As a workaround, drop the custom OTel env/flags in the second init site so only the first initializes globals
Defensive patterns

Strategy: validation

Validate before calling

// Embedders: guard so OTel globals init at most once per process
use std::sync::Once;
static OTEL_INIT: Once = Once::new();
fn init_telemetry_once(sys: &impl TelemetrySys) -> anyhow::Result<()> {
    // First caller initializes; later callers are no-ops, avoiding the
    // OnceCell double-set that produces "failed to set otel globals".
    let mut first_err: Option<anyhow::Error> = None;
    OTEL_INIT.call_once(|| {
        if let Err(e) = deno_telemetry::init(sys) { first_err = Some(e); }
    });
    first_err.map_or(Ok(()), Err)
}

Try / catch

// In embedder bootstrap code
match deno_telemetry::init(&sys) {
    Err(e) if e.to_string().contains("failed to set otel globals") => {
        // Globals already installed by an earlier init: log and continue
        log::warn!("otel globals already set; skipping re-init");
    }
    other => other?,
}

Prevention

When it happens

Trigger: Embedding deno_core/deno_runtime and calling the telemetry init (configure_opentelemetry-style entry point) twice; a Deno version regression where a worker or snapshot path re-runs OTel setup; runtime customization code that installs OTel globals before Deno's own --otel startup path runs.

Common situations: Custom embedders (runtime builders on top of deno_core) that add their own OTel bootstrap; upgrading Deno versions where init ordering changed; tests that boot multiple JsRuntimes in one process with --otel semantics each time.

Related errors


AI-assisted analysis of denoland/deno@9ad36f7a2c (2026-08-20). Data as JSON: /api/errors/bd632989637d482b. Report an issue: GitHub.

Appendix: source

Thrown at ext/telemetry/lib.rs:1423

  let span_attribute_count_limit = span_attribute_count_limit_from_env(sys);
  let span_attribute_value_length_limit =
    span_attribute_value_length_limit_from_env(sys);
  let sampler = Sampler::from_env(sys)?;

  OTEL_GLOBALS
    .set(OtelGlobals {
      log_processor,
      span_processor,
      id_generator,
      meter_provider,
      builtin_instrumentation_scope,
      span_event_count_limit,
      span_attribute_count_limit,
      span_attribute_value_length_limit,
      sampler,
      config,
    })
    .map_err(|_| deno_core::anyhow::anyhow!("failed to set otel globals"))?;

  deno_signals::before_exit(before_exit);
  deno_net::tunnel::disable_before_exit();

  Ok(())
}

fn before_exit() {
  log::trace!("deno_telemetry::before_exit");

  let Some(OtelGlobals {
    span_processor: spans,
    log_processor: logs,
    meter_provider,
    ..
  }) = OTEL_GLOBALS.get()
  else {
    return;

View on GitHub (pinned to 9ad36f7a2c)