Skip to main content

How to Set Up Distributed Tracing with Jaeger

Deploy Jaeger, export OpenTelemetry spans to it, and propagate W3C trace context so requests are traced across services. Covers reading the waterfall view and configuring ratio sampling.

Difficulty
Intermediate
Duration
45 minutes
Steps
6

What and why

Distributed tracing follows a single request as it crosses service boundaries, recording each span and its timing. Jaeger is a popular open-source tracing backend. With OpenTelemetry generating spans and Jaeger storing and visualizing them, you can pinpoint which downstream call makes a request slow.

Prerequisites

  • Two or more services that call each other over HTTP or gRPC.
  • OpenTelemetry instrumentation already added to each service.
  • Docker installed.

Steps

1. Run the Jaeger all-in-one image

docker run -d --name jaeger \
  -p 16686:16686 \
  -p 4317:4317 -p 4318:4318 \
  jaegertracing/all-in-one:latest

Port 16686 serves the UI; 4317/4318 accept OTLP spans.

2. Point exporters at Jaeger

Configure each service to export OTLP to Jaeger:

export OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:4318
export OTEL_TRACES_EXPORTER=otlp

3. Propagate context across services

Context propagation is what links spans into one trace. OpenTelemetry SDKs inject the W3C traceparent header automatically when you use instrumented HTTP clients. If you build requests manually, inject context yourself:

const { propagation, context } = require('@opentelemetry/api');
const headers = {};
propagation.inject(context.active(), headers);
// pass headers on the outbound request

4. Generate cross-service traffic

Call the entry service so it fans out to the downstream service. Each hop should create child spans under the same trace ID.

5. Analyze traces in the UI

Open http://localhost:16686, select the entry service, and click Find Traces. Open a trace to see the waterfall. The widest bar in the timeline is your bottleneck; expand spans to read attributes and errors.

6. Configure sampling

Full tracing is expensive at scale. Use parent-based, ratio sampling:

export OTEL_TRACES_SAMPLER=parentbased_traceidratio
export OTEL_TRACES_SAMPLER_ARG=0.1

This keeps 10 percent of traces while preserving complete traces (a sampled parent keeps its children).

Verification

  • The Jaeger UI lists your services.
  • A single request appears as one trace spanning multiple services.
  • Span attributes and any errors are visible in the waterfall.

Next Steps

Swap the all-in-one image for a production deployment with persistent storage (Elasticsearch or Cassandra), add tail-based sampling at the Collector, and correlate traces with logs and metrics by exemplars.

Prerequisites

  • Two or more services that call each other
  • OpenTelemetry instrumentation in place
  • Docker installed

Steps

  • 1
    Run the Jaeger all-in-one image
  • 2
    Point exporters at Jaeger
  • 3
    Propagate context across services
  • 4
    Generate cross-service traffic
  • 5
    Analyze traces in the UI
  • 6
    Configure sampling