Skip to content

CONCEPT

Async context propagation

Definition

Async context propagation is the mechanism by which a distributed-tracing library associates a span (or other per-request context) with the asynchronous continuation of the code that started it โ€” so that spans created inside a setTimeout, fetch, Promise.then, or similar callback automatically become children of the current span without the programmer having to pass the parent span explicitly.

How it works per runtime

Node.js: AsyncLocalStorage

Node's AsyncLocalStorage API (built on async_hooks) provides runtime-native async- context propagation. OpenTelemetry JS's Node SDK uses it by default. Calls like:

await tracer.startActiveSpan("name", async () => {
  await callOtherFunction();  // callOtherFunction's spans
                              // auto-parent to "name"
});

work without passing the span explicitly. The async-local store is keyed on the Node.js async-ID tree.

Browser: no native primitive

The TC39 AsyncContext proposal is still in progress. Until it lands, the only equivalent is Zone.js, which monkey-patches global functions (setTimeout, Promise, addEventListener, etc.) to instrument async scheduling manually.

The cost of automatic propagation

  • On the server: auto-context is clean; OTel's startActiveSpan API is idiomatic. But migrating from OpenTracing-style manual-span-passing is expensive โ€” "especially in a large codebase like ours" (Source: ).
  • On the browser: Zone.js monkey-patches globals in the customer's browser. Zalando "are not big fans of this" and opted out entirely (concepts/zone-js-monkey-patching).

Zalando's resolution

Opt out of context propagation on both client and server and pass span objects manually through function parameters, using OTel's third startup form (tracer.startSpan("name", {}, context)). This is the patterns/manual-span-passing-over-async-context pattern.

Trade-offs:

  • Loss: spans must be threaded explicitly; libraries that want to create child spans must accept a context / parent- span parameter, making the API uglier.
  • Gain (server): seamless migration from OpenTracing; no big-bang rewrite of the large existing instrumentation codebase.
  • Gain (browser): no runtime monkey-patching of customer- browser globals; lower bundle size; no Zone.js surprise interactions with third-party JS.

Seen in

  • โ€” the canonical "why not" for OTel context propagation in both runtimes.
Last updated ยท 542 distilled / 1,571 read