Como correlacionar logs, métricas e traces com exemplars no Grafana

1. Fundamentos da Correlação entre Telemetrias

A observabilidade moderna enfrenta um desafio estrutural: métricas, logs e traces frequentemente vivem em silos separados. Um engenheiro que identifica um pico de latência em um gráfico precisa manualmente buscar logs e depois tentar encontrar o trace correspondente. Esse processo fragmentado consome tempo e dificulta a identificação da causa raiz.

Os exemplars surgem como a ponte entre métricas e traces. Um exemplar é uma amostra representativa de uma requisição específica que gerou um dado ponto de métrica. Ele contém o trace_id e metadados adicionais, permitindo que você clique em um ponto do gráfico e abra diretamente o trace correspondente no Grafana Tempo.

O ecossistema Grafana — composto por Loki (logs), Tempo (traces) e Mimir/Prometheus (métricas) — oferece uma plataforma unificada para essa correlação, concretizando a promessa do observability 2.0: dados interconectados com navegação fluida entre telemetrias.

2. Configuração do Stack para Suporte a Exemplars

Para habilitar exemplars, o primeiro passo é configurar o Prometheus ou VictoriaMetrics para coletar e armazenar essas amostras. No arquivo prometheus.yml, adicione a configuração de exemplars no scrape_config:

scrape_configs:
  - job_name: 'meu-servico'
    static_configs:
      - targets: ['localhost:9090']
    exemplars:
      - source_labels: ['trace_id']
        target_label: 'trace_id'
        max_exemplars: 100

Em paralelo, configure o Grafana Tempo como backend de tracing. No docker-compose.yml, defina:

services:
  tempo:
    image: grafana/tempo:latest
    command: ["-config.file=/etc/tempo.yaml"]
    volumes:
      - ./tempo.yaml:/etc/tempo.yaml
    ports:
      - "3200:3200"   # tempo query
      - "4317:4317"   # otlp grpc
      - "4318:4318"   # otlp http

Para o Loki, ajuste a coleta de logs para incluir trace_id e span_id:

scrape_configs:
  - job_name: 'logs'
    pipeline_stages:
      - regex:
          expression: 'trace_id=(?P<trace_id>\w+)'
      - labels:
          trace_id:

3. Ingestão e Propagação de IDs de Contexto

A propagação correta do trace_id entre serviços é essencial. Com OpenTelemetry, você pode configurar um exporter OTLP que envia traces e métricas com exemplars simultaneamente:

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()
processor = BatchSpanProcessor(OTLPSpanExporter(endpoint="http://tempo:4317"))
provider.add_span_processor(processor)
trace.set_tracer_provider(provider)

tracer = trace.get_tracer(__name__)

with tracer.start_as_current_span("requisicao-principal") as span:
    span.set_attribute("http.status_code", 200)
    # O trace_id é automaticamente propagado para logs e métricas
    logging.info("Requisição processada", extra={"trace_id": span.get_span_context().trace_id})

Para instrumentação automática em Node.js:

const { NodeTracerProvider } = require('@opentelemetry/sdk-trace-node');
const { OTLPTraceExporter } = require('@opentelemetry/exporter-trace-otlp-grpc');
const { BatchSpanProcessor } = require('@opentelemetry/sdk-trace-base');

const provider = new NodeTracerProvider();
const exporter = new OTLPTraceExporter({ url: 'http://tempo:4317' });
provider.addSpanProcessor(new BatchSpanProcessor(exporter));
provider.register();

4. Consultas e Visualização com Exemplars no Grafana

No Grafana, ao criar um painel de métricas com PromQL, os exemplars aparecem como pontos destacáveis no gráfico. Configure a consulta:

rate(http_requests_total{job="meu-servico"}[5m])

No painel, vá em Query options e ative Exemplars. Defina o data source do Tempo como destino do drill-down:

Configuração do painel:
  - Data source: Prometheus
  - Query: rate(http_requests_total[5m])
  - Exemplars:
      - Internal link: Tempo
      - Trace ID label: trace_id

Ao passar o mouse sobre um ponto do gráfico, você verá os exemplars disponíveis. Clicando em um, o Grafana abre automaticamente o trace completo no Tempo, mostrando cada span e sua duração.

5. Correlação Bidirecional: Logs → Métricas → Traces

Para logs, configure derived fields no Loki. No datasource do Loki no Grafana:

Derived fields:
  - Name: Trace ID
  - Regex: trace_id=(\w+)
  - URL/query: ${__value.raw}
  - Data source: Tempo

Isso transforma automaticamente qualquer trace_id em log em um link clicável para o trace. Crie um dashboard unificado:

Dashboard "Observabilidade Unificada":
  - Painel 1: Métrica de latência p99 (Prometheus)
  - Painel 2: Logs recentes (Loki) com derived fields
  - Painel 3: Traces recentes (Tempo)
  - Variável de template: $service

Com isso, você pode navegar de um pico de latência no gráfico → clicar no exemplar → ver o trace → e acessar os logs específicos daquele trace.

6. Estratégias Avançadas e Otimização de Custos

Para reduzir volume sem perder representatividade, implemente sampling adaptativo:

# Configuração do Tempo para sampling baseado em cardinalidade
overrides:
  'meu-servico':
    max_bytes_per_trace: 50000
    ingestion_rate_limit_bytes: 1000000
    ingestion_burst_size_bytes: 2000000

Use exemplars apenas em métricas críticas:

# No Prometheus, exemplars apenas para métricas de SLO
exemplars:
  - source_labels: ['trace_id']
    target_label: 'trace_id'
    max_exemplars: 20
  # Métricas de baixa cardinalidade não precisam de exemplars

Monitore a taxa de exemplars perdidos com:

prometheus_tsdb_exemplar_exemplars_appended_total
prometheus_tsdb_exemplar_out_of_bounds_exemplars_total

7. Troubleshooting e Depuração com Exemplars

Exemplo prático: você observa um pico de latência p99 às 14:32. No painel de métricas:

histogram_quantile(0.99, rate(http_request_duration_seconds_bucket[5m]))

Clique no ponto do pico → selecione o exemplar com maior duração → o Tempo abre o trace. Você identifica que o span consulta-banco levou 4.2 segundos. Acesse os logs do Loki filtrados por trace_id:

{trace_id="abc123"} |= "SELECT * FROM usuarios"

O log mostra deadlock detected. Correlação completa em segundos.

Boas práticas para evitar ruído:

  • Limite exemplars para 1 a cada 1000 requisições em séries de alta cardinalidade
  • Use max_exemplars por série temporal (padrão: 100)
  • Configure exemplars_interval para evitar amostras muito próximas

8. Próximos Passos e Integrações Futuras

A integração com Grafana OnCall permite que alertas disparados por métricas incluam links diretos para traces:

# Configuração de alerta com link para trace
- alert: HighLatency
  expr: histogram_quantile(0.99, ...) > 2
  annotations:
    summary: "Latência alta detectada"
    runbook_url: "https://runbook.exemplo.com"
    trace_link: "http://grafana:3000/explore?orgId=1&left=%7B%22datasource%22:%22Tempo%22,%22queries%22:%5B%7B%22queryType%22:%22traceId%22,%22query%22:%22${trace_id}%22%7D%5D%7D"

Para SLO burn rate, crie painéis que mostram exemplars dos traces que contribuíram para o erro:

slo:http_errors:ratio_rate5m{job="meu-servico"} > 0.01

O roadmap do Grafana inclui correlação automática com machine learning, onde anomalias em métricas serão automaticamente vinculadas a traces suspeitos, reduzindo ainda mais o tempo de diagnóstico.


Referências