What Is Jaeger Tracing? Learn by Implementing
Distributed applications make it difficult to follow requests across services and identify the source of latency or errors. Jaeger tracing addresses this problem by collecting and visualizing trace data generated by instrumented applications.
This article explains what Jaeger is, how distributed tracing works, its core components, how to set it up, and how to use traces to troubleshoot performance issues in distributed systems.
Jaeger is an open-source, cloud-native distributed tracing platform for monitoring and troubleshooting microservices-based applications. It reconstructs the path of a request from spans created as the request travels through multiple services.
Created at Uber in 2015, Jaeger became a graduated CNCF project in 2019. Jaeger v2 is re-architected around the OpenTelemetry Protocol (OTLP) and can receive trace data through OTLP. Applications are typically instrumented using OpenTelemetry, while Jaeger receives, stores, queries, and visualizes the resulting traces.
How Does Jaeger Tracing Work?
Jaeger tracing works by receiving spans generated by instrumented applications and reconstructing them into an end-to-end view of a request. Before Jaeger can display a trace, the instrumentation must start or continue a trace, record operations as spans, propagate trace context between services, and export completed spans.
1. Starting a Trace
When an instrumented service receives a request without existing trace context, its OpenTelemetry instrumentation creates a root span with a new trace ID and span ID.
The trace ID identifies the complete request journey, while the span ID identifies this particular operation. If valid trace context is present in the incoming request, the service continues that trace instead of starting a new one.
2. Recording Operations as Spans
Each instrumented operation creates a span. A span records details such as the operation name, start and end times, duration, status, service information, attributes, and events.
Each span has zero or one parent. A non-root span records its parent span’s ID, linking the operations into a trace tree. A root span has no parent span ID because it is the first span created for the trace. New spans generally use the currently active span as their parent, forming a hierarchy of operations. For example, an HTTP span for GET /checkout might record the route, response status code, and total duration. If the operation fails, the span can include an error status and an exception event. Application-specific information can also be recorded as custom span attributes.
When the service calls another service, HTTP instrumentation commonly creates a client span for the outgoing request. This client span becomes the current span whose context is sent to the downstream service.
3. Propagating Trace Context
Before sending the request, the instrumentation injects the current span’s context into the outgoing request headers. This process is called trace context propagation.
HTTP applications commonly use the W3C Trace Context format:
traceparent: 00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01
│ │ │ │
│ │ │ Trace flags
│ │ Parent ID
│ Trace ID
VersionThe parent-id field contains the span ID of the caller’s current span. The downstream service extracts this context and creates its own span with the same trace ID and a new span ID. The extracted span context becomes the parent of the new span, connecting operations performed by both services into one trace.
4. Exporting Spans
When a span ends, the OpenTelemetry SDK passes it to a span processor. Applications commonly use a Batch Span Processor, which temporarily queues completed spans and exports them in batches. The spans are sent through OTLP over gRPC or HTTP, either directly to Jaeger or through an optional OpenTelemetry Collector.
Batching moves most export work away from the request path and reduces the number of network calls. However, tracing still introduces some processing and memory overhead, and spans can be dropped if the export queue becomes full. After export, the spans enter the Jaeger backend, where they are stored and retrieved by trace ID. Jaeger uses their trace IDs and parent-child relationships to reconstruct and visualize the complete request.
Jaeger Architecture and Core Components
Jaeger v2 is built on the OpenTelemetry Collector framework. A single Jaeger binary can perform different roles, allowing the components to run together during local development or separately in production.

