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.

Tempo: Distributed Tracing with Grafana

By Kokil Thapa | Last reviewed: September 2026

Tempo: Distributed Tracing with Grafana answers a problem every production team hits sooner or later. A checkout fails, a webhook times out, or a queue job stalls — and your logs show fragments, not the full path. Tempo is Grafana’s trace backend. It stores spans cheaply, pairs with Prometheus metrics and Grafana dashboards, and connects to Loki log lines so you can pivot from a spike to the exact request chain. This guide covers architecture, OpenTelemetry wiring, TraceQL, deployment, and how Tempo compares to Jaeger on real stacks.

What is Grafana Tempo and why use it for distributed tracing?

Tempo is a high-volume trace backend built by Grafana Labs. It accepts spans from OpenTelemetry, Jaeger, Zipkin, and other formats. Unlike older systems that index every span attribute up front, Tempo stores compressed blocks on object storage or local disk. You search by trace ID, time range, or TraceQL filters — not by building a costly inverted index of every tag.

That design matters on budget-conscious projects. A law-firm portal or Laravel booking platform may run on a single Ubuntu server or a small Kubernetes cluster. You still need traces when payment callbacks, SMS gateways, or supplier APIs fail. Tempo keeps storage predictable while Grafana gives one UI for metrics, logs, and traces.

The Grafana observability stack — sometimes called the LGTM stack — maps cleanly:

  • Prometheus — counters, histograms, and alerts on latency and error rates.
  • Loki — structured and unstructured logs with label-based filtering.
  • Tempo — distributed traces showing service-to-service timing.
  • Grafana — dashboards, Explore, and correlated drill-down.
Tempo in the Grafana Observability StackApp LayerLaravel / APIOTel SDKAuto-instrumentCollectorOTLP receiverTempoTrace storePrometheusMetrics + alertsLokiLog aggregationGrafanaUnified Explore UIExemplars link metric spikes to trace IDs in Grafana
Tempo distributed tracing with Grafana — spans flow from apps through OpenTelemetry into Tempo, alongside Prometheus and Loki.

On stacks I maintain, that correlation saves hours. You see a latency spike on a histogram panel, click an exemplar, and land on the slow trace — then jump to Loki for the same request ID. That workflow beats grep-ing three log files on a shared EC2 host.

How do you send traces to Tempo from OpenTelemetry and PHP apps?

Most greenfield setups use the OpenTelemetry specification and send OTLP to an OpenTelemetry Collector. The collector batches, filters, and forwards spans to Tempo. Direct app-to-Tempo works for small installs, but the collector gives you sampling, attribute scrubbing, and multi-backend export without redeploying code.

Run Tempo with Docker Compose

For a lab or staging server, start with the official single-binary config. Create tempo.yaml:

server:
  http_listen_port: 3200

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

storage:
  trace:
    backend: local
    local:
      path: /var/tempo/traces
    wal:
      path: /var/tempo/wal

compactor:
  compaction:
    block_retention: 168h

Pair it with a collector config that forwards OTLP to Tempo:

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

processors:
  batch:
    timeout: 5s
    send_batch_size: 1024

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

service:
  pipelines:
    traces:
      receivers: [otlp]
      processors: [batch]
      exporters: [otlp/tempo]

Instrument Laravel and PHP services

PHP auto-instrumentation has improved, but production Laravel apps often need deliberate span boundaries. Wrap controller actions, queue jobs, and outbound HTTP calls. Use the OpenTelemetry PHP SDK or run a sidecar collector on the same host as PHP-FPM.

A practical pattern on API-heavy Laravel projects:

  1. Generate a trace context at the HTTP middleware layer and attach it to the request.
  2. Create child spans for database queries, cache calls, and payment gateway requests.
  3. Propagate the traceparent header on internal service calls and webhooks.
  4. Export spans via OTLP to the local collector on port 4317.
  5. Store the trace ID in your log context so Loki queries align with Tempo.

Validate propagation before you tune sampling. A broken header means each service starts a new trace — useless for debugging cross-service checkout flows.

Trace Span Flow to TempoHTTP In45 msAuth12 msDB Query28 msPayment120 msTempoSingle trace ID ties all spans togetherOpenTelemetry CollectorBatch · Sample · Scrub PII · Forward OTLP
Each request becomes a trace with child spans — Auth, database, and payment timing exported to Tempo via OpenTelemetry.

