Kokil Thapa - Professional Web Developer in Nepal
Freelancer Web Developer in Nepal with 15+ Years of Experience

Kokil Thapa is an experienced full-stack web developer focused on building fast, secure, and scalable web applications. He helps businesses and individuals create SEO-friendly, user-focused digital platforms designed for long-term growth.

Jaeger: Distributed Tracing Explained

By Kokil Thapa | Last reviewed: September 2026

A single slow checkout on a Laravel eCommerce site can touch five services, two queues, and a payment gateway. Logs show fragments, but they never stitch the full story together. Jaeger: Distributed Tracing Explained is the reference most teams need when they outgrow grep and start asking which hop actually burned 800 ms. Jaeger is a CNCF graduated tracing backend that stores traces as trees of spans and renders them in a UI you can search by trace ID, service, or latency. This guide covers how Jaeger works, how to deploy it, and how to wire it into PHP and Laravel stacks you already run in production.

If you already run microservices or a modular monolith with async jobs, start with our companion walkthrough on OpenTelemetry and Jaeger instrumentation. For API-heavy platforms, pairing traces with structured logging and API development practices gives you faster root-cause analysis than either tool alone.

What is Jaeger and why do teams adopt distributed tracing?

Jaeger originated at Uber and is now a graduated project under the Cloud Native Computing Foundation. It answers one question logs struggle with: where did time go across service boundaries? A trace is the full journey of one logical request. Each unit inside that trace is a span — a timed operation with a name, start time, duration, and optional tags.

On production Laravel applications I've maintained, the pattern repeats. A booking API returns 504 after 30 seconds. Nginx, PHP-FPM, MySQL, Redis, and an external SMS API each look fine in isolation. Distributed tracing shows the SMS call blocked for 28 seconds while everything else finished in under 200 ms. That single span saves hours of guesswork.

Jaeger Trace AnatomyTrace ID: 7f3a…c91b — POST /api/bookings (total 342 ms)Span: nginx.proxy (342 ms)Span: laravel.controllerSpan: queue.dispatchSpan: mysql.querySpan: redis.getJaeger UI searchJaeger CollectorStorage backend
Jaeger distributed tracing explained: one trace contains nested spans exported from each service and stored for search in the Jaeger UI.

Tracing differs from metrics and logs in scope. Metrics tell you error rates climbed. Logs tell you one service threw an exception. Traces show the causal chain — parent span to child span — across process and network boundaries. For platforms like Adventure Third Pole Trek, where booking, supplier CRM, and payment flows interleave, that chain is operational gold.

Core Jaeger components

  • Client libraries / OpenTelemetry SDK — create spans inside your application code or auto-instrumentation agents.
  • Agent — optional sidecar that receives spans over UDP and forwards them to the collector; useful in Kubernetes.
  • Collector — validates, batches, and writes spans to storage.
  • Query — API layer that serves trace lookups to the UI.
  • UI — search traces by service, operation, tags, min/max duration, or trace ID.
  • Storage — Cassandra, Elasticsearch, or Badger (dev only) hold span data with configurable retention.

Modern deployments rarely use Jaeger's legacy client libraries directly. The recommended path in 2026 is OpenTelemetry for instrumentation and OTLP export to Jaeger's collector. The official Jaeger documentation describes this as the supported integration model going forward.

How does Jaeger distributed tracing work end to end?

Every span carries a trace_id shared across the request and a unique span_id. When service A calls service B, A injects trace context into HTTP headers — typically traceparent per the W3C Trace Context standard. Service B extracts that context and creates a child span linked to the same trace. Without propagation, you get orphaned spans that look like unrelated requests.

Sampling controls cost. Head-based sampling decides at trace start whether to keep the entire trace. Tail-based sampling (often handled upstream in the OpenTelemetry Collector) can keep slow or errored traces while dropping happy paths. For a booking portal handling thousands of reads per minute, sampling at 1–5% is usually enough to catch regressions without filling Elasticsearch.

