Distributed Tracing
Traces follow one request across every service it touches. Understand traces and spans, context propagation with W3C trace context, sampling strategies, OpenTelemetry, and how traces tie logs and metrics together.
What is this?
Distributed tracing records the journey of a single request as it fans out across services. A trace is a tree of spans; each span is one unit of work with a name, start and end time, and attributes. Context propagation passes the trace ID and parent span ID in headers (W3C trace context's traceparent) so downstream services can join their work to the same tree. Sampling decides which traces get recorded, and OpenTelemetry standardizes instrumentation and export.
Why do you need it?
- In a microservice architecture, a slow response has many possible causes across many services.
- Traces reveal the actual call graph and per-span latency, replacing guesses about where time went.
- Context propagation links logs, metrics and traces for the same request through a shared trace ID.
- Sampling keeps the cost and storage of tracing predictable at scale.
- OpenTelemetry gives one standard for instrumentation, so one SDK covers HTTP, gRPC, databases and queues.
Mental model
Breadcrumbs on a hiking trail
Logs versus traces
Dropping breadcrumbs at each step is logging — you know where you were, but not the route you took or how long each leg lasted. A GPS track log is a trace: one continuous record of the whole journey with timing on every segment. Traces show the map; logs are the notes scribbled at each landmark.
How it works
- The first service to receive a request generates a 128-bit trace ID and starts a root span for the entry operation.
- The tracing SDK injects context into outbound calls using the traceparent header, which carries trace ID, parent span ID and a sampled flag.
- Downstream services extract the header, create child spans with the same trace ID, and propagate the context to their own dependencies.
- Each span records its name, start and end timestamps, attributes, status and events; an exporter batches them to a collector or backend.
- A tracing backend such as Jaeger or Grafana Tempo reconstructs spans sharing a trace ID into the request's tree for visualization.
A request tree built from spans joined by one trace ID.
Key commands
curl -H 'traceparent: 00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01' http://localhost:8080/checkoutInject a trace header manually to group a request with a known trace.
curl -s 'http://localhost:16686/api/traces?service=checkout&limit=10'List recent traces for a service through the Jaeger query API.
docker logs --since 5m checkout | grep 4bf92f3577b34da6Pull log lines that share a trace ID from a container.
kubectl get pods -l app=checkoutLocate the pods that carry the tracing instrumentation.
curl -s 'http://localhost:9090/api/v1/query?query=span_processor_batch_size'Inspect OTel exporter batch metrics to confirm traces are leaving the app.
Examples
Inspect trace context on a request
$ curl -v https://api.example.com/checkout 2>&1 | grep -i traceparent< traceparent: 00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01 # The header format: version - trace-id - span-id - flags# 00 4bf92f3577b34da6a3ce929d0e0e4736 00f067aa0ba902b7 01# version trace (128-bit hex) span (64-bit hex) sampled # Find every log line belonging to that tracegrep 4bf92f3577b34da6a3ce929d0e0e4736 /var/log/app/*.logtraceparent is the W3C standard header that carries trace context between services. The sampled flag of 01 means this trace was selected for recording. Because every service logs the same trace ID, grepping for it reassembles the whole request story even where traces were dropped by sampling.
In production
- The platform SDK auto-instruments HTTP, gRPC and database clients, so every service emits spans without per-team work.
- On-call opens the trace of a slow checkout and sees in the waterfall exactly which downstream service added the 4 seconds.
- Tail-based sampling captures rare error paths that head-based sampling would have dropped at the entrance.
- Trace IDs written into every log line let a support ticket number resolve straight to a call graph.
- The trace backend feeds RED metrics — rate, errors and duration — per endpoint, replacing hand-written latency gauges.
Best practices
- Propagate context across every boundary: HTTP headers, message queues, jobs and database calls.
- Keep span names low-cardinality and static, e.g. GET /checkout, never embedding user IDs.
- Sample intentionally: head-based sampling for high-volume traffic, tail-based sampling to keep rare errors.
- Add stable attributes like service.version, http.route and error.type to spans for richer queries.
- Log the trace ID in every service so correlation survives even when a trace is sampled out.
Common mistakes
- Not propagating the header, so every service starts a new root trace and the story fragments.
- Adding high-cardinality attributes like user IDs to every span, inflating the backend's storage.
- Sampling 100% head-based and burning the budget on healthy traffic instead of failures.
- Naming spans with dynamic values, which destroys the backend's ability to aggregate by operation.
- Instrumenting only some services, so the trace is full of gaps and hard to reason about.
Common issues
No traces appear in the tracing backendThe OTLP exporter is misconfigured or the collector is unreachable.
Verify the OTLP endpoint (port 4317 for gRPC, 4318 for HTTP) and check exporter error metrics.
A request shows up as several separate root tracesContext propagation is missing on one service boundary.
Capture the traceparent header leaving the service and confirm the downstream service extracts it, not discards it.
Tracing storage grows faster than budgetSampling rate is too high or spans carry high-cardinality attributes.
Lower the head sampling rate, apply tail-based sampling, and trim volatile attributes from spans.
Debugging tips
- Sort spans by duration; the widest gap between child spans indicates waiting on a dependency.
- Fetch the same trace a few times and compare durations to distinguish a slow path from an intermittent spike.
- Filter spans with error status or error.type attributes to jump straight to the failing call.
- Grep logs for the trace ID to see application-level context around each span boundary.
- Check the sampling policy when a trace is missing — it may have been deliberately dropped, not lost.
Interview checkpoint
Hands-on lab
- Run the Jaeger all-in-one backend
- Emit a trace over the OTLP/HTTP protocol with curl
- Verify the trace in the Jaeger query API
- Clean up the backend when finished
Send a Trace to Jaeger and Inspect It
25 min · intermediate
Quick quiz
Which header does W3C trace context use to propagate trace state?
What is head-based sampling?
A span is a single unit of work with a start time, end time and attributes.
Cheat sheet
00-<traceid>-<spanid>-01traceparent header formattrace ID128-bit hex shared by every spanspan ID64-bit hex, unique per spansampled flag 01This trace is being recordedbaggageExtra key/value propagation (tracestate)OTLP gRPC :4317OTel collector gRPC receiverOTLP HTTP :4318OTel collector HTTP receiverJaeger UI :16686Query API and web UITempoGrafana's trace backendtrace_id in logsCorrelate logs with traces