OpenTelemetry Collector
The OpenTelemetry Collector is an optional component that sits outside the Jaeger backend. It can receive spans from applications, process or enrich them, and forward them to Jaeger.
Applications can also export OTLP traces directly to Jaeger when they do not need this additional processing layer.
Older Jaeger architectures commonly placed a Jaeger Agent beside each application. Jaeger v2 still supports an agent role, but the project recommends using a standard OpenTelemetry Collector for new agent or sidecar deployments.
Jaeger Collector
The Jaeger Collector receives spans from instrumented applications or an OpenTelemetry Collector. Modern applications instrumented via OpenTelemetry SDKs commonly send spans through OTLP over gRPC on port 4317 or OTLP over HTTP on port 4318.
After receiving the spans, the collector passes them through its configured processing pipeline and writes them to the storage backend. It does not create application spans because that work happens inside the instrumented application.
Trace Storage
The storage backend persists spans so Jaeger can search and reconstruct traces later. Jaeger supports distributed storage backends such as Cassandra, Elasticsearch, and OpenSearch.
In-memory storage is useful for local development because it does not require an additional database. However, its data disappears when Jaeger restarts, making it unsuitable for production use.
Jaeger Query
Jaeger Query reads trace data from storage and provides the APIs used to search and retrieve it. Searches can use properties such as service name, operation, duration, tags, and trace ID.
Separating the query role from collection allows production deployments to scale trace ingestion and trace searches independently.
Jaeger UI
The Jaeger UI communicates with Jaeger Query and presents trace data in a browser. It displays spans on a shared timeline, allowing engineers to inspect service relationships, operation durations, attributes, events, and errors.
The UI does not retrieve spans directly from the storage backend. Jaeger Query retrieves the requested data and returns it to the interface.
Key Features of Jaeger
-
Native OTLP Support: Jaeger accepts OTLP trace data over gRPC and HTTP, allowing OpenTelemetry-instrumented applications to export spans directly or through an OpenTelemetry Collector.
-
Trace Search and Visualization: Engineers can search traces by service, operation, duration, tags, or trace ID and inspect spans in a timeline. The UI also supports structural trace comparison.
-
Service Dependency Graphs: Jaeger generates System Architecture and Deep Dependency Graph views that show observed relationships and request paths between services.
-
Service Performance Monitoring: The Monitor view presents request rate, error rate, and duration metrics derived from spans, helping identify slow or failing operations.
-
Scalable Deployment and Storage: Jaeger’s Collector and Query roles can scale independently and work with storage backends such as Cassandra, Elasticsearch, and OpenSearch.
-
Remote and Adaptive Sampling: Jaeger centralizes sampling configuration and calculates probabilities for services and endpoints to meet configured trace-throughput targets.
Set Up Jaeger Locally with Docker
This example runs Jaeger alongside two instrumented Node.js services:
Client → frontend-service → inventory-service → JaegerThe demo generates normal, slow, and failed requests so you can inspect meaningful traces in the Jaeger UI.
Prerequisites
- Before starting, install Git, Docker Desktop with Docker Compose, and Node.js with npm.
- Download the demo accompanying this article, then open its directory:
git clone --depth 1 --filter=blob:none --sparse https://github.com/SigNoz/examples.git signoz-examples && cd signoz-examples && git sparse-checkout set nodejs/jaeger-tracing-demo && cd nodejs/jaeger-tracing-demo
Step 1: Start Jaeger and the applications
Install the traffic-generator dependencies:
npm installStart Jaeger, frontend-service, and inventory-service:
docker compose up --build -dConfirm that all three containers are running:
docker compose psThe Compose configuration runs Jaeger 2.20 using its built-in all-in-one configuration:
services:
jaeger:
image: cr.jaegertracing.io/jaegertracing/jaeger:2.20.0
ports:
- "16686:16686"
- "4317:4317"
- "4318:4318"Port 16686 exposes the Jaeger UI, while ports 4317 and 4318 accept OTLP data over gRPC and HTTP, respectively.
The application containers export their traces to Jaeger over OTLP/HTTP:
environment:
OTEL_SERVICE_NAME: frontend-service
OTEL_EXPORTER_OTLP_TRACES_ENDPOINT: http://jaeger:4318/v1/tracesHere, jaeger is the Compose service name. Docker resolves it to the Jaeger container inside the Compose network.
Step 2: Generate traces
Run the included traffic generator:
npm run trafficIt sends normal, deliberately slow, and deliberately failing requests through both services.
Step 3: View and Analyze Traces in Jaeger
After generating traffic, open the Jaeger UI at http://localhost:16686. Select frontend-service from the Service menu and click Find Traces.
The search results contain normal requests, slow requests lasting approximately 700 ms, and failed requests marked with an error. Start with a normal trace to understand the expected request path before investigating an outlier.

Understand the trace timeline
Open a trace to view its spans. Each row represents a timed operation performed while processing the request. Indentation shows parent-child relationships, while the horizontal bars show when each operation started and how long it ran relative to the complete request.
For this demo, a checkout trace follows this path:
frontend HTTP request
└── frontend call to inventory
└── inventory HTTP request
└── inventory.lookupThe frontend and inventory spans share the same trace ID because OpenTelemetry propagates trace context between the services.
Span durations can overlap because a parent span remains active while its child operation runs. Therefore, adding every span duration together does not give the total request duration.
Find the source of latency
Return to the search results and open a trace lasting approximately 700 ms. The exact duration will vary between runs.
In the captured example, the complete trace took 709.122 ms, while the inventory.lookup span took 703.498 ms. The width of this span shows that the inventory operation accounts for nearly all of the request latency.

