Repository navigation
Conversation
bengl
marked this pull request as ready for review
November 2, 2026 15:58
AbhiPrasad
reviewed
Nov 2, 2026
|
|
||
| const { STATUS_ERROR, STATUS_UNSET } = require('./span'); | ||
|
|
||
| const kOtelApi = Symbol.for('opentelemetry.js.api.1'); |
Member
There was a problem hiding this comment.
question for the OTEL team, how likely is this to change?
There was a problem hiding this comment.
This is very unlikely to change in @opentelemetry/api@^1.0.0.
Future major versions may do something else, but there's nothing in the pipeline yet.
bengl
force-pushed
the
bengl/otel-sdk-context
branch
from
November 2, 2026 22:11
d1361da to
1787ab0
Compare
When the OpenTelemetry SDK runs in the same process, microtel now keeps the current span in the SDK's context store instead of its own, so spans from either side parent spans from the other. Server handlers attach a context holding a bridge span over microtel's span, and client handlers take their parent from whatever span the SDK's store holds. That also covers the SDK's HTTP instrumentation tracing inbound requests while microtel traces the outbound ones. A parent taken from the SDK's store is remote only when its span context says so, the way the SDK decides for its own spans. So a client span under an SDK span in the same process exports without the remote bit. The SDK is found through the global that the OpenTelemetry API registers its parts on, and its context keys are Symbol.for strings, so there is no dependency on @opentelemetry/api. Sharing needs a context manager with attach(), since the handlers run in diagnostics_channel subscribers that cannot wrap the code they trace in with(). Without one, microtel keeps its own context. The bridge span takes attributes, events, status, exceptions, and name changes from OTel code while the span is recording, ignores end(), and drops links. As in the SDK, a span whose trace isn't sampled is not recording. Status and exceptions follow the SDK's rules, so an ok status is final for microtel's own writes too. Events are capped at the SDK's default limit of 128, since OTel code can now add them. Requests the SDK suppresses tracing for, such as its own exports, are not traced. Each side keeps propagating trace context the way it normally does. microtel's span rides along in the SDK's context, so the baggage microtel received still reaches its client spans under an SDK span. The tests run the real SDK, pinned as devDependencies, in child processes. Signed-off-by: Bryan English <bryan@bryanenglish.com>
bengl
force-pushed
the
bengl/otel-sdk-context
branch
from
November 4, 2026 19:55
1787ab0 to
f6be673
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
microtel and the official OpenTelemetry SDK can now run in the same process and share one context, so a trace stays connected across spans from both.
How. When the SDK has registered a context manager with
attach(), microtel keeps the current span in the SDK's context store instead of its own.flowchart LR handler["A microtel handler needs<br/>the current span"] --> check{"Has the SDK registered<br/>a context manager<br/>with attach()?"} check -- "no" --> own["microtel's own<br/>AsyncLocalStorage,<br/>as before"] check -- "yes" --> sdk["The SDK's context store,<br/>shared by both"]When microtel traces the inbound request:
sequenceDiagram participant Caller participant M as microtel participant Store as SDK context store participant App as App and SDK participant Next as Next service Caller->>M: request with a traceparent M->>M: create server span S M->>Store: attach() a context holding a bridge span for S App->>Store: startActiveSpan('work') finds S App->>Store: with() a context holding W, a child of S App->>M: http.get() while W is current M->>Store: read the current span, finds W M->>M: create client span C, a child of W M->>Next: request with C's traceparentWhen the SDK's HTTP instrumentation traces it instead, microtel only reads the store:
sequenceDiagram participant Caller participant SDK as SDK HTTP instrumentation participant Store as SDK context store participant App participant M as microtel participant Next as Next service Caller->>SDK: request with a traceparent SDK->>Store: with() a context holding server span S App->>Store: startActiveSpan('work') makes W, a child of S App->>M: http.get() while W is current M->>Store: read the current span, finds W M->>M: create client span C, a child of W M->>Next: request with C's traceparentThe SDK is found through the global the OpenTelemetry API registers itself on,
globalThis[Symbol.for('opentelemetry.js.api.1')], and its context keys areSymbol.forstrings, so microtel takes no dependency on@opentelemetry/api.attach()is needed because the handlers run in diagnostics_channel subscribers, before the code they trace, so they cannot wrap it inwith().@opentelemetry/context-async-hookshas supported it since 2.11.0. Without it, microtel keeps its own context, as before.The bridge span. While microtel traces a request,
trace.getActiveSpan()returns microtel's server span. Attributes, events, status, exceptions, and name changes made through it land on that span until the span ends. Status and exceptions follow the SDK's rules, so an ok status is final, for microtel's own writes too.end()is ignored, since microtel ends its own spans, and links are dropped. OTel code can now add events, so a span keeps at most 128 of them, the SDK's default limit.flowchart LR code["OTel code calls<br/>trace.getActiveSpan()"] --> bridge["bridge span"] bridge -- "setAttribute, addEvent,<br/>setStatus, recordException,<br/>updateName" --> span["microtel's span,<br/>until it ends"] bridge -. "end() and links" .-> dropped["ignored"]Sampling. microtel's spans under an SDK span take its sampling decision and tracestate. The SDK's spans under microtel's take microtel's through its default parent-based sampler, including the
ot=ththreshold that asampleRatiobelow 1 adds. As in the SDK, a span whose trace isn't sampled records nothing written through the bridge, and itsisRecording()returns false. A parent from the SDK's store is remote only when its span context says so, the way the SDK decides for its own spans, so a client span under an SDK span in the same process exports without the remote bit.What stays the same. Each side propagates trace context the way it normally does. microtel forwards the baggage of requests it received itself, even when an SDK span is current, and baggage the SDK extracted is not added to the requests microtel traces. Requests the SDK suppresses tracing for, such as its own exports, are not traced. Routes reported by the SDK's framework instrumentations are not picked up yet, and a TODO in
lib/context.jssays how they could be.Tests. The new tests run the real SDK, pinned as devDependencies, in child processes: with microtel tracing inbound requests, with the SDK tracing them, and with a context manager that has no
attach(). Breaking any of the behavior above fails them, which a mutation check confirmed for each piece.