Use a JSON formatter to inspect OTLP payloads during development. Broken attribute types or oversized span batches are easier to catch locally than in production.

How do you query traces in Grafana with TraceQL?

Tempo’s query language is TraceQL. It filters traces by span attributes, duration, service name, and status — similar in spirit to PromQL for metrics. You run TraceQL in Grafana Explore or embed results in dashboards.

Essential TraceQL patterns

{ resource.service.name = "checkout-api" && duration > 500ms }

{ name = "POST /api/payment/callback" && status = error }

{ span.http.status_code = 502 && resource.service.name = "gateway" }

TraceQL shines when paired with logs and metrics. Configure derived fields in Loki so a log line’s trace ID becomes a link to Tempo. Enable exemplars on Prometheus histograms so Grafana passes the trace ID into Explore.

For dashboard work, see the companion guide on building Grafana dashboards. Trace panels and service graphs turn TraceQL results into operational views your team can scan during incidents.

Service graph and metrics generator

Tempo can generate service graph metrics and span metrics from incoming traces. Those metrics land in Prometheus and power dependency maps — which service calls which, and how often errors occur. That closes a gap when you have traces but no pre-defined RED metrics for every internal endpoint.

Enable the metrics generator in Tempo config and point it at a Prometheus remote-write endpoint. Start with default span metrics; tune cardinality before you label every custom attribute.

How does Tempo compare to Jaeger and Zipkin for production?

Teams evaluating backends usually weigh Tempo against Jaeger and Zipkin. All three accept OpenTelemetry. The differences are storage cost, query model, and Grafana integration depth.

CriteriaGrafana TempoJaegerZipkin
Primary index modelTrace ID + object storage blocksAttribute indexing (Cassandra, ES, Badger)Dependency on external storage for scale
Grafana native integrationFirst-class — Explore, TraceQL, exemplarsGood via Jaeger data source pluginBasic via Zipkin data source
Storage cost at high volumeLower — minimal indexingHigher with full attribute indexesModerate; depends on backend choice
Query flexibilityTraceQL; best with known filtersRich UI search on indexed tagsSimple tag-based lookup
Operational fit for small teamsStrong with existing LGTM stackProven; more moving parts at scaleLightweight for prototypes

If you already run Prometheus and Loki, Tempo is the natural third pillar. Jaeger remains excellent when you need deep tag search across billions of spans and already operate its storage tier. For a deeper OpenTelemetry walkthrough, read the guide on distributed tracing with OpenTelemetry and Jaeger — the instrumentation steps overlap; only the exporter endpoint changes.

Trace Backend ComparisonTempoLow index costTraceQL + GrafanaObject storageJaegerRich tag searchMature UIHeavier storageZipkinSimple setupGood for demosLimited at scaleChoose Tempo when Grafana is your ops hubSame Explore UI for metrics logs and tracesExemplars and log links reduce MTTR
Tempo vs Jaeger vs Zipkin — Tempo wins on Grafana-native correlation and storage efficiency for high-volume traces.

How do you deploy and operate Tempo in production?

Tempo scales from a single binary on Ubuntu to a microservices deployment on Kubernetes. Match the mode to your traffic and ops capacity — not to a diagram you saw at a conference.

Single-server deployment

On a VPS running Laravel, MySQL, Redis, and Grafana, run Tempo as a systemd service or Docker container. Mount a dedicated volume for WAL and trace blocks. Retain seven to fourteen days of traces unless compliance requires longer — disk fills faster than teams expect.

I've encountered this during production deployments on shared EC2 hosts. Traces without retention policy consumed the same partition as MySQL binlogs. Set block_retention and monitor disk with your existing Prometheus node exporter.

Kubernetes and object storage

At higher volume, use S3-compatible storage (AWS S3, MinIO, or similar) as Tempo’s backend. Run distributors, ingesters, queriers, and compactors as separate components. The Grafana Tempo documentation publishes Helm charts and sizing guidance.

Wire Prometheus and Grafana monitoring to Tempo itself — ingest lag, compactor errors, and query latency deserve alerts like any other service.

Production Tempo DeploymentOTel CollectorSample 10%Tempo IngesterWAL bufferCompactorBlock mergeS3 Store7d retainGrafana Querier + TraceQLExplore · Dashboards · AlertingAlert on compactor lag and disk usageSame Prometheus stack monitors Tempo health
Production Tempo pipeline — sampling at the collector, block storage with retention, and Grafana for queries and alerts.