Select the inventory.lookup span and inspect its attributes and events. The span contains:
inventory.lookup.mode = slow
inventory.sku = camera-013It also records the following event:
simulated slow database queryThese details identify both the operation that caused the delay and the condition under which it occurred. The frontend itself was not performing 700 ms of work; it was waiting for the downstream inventory operation to complete.
Investigate a failed request
Return to the search results and open a trace marked with an error. Expand the inventory spans and select inventory.lookup.
The span has an ERROR status and records an exception similar to:
Inventory database timed out for camera-020
The inventory service records the timeout and returns HTTP 503. The frontend cannot complete the checkout, so it returns HTTP 502 to the client. Following the spans upward shows how one downstream failure affected the complete request.
In this example, the trace contains six spans across two services, with four spans marked as errors. OpenTelemetry context propagation connects these operations into one trace, while the recorded span status and exception provide the information needed to diagnose the failure.
Step 4: Stop the demo
When finished, stop and remove the containers:
docker compose downJaeger Best Practices
- Use consistent service names: Set a stable
OTEL_SERVICE_NAMEenvironment variable for each application so traces remain easy to search across environments. - Preserve trace context: Verify propagation across HTTP, gRPC, messaging, and asynchronous boundaries to prevent disconnected traces.
- Add useful span attributes: Record low-cardinality diagnostic details, but exclude credentials, personal data, and unbounded values.
- Configure sampling carefully: Control trace volume while retaining errors, slow requests, and other diagnostically valuable traces.
- Use an OpenTelemetry Collector: Add one when traces require batching, retries, enrichment, filtering, or routing.
- Monitor the tracing pipeline: Track rejected or dropped spans, exporter failures, storage health, and ingestion latency.
Limitations of Jaeger
- Tracing-focused: Jaeger primarily stores and analyzes traces. It does not provide native storage and analysis for application logs or general-purpose metrics.
- Additional infrastructure: Production deployments require an external storage backend and operational planning for scaling, retention, backups, and upgrades.
- Limited cross-signal correlation: Investigating a trace alongside related logs and infrastructure metrics generally requires integration with other observability tools.
- No native user management: Jaeger UI does not provide user accounts or roles, so restricted access typically requires an authenticated reverse proxy.
- Dependent on instrumentation quality: Missing spans, broken context propagation, or aggressive sampling can produce incomplete traces and hide the source of an incident.
- Extra setup for SPM: Service Performance Monitoring can derive metrics from spans, but it requires a separate Prometheus-compatible metrics store.
SigNoz: Open Source Alternative to Jaeger
SigNoz is an all-in-one open-source observability platform built around OpenTelemetry. You can use it to inspect traces, query logs and metrics, monitor application and infrastructure health, and create dashboards and alerts. SigNoz also groups exceptions captured through trace data, making application errors easier to investigate.
For trace analysis, SigNoz provides individual request views using flame graphs and Gantt charts. You can filter spans using attributes such as service name, operation, HTTP status code, deployment environment, or application-specific fields. Trace data can also be aggregated to examine service-level latency, request rates, and errors rather than inspecting requests individually.

The Jaeger demo in this article identified inventory.lookup as the source of a 700 ms delay. Jaeger provides the trace evidence needed to locate that operation. In a production incident, the investigation may continue into inventory-service logs, database metrics, container memory, or a recent deployment. SigNoz keeps those signals available in the same product, using their timestamps and OpenTelemetry resource attributes to narrow the search.
SigNoz also provides a RED metrics dashboard out of the box. These views summarize request rate, error rate, and duration for services and endpoints, including latency percentiles such as p50, p90, and p99. This provides a service-level view before you open an individual trace.
The OpenTelemetry instrumentation used in the Jaeger demo is not tied to Jaeger. To send its traces to SigNoz, change the OTLP exporter endpoint and supply the required authentication headers when using SigNoz Cloud. Metrics and logs require their corresponding OpenTelemetry collection to be configured as well.
Jaeger and SigNoz Compared
| Area | Jaeger | SigNoz |
|---|---|---|
| Main use | Collecting, searching, and visualizing distributed traces | Monitoring applications and infrastructure using traces, metrics, and logs |
| Trace analysis | Trace search, timelines, span details, comparison, and service dependencies | Trace search, flame graphs, attribute filtering, and trace aggregation |
| Application metrics | Span-derived metrics require additional configuration and a Prometheus-compatible store | Service RED metrics and latency percentiles are available in APM views |
| Logs | Requires a separate logging system | Log ingestion, search, filtering, and dashboards |
| Dashboards and alerts | General monitoring requires additional tools | Dashboards and alerts use trace, metric, and log data |
| Storage | Supports backends such as OpenSearch, Elasticsearch, and Cassandra | Uses ClickHouse |
| Deployment | Self-hosted | SigNoz Cloud or self-hosted |
| Instrumentation | Accepts OpenTelemetry traces over OTLP | Accepts OpenTelemetry traces, metrics, and logs over OTLP |
Jaeger remains a practical choice for teams that need a dedicated distributed tracing backend. Choose SigNoz when traces form one part of the incident workflow and your team also needs application metrics, logs, dashboards, and alerts without operating separate tools for each signal. See the Jaeger alternatives guide for a broader comparison of available tracing backends.
Get Started with SigNoz
SigNoz provides application metrics, distributed tracing, logs, dashboards, and alerts in an OpenTelemetry-native platform. You can use SigNoz Cloud or self-host SigNoz in your own environment.
If your applications already produce OpenTelemetry data, configure their OTLP exporters to send it to SigNoz. Otherwise, follow the instrumentation guides for your language or framework. Start with one service and verify that you can move from an application latency or error signal to its trace and related logs.
Start monitoring with SigNoz Cloud in minutes. Get metrics, traces, logs, dashboards, and alerts without managing the backend.
Get Started - Free