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.

Previously

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

  1. Watch
  2. Try it
  3. Predict
  4. Capture
the header rides along — until one hop drops itTIME TO CULPRIT4 minexampletraceparent 4bf9…-00f0traceparent 4bf9…-a3cetraceparent 4bf9…-d755gatewaycheckoutpaymentsledgerlegacytraceparentversion00trace id — 32 hex digits, minted once per request4bf92f3577b34da6a3ce929d0e0e4736same on every hopparent span id — the caller's own span00f067aa0ba902b7new on every hopflags01one byte of recommendations — scene 07 explains itON THE WIRE, HOP BY HOPsame every hopnew every hopgateway → checkouttraceparent 4bf9…-00f0checkout → paymentstraceparent 4bf9…-a3cepayments → ledgertraceparent 4bf9…-d755trace 4bf92f3577b34da6a3ce929d0e0e47363.0 s, all of it named0750ms1.5s2.3s3.0sPOST /checkout3.0sPOST /checkout2.9sPOST /charge2.6sPOST /ledger/entries2.3slegacyWIRE FORMATw3cone header, spoken by all fourgateway, checkout, payments and ledger all read and write the sameone-line header.IN-PROCESS HANDOFFoffrequest threadthread poolevery span here runs on the thread that already holds the contextOne id on every hop, one tree at the end: the 3.0 s is split across services that each own a named piece of it.
What to watch for

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.

Continue unlocks when the animation finishes.
Implementation

Highlighted lines are the ones running in the diagram right now.

Checkout.call_payments
the outbound request the ids have to ride on
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()
Propagator.inject
the caller writes the ids onto the wire
def inject(ctx, headers):
s = ctx.current_span
if s is None:
return
headers["traceparent"] = "-".join([
"00",
s.trace_id, # minted once per request
s.span_id, # the caller's own span
s.flags,
])
if writes_b3: # composite propagator
write_b3_headers(headers, s)
Propagator.extract
the receiver reads them back, or does not
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_CONTEXT
def on_request(headers):
parent = extract(headers)
return tracer.start_span(name, parent) # empty -> root
Context.attach
the ids inside one process, with no wire involved
def attach(ctx):
token = execution_unit.current
execution_unit.current = ctx # Context is immutable
return token # detach(token) undoes it
def 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

Built with Arqly
Every scene in Build a distributed tracing system (Jaeger / Zipkin style) builds on the one before it.All 17 Build a distributed tracing system (Jaeger / Zipkin style) scenes