Como implementar observabilidade com OpenTelemetry

1. Fundamentos do OpenTelemetry e a Tríade da Observabilidade

1.1. O que é OpenTelemetry

OpenTelemetry (OTel) é um conjunto de APIs, SDKs e ferramentas de código aberto para gerar, coletar e exportar dados de telemetria. Criado a partir da fusão do OpenTracing e OpenCensus em 2019, sob governança da CNCF (Cloud Native Computing Foundation), tornou-se o padrão da indústria para observabilidade. Sua principal vantagem é fornecer uma especificação unificada que evita vendor lock-in.

1.2. Os três pilares unificados

O OpenTelemetry unifica os três pilares clássicos da observabilidade:
- Tracing distribuído: rastreamento de requisições através de microsserviços
- Métricas: dados numéricos agregados (contadores, histogramas)
- Logs: registros estruturados com contexto

A unificação permite correlação entre esses sinais, algo que ferramentas isoladas não oferecem.

1.3. Arquitetura geral

A arquitetura do OpenTelemetry segue este fluxo:

Aplicação → SDK OTel → Collector → Backends (Prometheus, Jaeger, Loki)

Componentes principais:
- SDKs: bibliotecas que instrumentam a aplicação
- Exporters: enviam dados para destinos específicos
- Collector: pipeline central de processamento e roteamento

2. Instrumentação Automática vs. Manual

2.1. Instrumentação automática

Para linguagens com suporte a agentes, a instrumentação automática é a forma mais rápida de começar. Exemplo com Java:

# Configuração do agente Java OTel
export OTEL_SERVICE_NAME=meu-servico
export OTEL_EXPORTER_OTLP_ENDPOINT=http://collector:4318
java -javaagent:opentelemetry-javaagent.jar -jar minha-app.jar

Para Python:

pip install opentelemetry-distro
opentelemetry-bootstrap -a install
export OTEL_PYTHON_LOG_CORRELATION=true
python minha_app.py

2.2. Instrumentação manual

Para cenários que exigem controle granular, a instrumentação manual permite criar spans customizados:

from opentelemetry import trace
tracer = trace.get_tracer(__name__)

with tracer.start_as_current_span("processar_pagamento") as span:
    span.set_attribute("valor", 150.00)
    span.add_event("inicio_processamento")
    # lógica de negócio
    span.set_status(trace.StatusCode.OK)

2.3. Boas práticas para evitar overhead

  • Use sampling adaptativo: colete 100% dos traces de erro, mas apenas 10% dos bem-sucedidos
  • Evite spans excessivamente granulares (ex: um span por linha de código)
  • Configure timeouts nos exporters para não bloquear a aplicação

3. Configuração do OpenTelemetry Collector

3.1. Instalação básica

# docker-compose.yml
services:
  otel-collector:
    image: otel/opentelemetry-collector-contrib:latest
    command: ["--config=/etc/otel-collector-config.yaml"]
    volumes:
      - ./otel-collector-config.yaml:/etc/otel-collector-config.yaml
    ports:
      - "4317:4317"  # OTLP gRPC
      - "4318:4318"  # OTLP HTTP

3.2. Configuração do pipeline

# otel-collector-config.yaml
receivers:
  otlp:
    protocols:
      grpc:
        endpoint: 0.0.0.0:4317
      http:
        endpoint: 0.0.0.0:4318

processors:
  batch:
    timeout: 1s
    send_batch_size: 1024
  memory_limiter:
    check_interval: 1s
    limit_mib: 512

exporters:
  prometheus:
    endpoint: "0.0.0.0:8889"
  jaeger:
    endpoint: jaeger:14250
    tls:
      insecure: true
  loki:
    endpoint: http://loki:3100/loki/api/v1/push

service:
  pipelines:
    traces:
      receivers: [otlp]
      processors: [memory_limiter, batch]
      exporters: [jaeger]
    metrics:
      receivers: [otlp]
      processors: [memory_limiter, batch]
      exporters: [prometheus]
    logs:
      receivers: [otlp]
      processors: [memory_limiter, batch]
      exporters: [loki]

