Observabilidade: logs, métricas e traces

1. Fundamentos da Observabilidade em Sistemas Distribuídos

1.1. Definição e objetivos: monitoramento reativo vs. observabilidade preditiva

Observabilidade é a capacidade de inferir o estado interno de um sistema a partir de seus dados externos. Diferentemente do monitoramento reativo, que apenas dispara alertas quando métricas predefinidas são violadas, a observabilidade preditiva permite explorar comportamentos inesperados sem necessidade de instrumentação prévia para cada cenário.

Em sistemas distribuídos, onde falhas em cascata são comuns, a observabilidade não é um luxo — é um requisito arquitetural. Sem ela, uma requisição que atravessa 15 microsserviços pode falhar silenciosamente, deixando engenheiros sem pistas sobre a causa raiz.

1.2. Os três pilares: logs, métricas e traces – papéis e interdependências

Os três pilares da observabilidade atuam de forma complementar:

  • Logs: registros textuais de eventos discretos. Contam o que aconteceu.
  • Métricas: agregados numéricos ao longo do tempo. Mostram tendências e padrões.
  • Traces: rastreamento de requisições através de serviços. Revelam caminhos e tempos de execução.

Individualmente, cada pilar é limitado. Juntos, formam um sistema coeso: métricas acionam alertas, logs fornecem contexto, e traces revelam a topologia da falha.

1.3. Contexto arquitetural: microserviços, eventos assíncronos e falhas em cadeia

Arquiteturas modernas introduzem complexidade não-linear. Um timeout em um serviço de pagamento pode ser causado por contenção em um banco de dados de catálogo, a três saltos de distância. A observabilidade precisa atravessar fronteiras de serviço, protocolos e times.

2. Logs: Registro Estruturado e Contextual

2.1. Logs estruturados (JSON) vs. logs não estruturados – impacto em consultas e correlação

Logs não estruturados são difíceis de consultar em escala. Compare:

2025-01-15 10:30:45 ERRO falha ao processar pedido 12345

Com um log estruturado:

{"timestamp":"2025-01-15T10:30:45Z","level":"ERROR","service":"payment","trace_id":"abc123","order_id":12345,"message":"falha ao processar pedido","error":"timeout"}

O formato estruturado permite consultas como service=payment AND level=ERROR e correlação direta com traces via trace_id.

2.2. Níveis de severidade e boas práticas: evitar ruído, usar IDs de correlação

Boas práticas incluem:
- Usar níveis padronizados: DEBUG, INFO, WARN, ERROR, FATAL
- Incluir sempre trace_id, service e timestamp
- Evitar logs em loops de alta frequência
- Logar em pontos de entrada/saída de serviços e em decisões críticas

2.3. Centralização de logs com agentes (Fluentd, Logstash) e armazenamento de longa duração

Agentes como Fluentd coletam logs localmente e os enviam para armazenamento centralizado (Elasticsearch, Loki). O pipeline típico:

Aplicação → Fluentd (buffer) → Kafka (fila) → Logstash (transformação) → Elasticsearch → Kibana

3. Métricas: Sinais Quantitativos de Saúde e Desempenho

3.1. Tipos de métricas: contadores, gauges, histogramas e sumários

  • Contador: valor monotônico crescente (ex: requisições totais)
  • Gauge: valor que sobe e desce (ex: memória usada)
  • Histograma: distribuição de valores (ex: latência em buckets)
  • Sumário: quantis calculados no cliente (ex: p99)

3.2. Métricas RED (Rate, Errors, Duration) e USE (Utilization, Saturation, Errors)

Dois frameworks consolidados:

RED (para serviços):
- Rate: requisições por segundo
- Errors: taxa de falhas
- Duration: latência (p50, p95, p99)

USE (para recursos):
- Utilization: percentual de uso
- Saturation: fila de espera
- Errors: contagem de falhas

3.3. Coleta e agregação: pull (Prometheus) vs. push (StatsD, Telegraf) – trade-offs arquiteturais

Modelo Vantagens Desvantagens
Pull (Prometheus) Descoberta automática, menos carga em picos Requer configuração de rede, difícil em ambientes efêmeros
Push (StatsD) Simples, funciona em qualquer topologia Pode perder dados se coletor cair, requer autenticação

4. Traces: Rastreamento Distribuído de Requisições

4.1. Conceitos: spans, trace context, parent-child relationships

Um trace representa uma requisição completa. Cada span é uma unidade de trabalho dentro do trace. Spans formam uma árvore com relações pai-filho.

Trace ID: abc123
├── Span A (frontend) — 200ms
│   ├── Span B (auth) — 50ms
│   └── Span C (payment) — 120ms
│       └── Span D (database) — 80ms

