Monitoring
Intermediate 16 min read Monitoring

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.

distributed tracingspantracecontext propagationtraceparentsamplingOpenTelemetryOTLPJaegertrace ID
Introduction

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.

Motivation

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.
Think about it

Mental model

Imagine

Breadcrumbs on a hiking trail

Think of

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.

Mechanics

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.

Gateway
Propagate
Service A
Service B
Backend
Engineer
Reference

Key commands

curl -H 'traceparent: 00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01' http://localhost:8080/checkout

Inject 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 4bf92f3577b34da6

Pull log lines that share a trace ID from a container.

kubectl get pods -l app=checkout

Locate 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.

Hands-on

Examples

Inspect trace context on a request

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 trace
grep 4bf92f3577b34da6a3ce929d0e0e4736 /var/log/app/*.log

traceparent 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.

Real world

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.
Guidelines

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.
Watch 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.
Root cause analysis

Common issues

Error
No traces appear in the tracing backend
Root cause

The OTLP exporter is misconfigured or the collector is unreachable.

Fix

Verify the OTLP endpoint (port 4317 for gRPC, 4318 for HTTP) and check exporter error metrics.

Error
A request shows up as several separate root traces
Root cause

Context propagation is missing on one service boundary.

Fix

Capture the traceparent header leaving the service and confirm the downstream service extracts it, not discards it.

Error
Tracing storage grows faster than budget
Root cause

Sampling rate is too high or spans carry high-cardinality attributes.

Fix

Lower the head sampling rate, apply tail-based sampling, and trim volatile attributes from spans.

When things break

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.
Interviews

Interview checkpoint

Practice

Hands-on lab

Send a Trace to Jaeger and Inspect It
25 min · intermediate
In progress
Objectives
  • 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

0/6 steps
0%
sandbox · Linux sandbox
$
Try these commands
Test yourself

Quick quiz

0/3
1

Which header does W3C trace context use to propagate trace state?

2

What is head-based sampling?

3

A span is a single unit of work with a start time, end time and attributes.

Quick reference

Cheat sheet

W3C trace context
00-<traceid>-<spanid>-01traceparent header format
trace ID128-bit hex shared by every span
span ID64-bit hex, unique per span
sampled flag 01This trace is being recorded
baggageExtra key/value propagation (tracestate)
OpenTelemetry + backends
OTLP gRPC :4317OTel collector gRPC receiver
OTLP HTTP :4318OTel collector HTTP receiver
Jaeger UI :16686Query API and web UI
TempoGrafana's trace backend
trace_id in logsCorrelate logs with traces
Go deeper

Official documentation