3.3. Processamento avançado

Sampling baseado em tail para traces longos:

processors:
  tail_sampling:
    decision_wait: 30s
    policies:
      - name: error-policy
        type: status_code
        status_code: ERROR
      - name: slow-policy
        type: latency
        latency_threshold_ms: 500

4. Rastreamento Distribuído (Tracing)

4.1. Criação de spans com contexto

import { trace, context } from '@opentelemetry/api';

const tracer = trace.getTracer('meu-servico');

function handler(req, res) {
  const span = tracer.startSpan('GET /api/users');
  span.setAttribute('user.id', req.userId);

  context.with(trace.setSpan(context.active(), span), () => {
    // código que propaga contexto automaticamente
    span.end();
  });
}

4.2. Propagação W3C Trace Context

O OpenTelemetry implementa o padrão W3C Trace-Context, que adiciona headers HTTP automaticamente:

# Headers propagados
traceparent: 00-0af7651916cd43dd8448eb211c80319c-b7ad6b7169203331-01
tracestate: congo=t61rcWkgMzE

4.3. Depuração com flame graphs

Use o Jaeger para visualizar trace waterfalls:

# Consulta no Jaeger UI
service=meu-servico AND http.status_code>=500

5. Métricas Customizadas e Alertas

5.1. Criação de métricas com API OTel

from opentelemetry import metrics

meter = metrics.get_meter(__name__)
request_counter = meter.create_counter(
    "requests_total",
    description="Total de requisições",
    unit="1"
)

def process_request():
    request_counter.add(1, {"endpoint": "/api/users"})

5.2. Exportação para Prometheus

# Métricas expostas pelo Collector
# HELP requests_total Total de requisições
# TYPE requests_total counter
requests_total{endpoint="/api/users"} 42

5.3. Transformando em alertas

# Regra Prometheus
groups:
  - name: otel-alerts
    rules:
      - alert: HighErrorRate
        expr: rate(http_requests_total{status="500"}[5m]) > 0.05
        for: 2m
        labels:
          severity: critical

6. Logs Estruturados e Correlação

6.1. Configuração de logging estruturado

# app.py
import logging
from opentelemetry import trace

logging.basicConfig(
    level=logging.INFO,
    format='%(asctime)s [%(trace_id)s] %(message)s'
)

span = trace.get_current_span()
logging.info("Pedido processado", extra={
    "trace_id": format(span.get_span_context().trace_id, '032x')
})

6.2. Pipeline de logs para Loki

# Configuração do Collector para logs
exporters:
  loki:
    endpoint: http://loki:3100/loki/api/v1/push
    labels:
      attributes:
        - service.name
        - trace_id

7. Operação e Troubleshooting

7.1. Monitoramento do Collector

# Métricas do próprio Collector
otelcol_process_uptime_seconds
otelcol_exporter_sent_spans
otelcol_receiver_accepted_spans

7.2. Debugging comum

Problemas frequentes:
- Spans perdidos: verifique se o batch processor tem timeout adequado
- Alta latência: use memory_limiter para evitar OOM
- Erros de exportação: configure retry e circuit breaker

7.3. Versionamento de configurações

# Mantenha versões do config em git
git tag v1.0.0-otel-config
git checkout v1.0.0-otel-config -- otel-collector-config.yaml

8. Evolução Contínua: Do MVP ao Padrão Corporativo

8.1. Roadmap gradual

  1. Fase 1 (MVP): Tracing automático em 2-3 serviços críticos
  2. Fase 2: Adicionar métricas customizadas e dashboards
  3. Fase 3: Logs estruturados com correlação
  4. Fase 4: Sampling adaptativo e governança de custos

8.2. Controle de custos

# Sampling adaptativo no Collector
processors:
  probabilistic_sampler:
    hash_seed: 42
    sampling_percentage: 10

8.3. SLOs e dashboards unificados

# Exemplo de SLI com métricas OTel
sli: rate(http_requests_total{status=~"2.."}[5m]) / rate(http_requests_total[5m])
slo: 99.9%

Referências