Sampling and cost control

Tracing every request at 10k RPS is expensive. Use head sampling in the collector for baseline coverage. Add tail sampling for errors and slow requests — keep all traces above 500 ms or with non-2xx status codes.

Scrub PII in the collector before spans reach Tempo. Payment tokens, national ID numbers, and raw email addresses do not belong in span attributes. I've seen teams ship traces faster than they ship log redaction — fix that in the collector pipeline.

What production pitfalls should you avoid with Tempo?

Tempo is operationally simpler than a full Jaeger plus Elasticsearch cluster. It still has sharp edges.

  • High-cardinality attributes — user IDs on every span explode metrics generator output. Prefer coarse labels.
  • Missing trace context on async jobs — Laravel queue workers need explicit context injection or traces break at the job boundary.
  • No link between logs and traces — without trace ID in log lines, Loki and Tempo stay siloed.
  • 100% sampling in production — start at 5–10% and raise only when storage budget allows.
  • Forgetting compactor monitoring — stuck compactors mean growing WAL and eventual ingest failure.

For ongoing tuning, testing and optimization and support and maintenance contracts often include observability hardening — not because Tempo is exotic, but because teams defer it until the first bad incident.

On client portals with document uploads and payment flows, I treat tracing like backups. You do not skip it because the site is small. You size retention and sampling for the budget you have.

Key Takeaways

  • Tempo stores traces with minimal indexing — pair it with Grafana, Prometheus, and Loki for full observability.
  • Send spans via OpenTelemetry Collector over OTLP; validate trace context propagation before tuning sampling.
  • Query with TraceQL in Grafana Explore; link logs and metrics through trace IDs and exemplars.
  • Choose Tempo over Jaeger when Grafana is already your ops hub and storage cost matters at scale.
  • Set retention, sampling, and PII scrubbing on day one — disk and compliance problems arrive quietly.
  • Alert on Tempo compactor health and ingest lag the same way you monitor application services.

People Also Ask

Does Tempo replace Jaeger entirely?

Not always. Tempo replaces Jaeger as your trace storage backend when you want Grafana-native correlation and lower indexing overhead. Jaeger’s UI still suits teams that rely on deep tag search and already run Jaeger storage. Many migrations keep Jaeger instrumentation and point OTLP export at Tempo instead.

Can Tempo run without Kubernetes?

Yes. The single-binary mode runs on a plain Linux server with local disk or S3-compatible storage. Several stacks I maintain use Docker Compose on Ubuntu alongside PHP-FPM and MySQL — no cluster required for moderate trace volume.

How long should you retain traces in Tempo?

Seven to fourteen days covers most incident response needs. Extend retention when compliance or post-mortem policy requires it — and move blocks to object storage rather than filling local SSD. Adjust block_retention and compactor settings to match.

What is the difference between Tempo and Loki?

Loki stores log lines indexed by labels. Tempo stores distributed trace spans grouped by trace ID. They complement each other in Grafana — logs tell you what was said, traces show you the path and timing across services. Link them with shared trace IDs in log fields.

Ship tracing before the next outage

Tempo: Distributed Tracing with Grafana gives you request-level visibility without running a separate observability silo. Instrument with OpenTelemetry, forward through a collector, store in Tempo, and query in Grafana alongside the metrics and logs you already have. Start on staging, verify propagation through your slowest workflow — payments, webhooks, or queue chains — then roll sampling and retention into production.

Need help wiring Tempo into a Laravel stack or your existing Prometheus setup? Contact us for observability and Linux system administration support. Browse the portfolio for production platforms that depend on reliable monitoring, or read more on API monitoring with Prometheus and Grafana and Alertmanager alerting patterns.

Frequently Asked Questions

Grafana Tempo is Grafana Labs’ open-source trace backend. It accepts spans from OpenTelemetry, Jaeger, Zipkin, and other formats, stores compressed blocks on object storage or local disk, indexes only trace IDs, and lets you query traces in Grafana via TraceQL.

Logs show fragments when a checkout fails, a webhook times out, or a queue job stalls. Tempo stores the full request path as spans so you can see service-to-service timing. Paired with Prometheus metrics and Loki logs in Grafana, you pivot from a latency spike to the exact trace, then to matching log lines — a workflow that beats grep-ing separate log files on a shared EC2 host.

