Full lesson
Explore the full explanation, examples, and visuals at your own pace.
Part 1
General knowledgeTrace context travels inside the headers of the GET /product request. A user posts to /cart/add on Frontend. Frontend then calls Product Catalog, and that call carries a traceparent header. Product Catalog replies, and Frontend answers the user. A span, by the way, is one timed operation, like each of these calls.
Two separate ideas
Context propagation is really two ideas. Context is the data object: a trace ID and a span ID that let one signal be correlated with another. Propagation is the mechanism that serializes and deserializes that object, moving it between services and processes.
- Context: trace ID plus span ID
- Propagation: moves context across boundaries
Same trace, new child span
When Frontend calls Product Catalog, Catalog doesn't start a new trace. It keeps the same trace ID, so both services share it. Then it creates its own span and sets Frontend's span as its parent. That parent link is what ties the two services into one request.
W3C traceparent header
OpenTelemetry's default propagator uses the W3C TraceContext format, which carries context in a header called traceparent. The template shows four fields joined by dashes. Below it is the real value Frontend sends to Product Catalog on its GET /product call. Next, we'll read it field by field.
<version>-<trace-id>-<parent-id>-<trace-flags>
traceparent: 00-a0892f3577b34da6a3ce929d0e0e4736-f03067aa0ba902b7-01Reading the example header
General knowledgeReading left to right, the header has four fields. Version is 00. The trace ID, starting a0892f, stays the same for the whole request. The parent ID, starting f03067aa, is Frontend's span, so Product Catalog knows who called it. Flags is 01, which usually means the trace is sampled.
Inject on send, extract on receive
Two roles make this work. A carrier is simply the place where metadata travels with a request, and here it's the HTTP headers. Frontend injects its context into those headers, so traceparent rides along. Product Catalog extracts that context from the headers, then starts a child span that continues the same trace.
Manual propagation (JavaScript sketch)
General knowledgeThis sketch shows both sides. On the Frontend, inject writes the context into a headers object, and that's where traceparent ends up, before fetch sends it. On Product Catalog, extract reads those headers back, and context.with runs the handler so new spans become children. It's illustrative, using the JavaScript API.
// Frontend: sender
const headers = {};
propagation.inject(context.active(), headers);
fetch(catalogUrl + '/product', { headers });
// Product Catalog: receiver
const ctx = propagation.extract(context.active(), req.headers);
context.with(ctx, () => handleProduct(req));Usually automatic: instrumentation libraries propagate for you
Most of the time, instrumentation libraries handle this for you. You only write it by hand without library support, and then extract and remove context before processing.
Part 9
The same propagated context correlates traces, logs and metrics, not just traces. One small header does all of that work. Two IDs ride along with each request, the trace ID and the caller's span ID, so spans join one trace, and logs and metrics can share that link.
Logs stamped with trace and span IDs
Logs get the same treatment. While a span is active, the OpenTelemetry SDK can automatically inject its trace ID and span ID into each log record. Then a trace view can group those logs by trace, so you follow related logs across Frontend and Product Catalog.
GET /product, split by caller
Because context travels with each call, metrics can be split by caller. Across all callers, GET /product averages 300 milliseconds. But calls from cart/add, about 330 per second, average just 130. Calls from checkout, about 40 per second, average 1703. The overall average was hiding that slow path.
Chart values
| Call path | ms |
|---|---|
| All callers | 300 |
| cart/add > product | 130 |
| checkout > product | 1703 |
Security at the boundary
Crossing a boundary has security costs. Forged incoming trace headers can manipulate your data, so sanitize them. Outgoing IDs can reveal your architecture. And baggage, which is arbitrary key-value pairs traveling with requests, may get logged or forwarded, so keep secrets out.
- Incoming: ignore or sanitize untrusted headers
- Outgoing: don't leak internal IDs externally
- Baggage: no credentials, API keys, PII
Two IDs ride in each request header so spans join one trace.
General knowledgeTwo IDs ride in each request header so spans join one trace. When a trace breaks between services, check that traceparent is injected and extracted.
