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
- Ensure the telemetry/global-OTel setup runs exactly once per process (guard it behind your own OnceCell or lazy static)
- If you embed Deno, don't call the OTel init API yourself and also pass --otel; pick one owner
- 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
- 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
- Never call the telemetry init API from two places (CLI --otel path and embedder bootstrap)
- Wrap global installs in Once/OnceCell at the call site, since the internal set is not idempotent
- For multi-runtime test harnesses, run each --otel runtime in its own process
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
- Env var OTEL_EXPORTER_OTLP_PROTOCOL specifies an…
- Env var OTEL_TRACES_SAMPLER specifies an unsupported sampler
- Failed to read env var OTEL_EXPORTER_OTLP_PROTOCOL
- Invalid value for…
- an internal error occurred
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)