The id has to ride along — context propagation with the traceparent header
The caller writes the trace id and its own span id into an outbound header and the callee reads them back out, so any hop that builds a request without copying that header starts a brand-new trace — and both halves still look complete.
Scene 02 assumed each hop already knew the trace id. Nothing gives a service that id unless the caller hands it over on the way in.
Scene 03
The id has to ride along
- Watch
- Try it
- Predict
- Capture
How does the second service know which request it is part of? Nobody tells it — the first service has to write the ids into the request it sends, and the receiver reads them back out. Read the hop-by-hop rail on the left: the blue stretch of what went out is identical on every hop, and the violet tail changes at every one. Then watch what happens when checkout builds a fresh request to payments and copies nothing into it.
Highlighted lines are the ones running in the diagram right now.
def call_payments(order):span = tracer.start_span("POST /charge", CLIENT)ctx = set_span_in_context(span, current_context())req = build_request("POST", "/charge", order)if client_is_instrumented:propagator.inject(ctx, req.headers)send(req)span.end()
def inject(ctx, headers):s = ctx.current_spanif s is None:returnheaders["traceparent"] = "-".join(["00",s.trace_id, # minted once per requests.span_id, # the caller's own spans.flags,])if writes_b3: # composite propagatorwrite_b3_headers(headers, s)
def extract(headers):tp = headers.get("traceparent")if reads_w3c and tp and len(tp) >= 55:_, trace_id, parent_id, flags = tp.split("-")return remote_context(trace_id, parent_id, flags)if reads_b3 and "X-B3-TraceId" in headers:return b3_context(headers)return EMPTY_CONTEXTdef on_request(headers):parent = extract(headers)return tracer.start_span(name, parent) # empty -> root
def attach(ctx):token = execution_unit.currentexecution_unit.current = ctx # Context is immutablereturn token # detach(token) undoes itdef apply_loyalty_points(order, ctx):if hands_off_to_pool:pool.submit(lambda: apply(order))else:token = attach(ctx)apply(order)detach(token)
Where this sits in Build a distributed tracing system (Jaeger / Zipkin style)
Scene 03 of 17, in the Carry it act — The header, the queue that drops it, what rides along.. The caller writes the trace id and its own span id into an outbound header and the callee reads them back. Any hop that builds a request without copying it starts a brand-new trace — and both halves still look complete.
Up next. The id rides in a header on an outbound request. So what happens at a hop where there is no outbound request to put a header on?
All 17 scenes in Build a distributed tracing system (Jaeger / Zipkin style) · Every curriculum