Bring tracing service more in line with upstream APIs

Signed-off-by: Eric Peterson <ericpeterson@spotify.com>
This commit is contained in:
Eric Peterson
2026-05-18 15:20:09 +02:00
parent d6c7805527
commit b70f13990b
15 changed files with 567 additions and 221 deletions
+20 -5
View File
@@ -102,30 +102,45 @@ The span object exposes:
## Context Propagation
When your plugin receives requests through a protocol layer that breaks automatic OpenTelemetry context propagation (e.g. a WebSocket handler), use `withPropagatedContext` to extract the trace parent and baggage from the incoming HTTP headers and run the handler within that context:
The tracing service exposes two sub-objects that mirror the corresponding namespaces in `@opentelemetry/api`:
- `tracing.context` for context management (`active`, `with`).
- `tracing.propagation` for context propagation (`extract`, `getBaggage`, `getActiveBaggage`).
When your plugin handles a request through a transport or framework that doesn't automatically attach the caller's context to the work it runs (for example, a handler dispatched from a message-queue consumer, or a third-party transport like the MCP streamable HTTP transport that re-enters user code outside of Express's middleware chain), extract the trace parent and baggage from the inbound request's headers yourself and run the handler with that context active:
```ts
router.post('/', async (req, res) => {
await tracing.withPropagatedContext(req.headers, () =>
const ctx = tracing.propagation.extract(
tracing.context.active(),
req.headers,
);
await tracing.context.with(ctx, () =>
transport.handleRequest(req, res, req.body),
);
});
```
`propagation.extract` reads from a header-shaped record (`Record<string, string | string[] | undefined>`), so any source of headers — Express's `req.headers`, a Node.js `http.IncomingMessage`, or a payload field carrying serialized headers — works the same way.
Any spans created inside the callback — including those from `startActiveSpan` — will be children of the propagated trace and will have access to the propagated baggage.
The context returned by `propagation.extract` and `context.active` is an opaque handle: consumers pass it back into the API but do not introspect it.
## Reading Baggage
Use `getActiveBaggage()` to read baggage entries from the active context. This is useful for forwarding caller-set metadata onto your spans — for example, a request ID, tenant identifier, or feature-flag context that the caller propagated via baggage:
Use `propagation.getActiveBaggage()` to read baggage entries from the currently active context. This is useful for forwarding caller-set metadata onto your spans — for example, a request ID, tenant identifier, or feature-flag context that the caller propagated via baggage:
```ts
const baggage = tracing.getActiveBaggage();
const baggage = tracing.propagation.getActiveBaggage();
const tenantId = baggage?.getEntry('app.tenant.id')?.value;
if (tenantId) {
span.setAttribute('app.tenant.id', tenantId);
}
```
Use `propagation.getBaggage(ctx)` when you already hold a specific context handle (for example, one returned by `propagation.extract`) and want to read its baggage without first activating the context.
The returned object exposes:
| Method | Description |
@@ -133,7 +148,7 @@ The returned object exposes:
| `getEntry(key)` | Returns `{ value: string }` for the key, or `undefined`. |
| `getAllEntries()` | Returns all entries as `[key, { value }][]`. |
Returns `undefined` when no baggage is present in the active context.
Both calls return `undefined` when no baggage is present.
## Principal Enrichment