LGTM maps to Grafana’s observability pillars: Prometheus for counters, histograms, and alerts on latency and error rates; Loki for structured and unstructured logs with label-based filtering; Tempo for distributed traces showing service-to-service timing; and Grafana for dashboards, Explore, and correlated drill-down across all three. On stacks I maintain, that correlation saves hours during incidents.

Most setups send OTLP spans to an OpenTelemetry Collector, which batches, filters, and forwards to Tempo. For Laravel, generate trace context in HTTP middleware, create child spans for database queries, cache calls, and payment gateway requests, propagate the traceparent header on internal calls and webhooks, and export via OTLP to the local collector on port 4317. Store the trace ID in log context so Loki aligns with Tempo.

Direct app-to-Tempo works for small installs, but the collector is the better default. It gives you sampling, attribute scrubbing, and multi-backend export without redeploying application code. Run head sampling for baseline coverage and tail sampling to keep slow or error traces — for example above 500 ms or with non-2xx status codes — while controlling storage cost at higher request volumes.

TraceQL filters traces by span attributes, duration, service name, and status — similar in spirit to PromQL for metrics. Run it in Grafana Explore or embed results in dashboards. Useful patterns include filtering by service name and duration threshold, by span name and error status, or by HTTP status code and service. Pair TraceQL with Loki derived fields and Prometheus exemplars so trace IDs link across metrics and logs.

All three accept OpenTelemetry. Tempo indexes trace IDs and stores blocks on object storage, keeping storage cost lower at high volume and integrating natively with Grafana Explore, TraceQL, and exemplars. Jaeger offers rich tag search via attribute indexing but needs more storage infrastructure at scale. Zipkin suits lightweight prototypes with simple tag lookup. If you already run Prometheus and Loki, Tempo is the natural third pillar.

Yes. Tempo’s single-binary mode runs on a plain Linux server with local disk or S3-compatible storage. Several stacks I maintain use Docker Compose on Ubuntu alongside PHP-FPM and MySQL — no cluster required for moderate trace volume.

Seven to fourteen days covers most incident response needs. The article’s sample config sets block_retention to 168 hours. Extend only when compliance or post-mortem policy requires it, and prefer object storage over filling local SSD.

Loki stores log lines indexed by labels. Tempo stores distributed trace spans grouped by trace ID. They complement each other in Grafana — logs tell you what was said, traces show you the path and timing across services. Link them by putting the trace ID in log fields and configuring Loki derived fields so log lines become clickable links into Tempo.

Run Tempo as a systemd service or Docker container alongside your application stack. Use the official single-binary config with OTLP receivers on ports 4317 and 4318, local storage paths for WAL and trace blocks, and block_retention set to match your disk budget. Mount a dedicated volume for trace data, pair Tempo with a collector config that forwards OTLP to tempo:4317, and monitor disk with your existing Prometheus node exporter — traces can fill the same partition as MySQL binlogs if you skip retention policy.

At higher trace volume, use S3-compatible storage such as AWS S3 or MinIO as Tempo’s backend and run distributors, ingesters, queriers, and compactors as separate components. Grafana publishes Helm charts and sizing guidance for this mode. Wire Prometheus and Grafana monitoring to Tempo itself — ingest lag, compactor errors, and query latency deserve alerts like any other production service.

Tempo can generate service graph metrics and span metrics from incoming traces and push them to Prometheus via remote write. Those metrics power dependency maps showing which service calls which and how often errors occur — useful when you have traces but no pre-defined RED metrics for every internal endpoint. Enable the metrics generator in Tempo config, start with default span metrics, and tune cardinality before labelling every custom attribute.

Tracing every request at high RPS is expensive. Start head sampling at 5–10% and raise only when storage budget allows. Add tail sampling to keep all traces above 500 ms or with non-2xx status codes. Scrub PII in the collector before spans reach Tempo — payment tokens, national ID numbers, and raw email addresses do not belong in span attributes. I’ve seen teams ship traces faster than log redaction; fix that in the collector pipeline on day one.

High-cardinality attributes such as user IDs on every span explode metrics generator output — prefer coarse labels. Laravel queue workers need explicit trace context injection or traces break at async job boundaries. Without trace IDs in log lines, Loki and Tempo stay siloed. Avoid 100% sampling in production, and alert on compactor health — stuck compactors mean growing WAL and eventual ingest failure. On client portals with document uploads and payment flows, I treat tracing like backups: size retention and sampling for the budget you have.

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: