Skip to content
Learni
View all tutorials
Observabilité

How to Configure Jaeger for Advanced Distributed Tracing in 2026

Lire en français

Introduction

Jaeger has become the go-to tool for distributed tracing in microservices architectures. In 2026, observability requirements demand more than a basic deployment: you need intelligent sampling, multi-service correlation, and seamless integration with CI/CD pipelines. This tutorial guides you step by step through setting up a production-ready solution. You'll learn how to instrument Node.js/TypeScript applications, configure high-performance collectors, and optimize trace storage costs. Each step includes concrete, working examples you can copy directly.

Prerequisites

  • Docker and Docker Compose v2.20+
  • Solid knowledge of TypeScript and OpenTelemetry
  • Kubernetes cluster (optional for production)
  • Node.js 20+ and npm

Production Deployment with Docker Compose

docker-compose.yml
version: '3.8'
services:
  jaeger-collector:
    image: jaegertracing/jaeger-collector:1.55
    environment:
      - COLLECTOR_OTLP_ENABLED=true
      - SPAN_STORAGE_TYPE=elasticsearch
    ports:
      - "14268:14268"
      - "4317:4317"
      - "4318:4318"
  jaeger-query:
    image: jaegertracing/jaeger-query:1.55
    environment:
      - SPAN_STORAGE_TYPE=elasticsearch
    ports:
      - "16686:16686"

This file deploys a production-ready Jaeger collector and query service. Port 4317 handles OTLP gRPC and 4318 handles OTLP HTTP for modern ingestion.

Verifying the Deployment

Run docker compose up -d. Access the Jaeger UI at http://localhost:16686. Verify that your services appear in the service selector.

Advanced OpenTelemetry Instrumentation

src/tracing.ts
import { NodeSDK } from '@opentelemetry/sdk-node';
import { getNodeAutoInstrumentations } from '@opentelemetry/auto-instrumentations-node';
import { Resource } from '@opentelemetry/resources';
import { SemanticResourceAttributes } from '@opentelemetry/semantic-conventions';

const sdk = new NodeSDK({
  resource: new Resource({
    [SemanticResourceAttributes.SERVICE_NAME]: 'api-users',
    [SemanticResourceAttributes.DEPLOYMENT_ENVIRONMENT]: 'production',
  }),
  instrumentations: [getNodeAutoInstrumentations()],
});
sdk.start();

This module configures the OpenTelemetry SDK with automatic framework detection. It sends traces to the Jaeger collector via OTLP by default.

Configuring Probabilistic Sampling

src/sampling.ts
import { ParentBasedSampler, TraceIdRatioBasedSampler } from '@opentelemetry/core';

const sampler = new ParentBasedSampler({
  root: new TraceIdRatioBasedSampler(0.1), // 10% of root traces
});
// Pass this into your NodeSDK options

A 10% sampling rate drastically reduces data volume while maintaining a representative view of traffic. Use ParentBasedSampler to respect parent service sampling decisions.

Adding Custom Tags and Spans

src/user-service.ts
import { trace } from '@opentelemetry/api';
const tracer = trace.getTracer('user-service');
export async function getUser(id: string) {
  const span = tracer.startSpan('getUser');
  span.setAttribute('user.id', id);
  span.setAttribute('user.tier', 'premium');
  try {
    return await db.findUser(id);
  } finally {
    span.end();
  }
}

Custom attributes let you filter and analyze traces in the Jaeger UI. Always end spans in a finally block to prevent leaks.

Configuring the Collector with Elasticsearch

collector-config.yml
receivers:
  otlp:
    protocols:
      grpc:
        endpoint: 0.0.0.0:4317
processors:
  probabilistic_sampler:
    hash_seed: 22
    sampling_percentage: 15
exporters:
  elasticsearch:
    endpoints: ["http://elasticsearch:9200"]
service:
  pipelines:
    traces:
      receivers: [otlp]
      processors: [probabilistic_sampler]
      exporters: [elasticsearch]

This advanced configuration enables sampling at the collector level and stores traces in Elasticsearch for long-term retention and complex queries.

Best Practices

  • Always propagate trace context between services using W3C TraceContext headers
  • Use consistent, hierarchical span names
  • Set span size limits to avoid oversized traces
  • Monitor collector latency and queue sizes
  • Version your attribute schemas to simplify historical analysis

Common Mistakes to Avoid

  • Forgetting to enable OTLP on the collector (ports 4317/4318)
  • Setting an overly low sampling rate in test environments
  • Failing to instrument databases and message queues
  • Ignoring storage backend connection errors

Going Further

Explore our comprehensive courses on observability and distributed tracing: https://learni-group.com/formations. You'll learn Jaeger integration with Grafana Tempo and trace analysis with OpenSearch.