Trace Context Propagation FlowBrowserstarts traceAPI Gatewayspan 45 msLaravel APIspan 180 msQueue Jobspan 95 mstraceparentMySQL SELECTspan 62 msRedis GETspan 12 msPayment APIspan 310 msJaeger UI — flamegraph shows Payment API as bottleneckFilter: service=laravel-api duration>200ms
Jaeger distributed tracing propagation: W3C traceparent headers link child spans so the UI reveals which downstream call consumed the most time.

Span attributes worth setting in production

Tags turn a pretty timeline into a searchable debugger. Standard semantic conventions from OpenTelemetry include http.method, http.status_code, db.system, and net.peer.name. Add business tags sparingly — order.id, tenant.slug, payment.provider — when support teams need to correlate traces with tickets.

Events inside spans mark point-in-time occurrences: "cache miss", "retry attempt 2", "webhook signature verified". They appear as markers on the span bar in Jaeger's UI. I've found them more useful than stuffing everything into span names.

Reading the Jaeger UI

  1. Open the Search tab and filter by service plus minimum duration to surface slow paths quickly.
  2. Click a trace to view the Gantt-style timeline; wider bars mean longer operations.
  3. Switch to the Trace Timeline JSON view when you need raw span data for a ticket or a JSON formatter review.
  4. Use Compare traces when validating a deploy: same endpoint, before and after release.
  5. Copy the trace ID into log queries so logs and traces align on one request.

Pair Jaeger with metrics from Prometheus and alerts via Alertmanager workflows. Traces explain individual slow requests; metrics tell you the blast radius.

How do you deploy Jaeger for production in 2026?

For local development, the all-in-one container is enough. Production needs separated collector, query, and storage with retention policies and backups. Elasticsearch 8.x or Cassandra remain common backends; object storage adapters exist for long-term archival in larger shops.

A minimal Docker Compose stack for staging on Ubuntu 24 with OpenTelemetry Collector forwarding OTLP to Jaeger looks like this:

services:
  jaeger:
    image: jaegertracing/all-in-one:1.64
    ports:
      - "16686:16686"
      - "4317:4317"
      - "4318:4318"
    environment:
      COLLECTOR_OTLP_ENABLED: "true"

  otel-collector:
    image: otel/opentelemetry-collector-contrib:0.120.0
    volumes:
      - ./otel-collector.yaml:/etc/otelcol-contrib/config.yaml
    depends_on:
      - jaeger

Collector config receiving OTLP and exporting to Jaeger:

receivers:
  otlp:
    protocols:
      grpc:
        endpoint: 0.0.0.0:4317
      http:
        endpoint: 0.0.0.0:4318

processors:
  batch:
    timeout: 5s
    send_batch_size: 1024
  probabilistic_sampler:
    sampling_percentage: 5

exporters:
  otlp/jaeger:
    endpoint: jaeger:4317
    tls:
      insecure: true

service:
  pipelines:
    traces:
      receivers: [otlp]
      processors: [probabilistic_sampler, batch]
      exporters: [otlp/jaeger]

On Kubernetes, run the collector as a DaemonSet or sidecar and Jaeger components via the official Helm chart. Pin image tags in Git, wire retention on Elasticsearch indices, and monitor collector queue depth. If spans pile up, your storage or network is the bottleneck — not PHP.

For teams without a dedicated platform group, managed options or Grafana Tempo may reduce ops load. See our comparison with Tempo and Grafana tracing when you already run the LGTM stack. Either way, treat tracing infrastructure like any other production dependency covered under Linux system administration and backup routines.

Production Jaeger TopologyLaravelOTel SDKNginxauto-inst.OTel Collectorsample + batchJaeger CollectorJaeger QueryJaeger UIElastic-search7–30 dayretentionOps checklist: TLS on OTLP, index lifecycle, collector memory limitsUFW allow 4317 only from app subnets — not the public internet
Production Jaeger deployment: applications export OTLP to a collector that samples spans before Jaeger writes them to Elasticsearch with defined retention.

How do you instrument PHP and Laravel applications for Jaeger?

PHP tracing matured through OpenTelemetry PHP auto-instrumentation and manual span APIs. On Laravel 12 or 13 with PHP 8.3+, install the OpenTelemetry extension and SDK packages via Composer 2.10:

composer require open-telemetry/opentelemetry
composer require open-telemetry/exporter-otlp
composer require open-telemetry/sdk

Register a service provider that bootstraps the tracer and exports over OTLP HTTP to your collector:

<?php

namespace App\Providers;

use Illuminate\Support\ServiceProvider;
use OpenTelemetry\Contrib\Otlp\OtlpHttpTransportFactory;
use OpenTelemetry\SDK\Trace\SpanProcessor\BatchSpanProcessor;
use OpenTelemetry\SDK\Trace\TracerProvider;

class TracingServiceProvider extends ServiceProvider
{
    public function boot(): void
    {
        $transport = (new OtlpHttpTransportFactory())
            ->create('http://otel-collector:4318/v1/traces');

        $provider = TracerProvider::builder()
            ->addSpanProcessor(new BatchSpanProcessor($transport))
            ->build();

        app()->instance('otel.tracer', $provider->getTracer('laravel-app'));
    }
}

Wrap critical paths manually when auto-instrumentation misses domain logic:

$tracer = app('otel.tracer');
$span = $tracer->spanBuilder('booking.create')->startSpan();

$scope = $span->activate();
try {
    $span->setAttribute('booking.destination', $destination);
    $booking = $this->bookingService->create($payload);
    $span->setAttribute('booking.id', $booking->id);
} catch (\Throwable $e) {
    $span->recordException($e);
    $span->setStatus(\OpenTelemetry\API\Trace\StatusCode::STATUS_ERROR);
    throw $e;
} finally {
    $scope->detach();
    $span->end();
}

For outbound HTTP calls to Khalti, Stripe, or SMS gateways, ensure the Guzzle middleware propagates trace context. Without it, external latency appears as a gap in your trace. Queue workers need separate instrumentation — a dispatched job starts a new trace unless you pass context in the job payload. On client portals like Mijar Law Associates, document upload and payment spans often live in the worker, not the web request.

Do not trace everything on day one. Start with checkout, auth, webhooks, and third-party integrations. Those paths fail under load and generate support tickets. Expand coverage after you confirm collector and storage sizing. For broader quality gates, fold trace IDs into your testing and optimization checklist alongside load tests.

Common Laravel tracing mistakes

  • Logging trace IDs but never passing them to the frontend error report.
  • Running the all-in-one Jaeger image in production without persistent storage.
  • 100% sampling on high-traffic read endpoints — Elasticsearch bills grow fast.
  • Forgetting opcache reload after deploy; old code paths still emit spans without new attributes.
  • Missing propagation on internal microservice calls behind Apache reverse proxies.

I've hit the opcache issue on Deployer 7 releases where PHP-FPM was not reloaded. Traces looked healthy while users hit stale controllers. Reload PHP-FPM after symlink swap, same as any Laravel deploy.

How does Jaeger compare to Zipkin, Tempo, and other tracing backends?

All tracing backends ingest spans; they differ in ops model, query UX, and ecosystem fit. Jaeger's UI remains one of the most approachable for engineers debugging latency. Zipkin is lighter but shows its age in large deployments. Grafana Tempo pairs naturally if you already run Loki and Prometheus.

CriteriaJaegerZipkinGrafana Tempo
Primary protocolOTLP (native), legacy Jaeger formatZipkin HTTP / JSONOTLP, Jaeger, Zipkin
Storage optionsElasticsearch, Cassandra, BadgerIn-memory, Elasticsearch, MySQLObject storage (S3, GCS, MinIO)
UI strengthsTrace search, comparison, dependency graphSimple timelineEmbedded in Grafana dashboards
Ops complexityMedium — separate collector/query/storageLow for small installsLow at scale with object storage
Best fitPolyglot microservices, dedicated trace UILegacy Java shops, quick POCTeams already on Grafana LGTM stack

