Setting Up Grafana Tempo for Distributed Tracing

When metrics show an anomaly and logs record an error, but you can't link them into a single picture, distributed tracing comes to the rescue. We configure Grafana Tempo for your project—from auditing instrumentation to implementation and team training. We deliver a turnkey solution, ensuring reliable operation and ongoing support.

Our competencies:

Frequently Asked Questions

Latest works

  • Development of a web application for FEEDME
    Development of a web application for FEEDME
    1365
  • Development of an online store for the company FURNORO
    Development of an online store for the company FURNORO
    1330
  • B2B Advance company logo design
    B2B Advance company logo design
    776
  • Development of a web application for Enviok
    Development of a web application for Enviok
    1077
  • AIDER company logo development
    AIDER company logo development
    1022
  • CRM development for Chasseurs
    CRM development for Chasseurs
    1113

Metrics show a latency anomaly, logs record an error, but connecting them into a single picture fails. We often face this situation: RPS increased, the 99th percentile dropped to 800 ms, and the cause is a single request that touches 10 microservices. Without tracing, finding that point is a needle in a haystack. Grafana Tempo is a distributed tracing backend that integrates seamlessly with Prometheus and Loki in a unified Grafana interface. We offer a turnkey solution: from instrumentation audit to correlation setup and team training. Request an audit of your system—get an implementation plan in one day. Contact us to discuss the details.

How to Configure Grafana Tempo for Distributed Tracing?

Tempo does not build indexes on trace content. It stores data directly in object storage (S3, GCS), indexing only the Trace ID. This yields radical storage cost savings—up to 70% compared to Jaeger, which requires Elasticsearch or Cassandra. In practice, Tempo costs significantly less: storing 1 TB of traces in S3 costs several times less. For a project with 20 microservices and a load of 5000 spans per second, the storage savings exceed $2000 per month. The downside: attribute search without a dedicated query language is impossible. However, the workflow "found an anomaly in metrics → navigated to a specific trace" covers 90% of diagnostic scenarios. For ad-hoc search, use TraceQL, available since Tempo 2.0.

Tempo vs Jaeger: Storage Cost

Criterion Jaeger (with Elasticsearch) Tempo (S3)
Storage cost (1 TB/month) High Low (up to 90% savings)
Indexing Full on all attributes Only Trace ID
Attribute search Yes Via TraceQL or correlation
Retention Limited by cluster size Nearly unlimited

Savings come from omitting the expensive index. We use tail sampling in the OpenTelemetry Collector to filter noise: all errors and slow requests are saved, others with a 10% probability. This halves stored data volume without losing diagnostic value.

Comparison of Sampling Methods

Parameter Head-based sampling Tail sampling
Sampling decision At application side (before sending) In Collector (after collecting all spans)
Error control Cannot guarantee all errors saved Policies possible (all errors saved)
Resources Low Collector load Requires OTLP receiver and buffering
Flexibility Fixed probability Multiple policies (latency, status_code)

For production environments, we recommend tail sampling—it provides maximum completeness of critical traces.

Instrumenting Applications with OpenTelemetry

Tempo accepts traces via OTLP (recommended), Jaeger, or Zipkin. Example of Python instrumentation:

from opentelemetry import trace
from opentelemetry.exporter.otlp.proto.grpc.trace_exporter import OTLPSpanExporter
from opentelemetry.sdk.trace import TracerProvider
from opentelemetry.sdk.trace.export import BatchSpanProcessor

provider = TracerProvider()
exporter = OTLPSpanExporter(endpoint="http://otel-collector:4317", insecure=True)
provider.add_span_processor(BatchSpanProcessor(exporter))
trace.set_tracer_provider(provider)
tracer = trace.get_tracer(__name__)

with tracer.start_as_current_span("process_order") as span:
    span.set_attribute("order.id", order_id)
    result = process(order_id)

Traces are sent to the OpenTelemetry Collector, which performs sampling and forwards to Tempo. We recommend using OpenTelemetry as the standard for instrumentation.

OpenTelemetry Collector: Central Hub

receivers:
  otlp:
    protocols:
      grpc:
        endpoint: 0.0.0.0:4317
      http:
        endpoint: 0.0.0.0:4318
  jaeger:
    protocols:
      grpc:
        endpoint: 0.0.0.0:14250
processors:
  batch:
    timeout: 5s
    send_batch_size: 1024
  tail_sampling:
    decision_wait: 10s
    policies:
      - name: errors-policy
        type: status_code
        status_code: {status_codes: [ERROR]}
      - name: slow-policy
        type: latency
        latency: {threshold_ms: 1000}
      - name: probabilistic
        type: probabilistic
        probabilistic: {sampling_percentage: 10}
exporters:
  otlp/tempo:
    endpoint: http://tempo:4317
    tls:
      insecure: true
service:
  pipelines:
    traces:
      receivers: [otlp, jaeger]
      processors: [batch, tail_sampling]
      exporters: [otlp/tempo]

Deploying Tempo

Deploying Tempo in Docker Compose
---
services:
  tempo:
    image: grafana/tempo:2.4.0
    command: ["-config.file=/etc/tempo.yaml"]
    volumes:
      - ./tempo.yaml:/etc/tempo.yaml
      - tempo-data:/tmp/tempo
    ports:
      - "3200:3200"
      - "4317:4317"
---
server:
  http_listen_port: 3200
distributor:
  receivers:
    otlp:
      protocols:
        grpc:
        http:
storage:
  trace:
    backend: local # for development; in production: s3
    local:
      path: /tmp/tempo/blocks
compactor:
  compaction:
    block_retention: 336h
metrics_generator:
  registry:
    external_labels:
      source: tempo
      cluster: docker-compose
  storage:
    path: /tmp/tempo/generator/wal
  remote_write:
    - url: http://prometheus:9090/api/v1/write
      send_exemplars: true

metrics_generator automatically creates Prometheus metrics from traces—service graphs and span metrics. This eliminates the need for manual HTTP request instrumentation.

Integrating Grafana Tempo with Prometheus and Loki

Configure the datasource in Grafana: set type Tempo, URL http://tempo:3200, and in jsonData enable tracesToLogsV2 (UID of Loki datasource) and serviceMap (UID of Prometheus). After that, in Explore, clicking a metric reveals a "View related traces" button, and from a trace you can navigate to logs.

TraceQL: Searching Traces by Attributes

TraceQL is a query language for Tempo, similar to PromQL. Examples: { span.http.status_code = 500 && duration > 2s } { resource.service.name = "order-service" && status = error }

{ .user.id = "12345" }

TraceQL addresses Tempo's main limitation—now you can search traces not only by ID but also by arbitrary attributes. The official TraceQL documentation contains a full syntax description.

How to Implement Distributed Tracing with Grafana Tempo?

  1. Instrumentation audit: check if OTel SDK, Jaeger, Zipkin are present. Select a sampling strategy.
  2. Deploy Tempo + Collector: deploy in your environment, configure object storage (S3, GCS).
  3. Configure Grafana datasource: set up correlations with Prometheus and Loki.
  4. Enable metrics_generator: obtain automatic metrics and service graphs.
  5. Documentation and training: provide instructions for adding tracing to new services.

What the Work Includes

  • Deploy Tempo and Collector with optimal sampling configuration.
  • Integrate with existing monitoring stack (Prometheus, Loki).
  • Set up alerts based on traces (e.g., SLO drops).
  • Documentation and hands-on team training.
  • Guarantee of correct correlation between metrics, traces, and logs.

Timeline: 2 to 5 days depending on infrastructure complexity. We are a team with 5+ years of observability experience, having implemented distributed tracing on over 30 projects. We will evaluate your project within one business day. Get a consultation to discuss the details. Contact us, and we will find the optimal solution.