4.2. Propagação de contexto: headers W3C Trace Context e baggage

O padrão W3C Trace Context define headers traceparent e tracestate para propagar contexto entre serviços. Cada serviço extrai o contexto, cria um span filho e repassa adiante.

4.3. Amostragem (sampling) – head-based vs. tail-based, custo vs. cobertura

  • Head-based: decide amostrar no início da requisição. Simples, mas pode perder eventos raros.
  • Tail-based: coleta todos os spans, decide após análise. Mais preciso, porém mais caro.

Estratégia comum: amostrar 100% de erros e 1-5% de requisições bem-sucedidas.

5. Correlação entre os Três Pilares na Prática

5.1. Unificação via ID de correlação: trace ID em logs e métricas

A chave para correlação é propagar o trace_id em todos os sinais:

Log: {"trace_id":"abc123","service":"payment","level":"ERROR","message":"timeout"}
Métrica: http_requests_duration_seconds{trace_id="abc123",status="500"} 2.3
Trace: Span C (payment) — erro com duração 2.3s

5.2. Dashboards e alertas: métricas para alarmes, traces para diagnóstico, logs para detalhamento

Fluxo típico de debugging:
1. Alerta no Grafana: latência p99 > 2s
2. Abre trace correspondente no Jaeger: descobre que o gargalo está no serviço de pagamento
3. Consulta logs daquele trace_id: encontra "timeout connecting to database"

5.3. Exemplo prático: debugging de latência alta combinando os três sinais

Suponha que métricas mostrem aumento repentino no p95 de latência:

# Métrica (PromQL):
histogram_quantile(0.95, rate(http_request_duration_seconds_bucket[5m]))

Ao inspecionar traces no Jaeger, identificamos que o serviço inventory está demorando. Logs desse trace revelam:

{"trace_id":"xyz789","service":"inventory","level":"WARN","message":"cache miss for product 5001","duration_ms":450}

Conclusão: o cache Redis estava expirado, causando consultas lentas ao banco.

6. Desafios Arquiteturais e Padrões de Implementação

6.1. Sobrecarga de instrumentação e impacto no desempenho

Instrumentação excessiva pode degradar performance. Soluções:
- Usar sampling inteligente
- Instrumentar assincronamente (bibliotecas não-bloqueantes)
- Separar pipeline de observabilidade do tráfego principal

6.2. Consistência eventual e ordenação de eventos em pipelines de observabilidade

Logs e traces chegam fora de ordem. Estratégias:
- Usar timestamps nanossegundos
- Implementar buffers com ordenação por partição (ex: Kafka por trace_id)
- Aceitar que a visualização pode ter atraso de segundos

6.3. Custos de armazenamento e estratégias de retenção (hot/warm/cold tiers)

Tier Armazenamento Retenção Exemplo
Hot SSD, alta performance 7 dias Elasticsearch rápido
Warm HDD, performance média 30 dias Elasticsearch com shards reduzidos
Cold S3/Blob Storage 1 ano Dados compactados, consulta sob demanda

7. Ferramentas e Ecossistema Open Source

7.1. OpenTelemetry como camada de instrumentação padronizada

OpenTelemetry (OTel) unifica a coleta de logs, métricas e traces em um único SDK. Suporta múltiplas linguagens e exporta para diversos backends.

# Configuração OTel (exemplo YAML):
receivers:
  otlp:
    protocols:
      grpc:
        endpoint: 0.0.0.0:4317
exporters:
  prometheus:
    endpoint: 0.0.0.0:8889
  jaeger:
    endpoint: jaeger:14250
service:
  pipelines:
    traces:
      receivers: [otlp]
      exporters: [jaeger]
    metrics:
      receivers: [otlp]
      exporters: [prometheus]

7.2. Stack comum: Prometheus + Grafana (métricas), Elasticsearch + Kibana (logs), Jaeger/Tempo (traces)

  • Métricas: Prometheus coleta, Grafana visualiza e alerta
  • Logs: Elasticsearch armazena, Kibana consulta
  • Traces: Jaeger ou Grafana Tempo para rastreamento distribuído

7.3. Integração com ADRs: decisões arquiteturais sobre escolha de ferramentas e formatos

Documentar decisões como ADRs (Architecture Decision Records) é crucial:

ADR-007: Adoção do OpenTelemetry
Contexto: Times usavam SDKs diferentes (Zipkin, OpenTracing, OpenCensus)
Decisão: Padronizar em OpenTelemetry para logs, métricas e traces
Consequências: Migração de 3 meses, redução de custos de manutenção em 40%

Referências