Pick Jaeger when you want a focused tracing UI and OpenTelemetry-native ingestion without tying traces to a single vendor dashboard. Pick Tempo when metrics, logs, and traces should live in one Grafana workspace. Many teams run OpenTelemetry Collector once and fan out to both during migration.

OpenTelemetry is the instrumentation layer regardless of backend. The OpenTelemetry project documents semantic conventions and SDK setup per language. Jaeger's own docs describe storage tuning and production deployment patterns — cite both when onboarding a team.

Tracing Backend Decision TreeNeed distributed tracing?Grafana stackalready in prod?Standalonetrace UI needed?yesyesGrafana Tempoobject storageJaegerES or CassandraZipkin POCsmall Java appsAll paths: instrument with OpenTelemetry first
Choosing a tracing backend: Jaeger fits teams wanting a dedicated UI and OTLP ingestion; Tempo suits existing Grafana deployments.

Security matters. Spans can carry user IDs, email fragments, or payment metadata. Scrub PII at the collector processor before storage. Restrict Jaeger UI behind VPN or SSO. Treat trace data with the same retention policy as application logs on legal-tech portals where document access is audited.

For enterprise rollouts spanning multiple Laravel apps and WordPress frontends, plan tracing in the architecture phase of enterprise application development. Retrofitting propagation across ad hoc cURL calls is painful. Standardize on Guzzle or Laravel HTTP client with shared middleware early.

Tracing also complements API rate limiting work — spikes in 429 responses show up in span tags while traces reveal which upstream partner triggered the storm. Connect trace IDs to your AIOps alerting pipeline when you automate anomaly detection on latency percentiles.

After you stabilise tracing, use flamegraphs to prioritise speed optimisation work with evidence instead of assumptions. The widest span bar is your next ticket. On eCommerce builds like Quick And Easy Nepalese Grocery, checkout traces often expose delivery-zone calculation or inventory locks that profilers miss.

Maintenance contracts should include collector health checks and storage growth reviews. Tracing that silently stops exporting is worse than no tracing — you trust data that frozen at last month's deploy. Include OTLP endpoint checks in the same runbook as database backups under support and maintenance procedures.

If you build custom observability wrappers, keep JSON span exports compatible with standard tools. A quick pass through a formatter catches malformed attributes before they hit the collector. For greenfield services, custom software development sprints should define span naming conventions in the same document as API versioning rules.

Server provisioning playbooks — whether Ansible or manual — should open OTLP ports only on internal networks. Public exposure of collectors has led to span injection and data leaks in the wild. Harden the host the same way you would for Redis or MySQL admin ports. Our Ansible playbooks for PHP servers pattern applies: group vars for collector URLs, not hard-coded IPs in .env files.

External references worth bookmarking: the Jaeger documentation for storage and deployment, and the OpenTelemetry documentation for SDK and semantic conventions. The CNCF graduated project listing confirms Jaeger's long-term maintenance trajectory independent of any single vendor.

Key Takeaways

  • Jaeger stores distributed traces as nested spans; OpenTelemetry is the standard instrumentation layer in 2026.
  • Propagate W3C traceparent headers on every internal and outbound HTTP call or traces will break at service boundaries.
  • Start sampling at 1–5% on high-traffic endpoints; expand after storage and collector capacity are proven.
  • Instrument checkout, auth, webhooks, and third-party APIs first — that is where production time disappears.
  • Scrub PII in collector processors and restrict Jaeger UI access; spans often contain identifiers you cannot log publicly.
  • Pair Jaeger traces with metrics and structured logs via shared trace IDs for full-stack incident response.

People Also Ask

Is Jaeger still maintained in 2026?

Yes. Jaeger remains an active CNCF graduated project with regular releases, OTLP-native collectors, and updated Helm charts. Development focus sits on OpenTelemetry compatibility rather than legacy Jaeger client libraries.

Do I need Kubernetes to run Jaeger?

No. Docker Compose on a single Ubuntu server is fine for small teams and staging. Kubernetes simplifies agent sidecars at scale, but many Laravel shops run collector and Jaeger on the same VM as the app until traffic demands separation.

What is the difference between a trace and a span in Jaeger?

A trace represents one logical request end to end and shares a single trace ID. Spans are the individual timed operations inside that trace — database query, HTTP call, queue job — each with its own span ID and parent reference.

Can Jaeger trace Laravel queue workers?

Yes, but workers do not inherit web request context automatically. Pass trace context in the job payload or create linked spans explicitly. Otherwise worker operations appear as unrelated traces and you lose the booking-to-notification story.

Ship observability that survives production traffic

Jaeger: Distributed Tracing Explained boils down to three moves: instrument with OpenTelemetry, export OTLP to a sampled collector, and read traces before you restart services blindly. The payoff is faster incidents, clearer API contracts, and optimisation work grounded in span timings instead of hunches. If you want tracing wired into your Laravel platform, payment flows, or multi-service deploy pipeline, contact us or review how we approach production systems on the home page and about page.

Frequently Asked Questions

Jaeger is a CNCF graduated tracing backend that stores request journeys as searchable trees of spans, showing timing, errors, and metadata across every service hop in one UI.

A trace is the full journey of one logical request across every service, queue, and external call. A span is a single timed operation inside that trace — it has a name, start time, duration, and optional tags. Nested spans form the tree you see in Jaeger's Gantt-style timeline. When a booking API returns 504, one slow child span — often an SMS or payment gateway call — reveals where 28 seconds disappeared while other hops finished in under 200 ms.

Metrics tell you error rates climbed; logs tell you one service threw an exception. Neither stitches the causal chain across process and network boundaries. Jaeger traces link parent spans to child spans so you see which hop actually burned 800 ms — an external API, a Redis call, or a queue worker. On production Laravel applications I've maintained, that single span view saves hours of grep across fragmented Nginx, PHP-FPM, and MySQL logs that each look fine in isolation.

Modern stacks include OpenTelemetry SDKs that create spans inside application code, an optional Agent sidecar that receives spans over UDP in Kubernetes, a Collector that validates batches and writes to storage, a Query API serving trace lookups, the Jaeger UI for search, and persistent Storage. In 2026 the supported path is OpenTelemetry instrumentation exporting OTLP to the collector — not legacy Jaeger client libraries directly. Production separates collector, query, and storage with retention policies and backups rather than relying on a single all-in-one container.

Every span carries a shared trace_id and a unique span_id. When service A calls service B, A injects trace context into HTTP headers — typically the W3C traceparent standard. Service B extracts that context and creates a child span linked to the same trace. Without propagation you get orphaned spans that look like unrelated requests. For outbound Guzzle calls to Khalti, Stripe, or SMS gateways, missing middleware leaves external latency as a gap instead of a measurable span bar in the UI.

Local development can use the all-in-one container with COLLECTOR_OTLP_ENABLED. Production needs separated collector, query, and storage with retention and backups. A staging stack on Ubuntu 24 pairs jaegertracing/all-in-one:1.64 with otel/opentelemetry-collector-contrib:0.120.0 receiving OTLP on ports 4317 and 4318, applying probabilistic sampling and batching before export. On Kubernetes, run the collector as a DaemonSet or sidecar and Jaeger via the official Helm chart. Pin image tags in Git, wire Elasticsearch index retention, and monitor collector queue depth — if spans pile up, storage or network is the bottleneck, not PHP.

Jaeger writes span data to Cassandra, Elasticsearch 8.x, or Badger. Badger is development-only without durable production retention. Elasticsearch and Cassandra remain common production choices with configurable retention policies and backups. Larger deployments may use object storage adapters for long-term archival. Index growth on Elasticsearch drives cost quickly when sampling is set too high, so size storage alongside your sampling percentage from the first production deploy rather than after bills spike.

On Laravel 12 or 13 with PHP 8.3+, install open-telemetry/opentelemetry, exporter-otlp, and sdk via Composer 2.10. Register a TracingServiceProvider that bootstraps a TracerProvider exporting OTLP HTTP to your collector at port 4318. Wrap critical paths manually when auto-instrumentation misses domain logic — booking.create with attributes like booking.destination and booking.id, recordException on failures. Ensure Guzzle middleware propagates trace context on outbound payment and SMS calls. Queue workers need separate instrumentation; otherwise dispatched jobs start new traces disconnected from the web request that triggered them.

Head-based sampling at 1–5% is usually enough on read-heavy endpoints. Tail-based sampling upstream in the OpenTelemetry Collector can keep slow or errored traces while dropping happy paths.

Open the Search tab and filter by service plus minimum duration to surface slow paths quickly. Click a trace for the Gantt timeline — wider bars mean longer operations. Switch to Trace Timeline JSON when you need raw span data for a support ticket. Use Compare traces to validate deploys on the same endpoint before and after release. Copy trace IDs into log queries so logs and traces align on one request. Pair Jaeger with Prometheus metrics and Alertmanager alerts: traces explain individual slow requests while metrics show blast radius across the fleet.

All tracing backends ingest spans but differ in ops model and ecosystem fit. Jaeger offers OTLP-native ingestion, approachable trace search, comparison views, and dependency graphs; ops complexity is medium with separate collector, query, and storage. Zipkin is lighter for small installs but shows its age in large deployments. Grafana Tempo pairs naturally with Loki and Prometheus, uses object storage backends, and embeds traces in Grafana dashboards with lower ops at scale. Pick Jaeger for a dedicated tracing UI without tying to one vendor dashboard. Pick Tempo when metrics, logs, and traces should live in one Grafana workspace.

Logging trace IDs but never passing them to frontend error reports. Running the all-in-one Jaeger image in production without persistent storage. Using 100% sampling on high-traffic read endpoints — Elasticsearch bills grow fast. Forgetting PHP-FPM reload after a Deployer 7 symlink swap so opcache serves stale controllers that emit spans without new attributes. Missing propagation on internal microservice calls behind Apache reverse proxies. Leaving queue workers uninstrumented so document upload and payment spans live disconnected from the web request. Tracing that silently stops exporting is worse than no tracing because you trust stale data.

Spans can carry user IDs, email fragments, or payment metadata. Scrub PII at the collector processor before storage. Restrict the Jaeger UI behind VPN or SSO instead of leaving port 16686 open without authentication. Apply the same retention policy as application logs — especially on legal-tech portals where document access is audited. Treat trace data as sensitive operational telemetry alongside your standard Linux administration, backup, and access-control routines rather than as disposable debug output.

Follow OpenTelemetry semantic conventions: http.method, http.status_code, db.system, and net.peer.name turn timelines into searchable debuggers. Add business tags sparingly — order.id, tenant.slug, payment.provider — when support teams need to correlate traces with tickets. Use span events for point-in-time markers like cache miss, retry attempt 2, or webhook signature verified; they appear on the span bar and beat stuffing everything into span names. Do not trace everything on day one. Start with checkout, auth, webhooks, and third-party integrations — those paths fail under load and generate support tickets.

Start when a modular monolith or microservices with async jobs outgrow grep — once checkout, booking, or API paths touch multiple services, queues, and external gateways and logs only show fragments.

Share this article

0 Comments

Leave a comment

Your email is not published. Comments appear once they have been read. Sign in to have your details filled in.

Quick Contact Options
Choose how you want to connect me: