Índice
- O que é observabilidade (e por que você deveria se importar)
- Monitoramento vs. observabilidade: a diferença que importa
- Os três pilares: logs, métricas e traces
- OpenTelemetry: o que é e por que existe
- Tracing explicado com analogias
- Métricas explicadas com analogias
- Logs explicados com analogias
- Mão na massa: instrumentando uma API FastAPI
- Visualizando traces no Jaeger
- Coletando métricas com Prometheus
- Erros comuns (e como evitá-los)
- Conclusão e próximos passos
O que é observabilidade (e por que você deveria se importar) {#o-que-e}
Imagine que você dirige um carro. No painel, há alguns indicadores: medidor de combustível, conta-giros, luz de temperatura do motor e a luzinha do óleo. Cada um desses indicadores te diz algo sobre o estado do carro agora. Mas se o carro enguiçar no meio da estrada, só olhar o painel não resolve: você precisa abrir o capô, entender o que aconteceu e por que aconteceu. É aí que entra a diferença entre saber que algo deu errado e entender por que deu errado.
Observabilidade é uma palavra emprestada da engenharia de controle (uma área da matemática que estuda sistemas dinâmicos). Em termos simples: um sistema é observável quando, apenas olhando para as informações que ele emite para fora, você consegue deduzir o estado interno dele sem precisar "abrir a caixa". Em software, isso significa: sua aplicação te dá dados suficientes para que você descubra por que algo deu errado, não apenas que deu errado.
Analogia rápida: Monitorar é como ter um alarme de incêndio — ele avisa quando o fogo já começou. Observabilidade é como ter sensores de fumaça, temperatura, umidade e um histórico de manutenção do prédio inteiros — você entende não só que pegou fogo, mas onde, por quê e como evitar da próxima vez.
Em sistemas modernos, especialmente quando usamos microserviços, a observabilidade deixa de ser um "nice to have" e vira necessidade. Uma simples requisição de "finalizar compra" pode passar por 6, 10 ou 20 serviços diferentes antes de retornar para o usuário. Quando algo demora 3 segundos a mais, você precisa saber em qual daquelas 20 etapas o tempo foi gasto.
Por que isso custa caro quando ignorado
Vamos ser diretos sobre dinheiro. Um incidente de indisponibilidade em plataformas de médio porte custa, em média, entre R$ 15 mil e R$ 100 mil por hora dependendo do segmento — considerando receita perdida, equipe trabalhando em overtime e dano reputacional. Quando sua equipe leva 4 horas para descobrir que o problema era uma query lenta no banco, em vez de 15 minutos, a diferença é justamente o que a observabilidade te dá: tempo de detecção menor (MTTD, Mean Time To Detect) e tempo de resolução menor (MTTR, Mean Time To Recovery).
MTTD (Mean Time To Detect): tempo médio entre o problema acontecer e você descobrir que aconteceu. MTTR (Mean Time To Recovery): tempo médio entre o problema acontecer e o serviço voltar a funcionar normalmente.
Monitoramento vs. observabilidade: a diferença que importa {#monitoramento-vs}
Esses dois termos são frequentemente usados como sinônimos, mas existe uma diferença prática importante que vai guiar como você implementa as coisas.
Monitoramento é o conjunto de ferramentas e práticas que te dizem se algo está funcionando ou não, baseado em perguntas que você já sabia que ia fazer. "A API está respondendo?", "O uso de CPU passou de 80%?", "Quantos erros 500 aconteceram na última hora?" — tudo isso é monitoramento. Você pré-configura alertas e dashboards para responder a essas perguntas.
Observabilidade é mais ampla: é a capacidade de fazer perguntas que você não sabia que ia precisar fazer. "Por que o checkout ficou lento só para usuários do Brasil usando o navegador Safari entre 14h e 15h de ontem?" — essa é uma pergunta que nenhum dashboard pré-configurado vai responder sozinho. Você só consegue respondê-la se tiver coletado dados ricos o suficiente (traces, métricas com dimensões e logs estruturados) durante o incidente.
| Aspecto | Monitoramento | Observabilidade |
|---|---|---|
| Pergunta típica | "Está funcionando?" | "Por que não está funcionando?" |
| Quando configurar | Antes do problema | Preparado para o desconhecido |
| Tipo de dado | Métricas pré-definidas | Traces + métricas + logs ricos |
| Custo de setup | Baixo/médio | Médio/alto |
| Resolve incidentes novos? | Não muito | Sim, é o objetivo principal |
| Exemplo de ferramenta | Alerta de CPU no Grafana | Trace no Jaeger mostrando 200ms em chamada ao Redis |
A boa notícia: você não precisa escolher um ou outro. Observabilidade inclui monitoramento. Você implementa observabilidade e, como subproduto, ganha monitoramento de graça.
Os três pilares: logs, métricas e traces {#tres-pilares}
Toda a literatura de observabilidade gira em torno de três tipos de dados, frequentemente chamados de "os três pilares". Vamos apresentá-los aqui de forma breve e aprofundar cada um nas seções seguintes.
- Logs: registros de eventos discretos que aconteceram na sua aplicação. "Usuário X fez login às 14:32", "Falha ao conectar no banco: timeout após 5s".
- Métricas: números agregados ao longo do tempo. "Taxa de requisições por segundo", "Uso de memória agora", "Quantidade de erros na última hora".
- Traces: o caminho completo de uma única requisição atravessando todos os serviços pelos quais ela passou, com a duração de cada etapa.
Cada um resolve um problema diferente. Nenhum resolve tudo sozinho.
Tabela comparativa dos três pilares
| Pilar | Responde a... | Granularidade | Custo de armazenar | Quando usar |
|---|---|---|---|---|
| Logs | "O que aconteceu nesse momento exato?" | Alta (1 evento por linha) | Alto (cresce rápido) | Debugging profundo, auditoria |
| Métricas | "Qual é a tendência ao longo do tempo?" | Baixa (números agregados) | Baixo (só números) | Dashboards, alertas, tendências |
| Traces | "Por onde essa requisição passou e onde demorou?" | Média (1 trace por requisição) | Médio | Diagnóstico de latência, depuração entre serviços |
Dica prática: se você está começando do zero, comece por métricas (mais baratas e já resolvem alertas), depois traces (resolvem depuração de latência) e por último logs estruturados (resolvem auditoria e debugging detalhado). Inverter essa ordem é um erro comum — logs sem traces e métricas geram muito ruído e pouco sinal.
OpenTelemetry: o que é e por que existe {#opentelemetry}
OpenTelemetry (frequentemente abreviado como OTel) é um projeto open source mantido pela CNCF (Cloud Native Computing Foundation, a mesma fundação que cuida do Kubernetes). Ele nasceu em 2019 da fusão de dois projetos anteriores — OpenTracing e OpenCensus — que faziam coisas parecidas e competiam entre si. A comunidade percebeu que fazer dois padrões concorrentes não ajudava ninguém, então unificou tudo em um só.
O problema que o OpenTelemetry resolve
Antes do OTel, se você quisesse coletar traces da sua aplicação, precisava instalar o SDK (Software Development Kit, o pacote de bibliotecas) de uma ferramenta específica — Jaeger, Zipkin, Datadog, New Relic, etc. Cada uma com sua própria API. Se depois você quisesse trocar de Jaeger para Datadog, precisava reescrever toda a instrumentação do código. Era caro, chato e prendia você a um fornecedor.
Analogia: é como comprar uma TV que só funciona com uma marca de fone de ouvido. Se você quiser trocar o fone, tem que trocar a TV junto. O OpenTelemetry é como uma TV com entrada Bluetooth padrão — funciona com qualquer fone, de qualquer marca.
O OTel resolve isso fornecendo uma única API padrão para instrumentar sua aplicação. Você escreve a instrumentação uma vez e decide depois para onde enviar os dados (que chamamos de backend, o destino final dos dados) — Jaeger, Prometheus, Datadog, Honeycomb, Tempo, o que for. A troca de backend vira uma mudança de configuração, não de código.
Como o OpenTelemetry é organizado
O projeto se divide em três partes principais:
- API: as interfaces e padrões que você importa no código (spans, métricas, logs). É o que você "chama" dentro da sua aplicação.
- SDK: a implementação concreta da API. É o motor que de fato coleta, processa e exporta os dados para algum backend.
- Collector: um processo separado (um "agente") que recebe dados de várias aplicações, processa, filtra e envia para um ou mais backends. Imagine como uma central de triagem dos Correios — todo mundo entrega lá e ele distribui para os destinos certos.
[App A] ─┐
[App B] ─┼─→ [OpenTelemetry Collector] ─┬─→ [Jaeger] (traces)
[App C] ─┘ ├─→ [Prometheus] (métricas)
└─→ [Loki] (logs)
Por que usar OpenTelemetry em vez de ferramentas diretamente?
| Motivo | Sem OTel | Com OTel |
|---|---|---|
| Trocar de backend | Reescrever código | Mudar config |
| Padronizar times | Cada time escolhe uma ferramenta | Todos falam a mesma língua |
| Vendor lock-in | Alto (preso a um fornecedor) | Baixo (padrão aberto) |
| Comunidade | Fragmentada | Unificada (CNCF) |
| Suporte a linguagens | Varia por ferramenta | 11+ linguagens oficiais |
Tracing explicado com analogias {#tracing}
Tracing é o pilar que mais confunde iniciantes, mas também é o mais poderoso quando se trata de depurar sistemas distribuídos. Vamos de analogia antes de definição técnica.
Analogia: Tracing é como rastrear um pacote dos Correios. Quando você envia uma encomenda, recebe um código de rastreio. Com ele, você vê cada etapa: "Objeto postado em São Paulo", "Em trânsito para centro de distribuição do Rio", "Saiu para entrega", "Entregue". Para cada etapa, há um horário. Subtraindo os horários, você descobre que o pacote ficou 2 dias parado no centro de distribuição — foi ali o gargalo. O tracing de software faz exatamente isso, mas com uma requisição em vez de um pacote.
Conceitos-chave do tracing
Um Trace é o caminho completo de uma requisição, do momento em que entra no seu sistema até o momento em que a resposta sai. Um trace é composto por um ou mais Spans.
Um Span é uma unidade de trabalho dentro do trace. Cada chamada a um serviço, cada query de banco, cada chamada a uma API externa pode ser um span. Um span tem:
- Nome: o que foi feito ("GET /checkout", "SELECT FROM orders").
- Tempo de início e duração: quanto tempo levou.
- Atributos (ou tags): metadados extras ("user.id=42", "http.status_code=500").
- Eventos: marcos pontuais dentro do span ("cache miss às 14:32:05").
- Span pai: qual span o chamou. É isso que cria a árvore hierárquica.
Um SpanContext é a identidade do span: um Trace ID (igual para todos os spans do mesmo trace) e um Span ID (único por span). É essa identidade que permite que spans de serviços diferentes sejam "amarrados" num único trace — o que chamamos de propagação de contexto.
Trace (Trace ID: 7a3f...) — total: 850ms
├─ Span 1: GET /checkout (850ms) [Serviço: API Gateway]
├─ Span 2: validar_token (12ms) [Serviço: Auth]
├─ Span 3: buscar_carrinho (180ms) [Serviço: Carrinho]
│ └─ Span 4: SELECT cart_items (165ms) [Banco: PostgreSQL]
└─ Span 5: processar_pagamento (640ms) [Serviço: Pagamentos]
└─ Span 6: POST /charge (620ms) [API externa: Stripe]
Nesse exemplo, fica óbvio que o gargalo está na chamada ao Stripe (620ms de 850ms totais). Sem tracing, você saberia só que "o checkout está lento". Com tracing, você sabe exatamente onde.
O que é propagação de contexto?
Quando a API Gateway chama o serviço de Pagamentos, ela precisa "passar adiante" o Trace ID, senão o Pagamentos vai gerar um trace novo e os dois nunca serão conectados. Isso é feito injetando cabeçalhos HTTP padronizados (no padrão W3C Trace Context, um padrão internacional reconhecido pelo World Wide Web Consortium) na requisição:
traceparent: 00-7a3f...-b2c1...-01
│ │ │ └── flags
│ │ └── span ID atual
│ └── trace ID (igual para todo o trace)
└── versão do formato
O OpenTelemetry faz essa propagação automaticamente quando você usa as bibliotecas de instrumentação oficiais — você não precisa escrever esses cabeçalhos à mão.
Métricas explicadas com analogias {#metricas}
Analogia: Métricas são como o painel do carro. Você não quer saber a velocidade exata em cada milissegundo da viagem (isso seria um log, e ia gerar uma quantidade absurda de dados). Você quer o velocímetro mostrando agora, e um histórico médio de "km/h nos últimos 10 minutos". Métricas são números agregados que te dão uma visão geral, sem armazenar cada evento individual.
Métricas são números que descrevem algum aspecto do seu sistema, coletados ao longo do tempo. Exemplos clássicos:
- Contadores (counters): só aumentam. Ex.: "total de requisições desde que o serviço subiu". Útil para calcular taxas (requisições por segundo derivando o contador).
- Medidores (gauges): sobem e descem. Ex.: "uso de memória agora", "conexões abertas no banco".
- Histogramas: distribuição de valores. Ex.: "quantas requisições levaram menos de 100ms, entre 100ms e 500ms, mais de 500ms". É a base para calcular percentis como p99 (o tempo abaixo do qual 99% das requisições são respondidas).
p99 significa que 99% das requisições foram mais rápidas que esse valor. Se o p99 é 800ms, significa que 1 em cada 100 requisições demorou mais que 800ms. É uma métrica muito usada porque a média esconde os usuários que tiveram a pior experiência — e geralmente são esses os que reclamam.
Tipos de métricas no OpenTelemetry
| Instrumento | O que mede | Exemplo | Equivalente em dashboard |
|---|---|---|---|
| Counter | Só cresce | Total de erros 500 | Número que só aumenta |
| Up/Down Counter | Cresce e decresce | Conexões ativas | Vai e volta |
| Gauge | Valor instantâneo | Memória em uso agora | Velocímetro |
| Histogram | Distribuição | Latência das requisições | Gráfico de barras por faixa |
Métricas são baratas de armazenar: são só números com timestamps. Por isso são ideais para dashboards de longo prazo ("como estava a latência no mês passado?") e para alertas ("alerte se o p99 passar de 1 segundo por 5 minutos seguidos").
Logs explicados com analogias {#logs}
Analogia: Logs são como o diário de bordo de um navio. O capitão anota tudo que acontece: "14:00 — partiu do porto", "18:30 — tempestade à vista, reduzindo velocidade", "22:15 — motor 2 com vibração anormal, equipe de manutenção acionada". Cada anotação é um evento isolado, com data e hora. Sozinha não conta muito, mas quando você junta todas na ordem certa, reconstrói a história inteira.
Logs são registros de eventos discretos. Cada log é uma linha (ou objeto) que diz: "nesse timestamp, aconteceu essa coisa, com esses detalhes". Existem três níveis de maturidade em como escrever logs:
- Logs em texto livre (iniciante):
print("usuário logado"). Fácil de escrever, difícil de buscar e filtrar depois. - Logs estruturados (intermediário): JSON com campos. Buscável, filtrável, indexável.
- Logs com contexto de trace (avançado): cada log carrega o
trace_idespan_idatuais, permitindo pular de um trace no Jaeger direto para os logs relacionados.
// Log estruturado SEM contexto de trace
{"timestamp": "2026-08-26T14:32:05Z", "level": "error", "msg": "falha no checkout"}
// Log estruturado COM contexto de trace (ideal)
{"timestamp": "2026-08-26T14:32:05Z", "level": "error", "msg": "falha no checkout",
"trace_id": "7a3f...", "span_id": "b2c1...", "user.id": 42, "cart.total": 199.90}
A diferença entre os dois exemplos acima é enorme na prática: com o
trace_id no log, quando você estiver olhando um trace lento no Jaeger,
pode copiar aquele ID e jogar na ferramenta de logs para ver todas as
mensagens geradas durante aquela requisição específica — em todos os
serviços.
Aviso de custo: logs são o pilar mais caro de armazenar. Um serviço medianamente usado pode gerar dezenas de GB de logs por dia. Use níveis de log (DEBUG, INFO, WARN, ERROR) com consciência e considere amostragem (sampling) para logs de baixo nível em produção.
Mão na massa: instrumentando uma API FastAPI {#fastapi}
Chegou a hora de ver código real. Vamos criar uma API simples com FastAPI (um framework web em Python, rápido e moderno) e instrumentá-la com OpenTelemetry para gerar traces. O objetivo é que, a cada requisição recebida, a API gere um trace com spans para cada operação interna.
Pré-requisitos
- Python 3.10 ou superior
- Familiaridade básica com terminal e pip
Passo 1: instalar as dependências
# Crie um ambiente virtual (boa prática — isola as bibliotecas do projeto)
python -m venv .venv
source .venv/bin/activate # no Windows: .venv\Scripts\activate
# Instale o FastAPI, o servidor ASGI Uvicorn e os pacotes do OpenTelemetry
pip install fastapi uvicorn \
opentelemetry-distro \
opentelemetry-exporter-otlp \
opentelemetry-instrumentation-fastapi \
opentelemetry-instrumentation-requests \
opentelemetry-instrumentation-logging
# O opentelemetry-distro instala automaticamente as instrumentações
# mais comuns. O "bootstrap" abaixo detecta suas libs e ativa as
# instrumentações correspondentes sem você precisar escrever código.
opentelemetry-bootstrap -a install
Passo 2: criar a aplicação
Crie um arquivo app.py:
# app.py — API de exemplo para demonstrar observabilidade com OTel
import logging
from fastapi import FastAPI
import requests
# O pacote abaixo configura o logging do Python para injetar
# automaticamente trace_id e span_id em cada log gerado dentro de um span.
from opentelemetry.instrumentation.logging import LoggingInstrumentor
# A instrumentação do FastAPI cria um span automaticamente para cada
# requisição HTTP recebida, com nome, método, status e duração.
from opentelemetry.instrumentation.fastapi import FastAPIInstrumentor
# A instrumentação do requests cria spans para cada chamada HTTP
# que sua aplicação fizer para serviços externos (propagando o contexto).
from opentelemetry.instrumentation.requests import RequestsInstrumentor
# Biblioteca principal de tracing — usamos para criar spans manuais.
from opentelemetry import trace
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
app = FastAPI()
# Instrumenta automaticamente todas as rotas do FastAPI.
# A partir daqui, cada request gera um trace com um span raiz.
FastAPIInstrumentor.instrument_app(app)
# Instrumenta chamadas externas com a lib requests.
RequestsInstrumentor().instrument()
# Faz cada linha de log carregar o trace_id e span_id atuais.
LoggingInstrumentor().instrument(set_logging_format=True)
@app.get("/")
def root():
# Rota simples — gera um span automático pelo FastAPIInstrumentor.
return {"status": "ok"}
@app.get("/lento")
def lento():
# Simula uma rota que chama um serviço externo demorado.
tracer = trace.get_tracer(__name__)
# Span manual: vamos medir quanto tempo levamos "pensando".
with tracer.start_as_current_span("processar_pagamento") as span:
# Adicionamos atributos ao span — informações extras pesquisáveis.
span.set_attribute("pagamento.metodo", "cartao")
span.set_attribute("pagamento.moeda", "BRL")
# Simula uma chamada externa (instrumentada, vira subspan).
# Substitua por uma URL real quando estiver testando.
try:
resp = requests.get("https://httpbin.org/delay/2", timeout=5)
span.set_attribute("pagamento.status_http", resp.status_code)
except Exception as e:
# Registra um evento de erro dentro do span.
span.record_exception(e)
span.set_status(trace.Status(trace.StatusCode.ERROR))
logger.error("falha ao chamar gateway de pagamento: %s", e)
return {"erro": "falha no pagamento"}
logger.info("pagamento processado com sucesso")
return {"status": "pago", "tempo_total_ms": 2000}
Passo 3: rodar com o OTel exportando para o Collector
O OpenTelemetry fornece um comando opentelemetry-instrument que
envolve sua aplicação e configura a exportação dos dados. Aqui vamos
apontar para um Collector rodando localmente na porta 4317 (gRPC).
# As variáveis de ambiente abaixo dizem ao OTel SDK:
# - OTEL_SERVICE_NAME: nome do serviço que aparecerá no Jaeger
# - OTEL_EXPORTER_OTLP_ENDPOINT: para onde enviar os dados (Collector)
# - OTEL_EXPORTER_OTLP_PROTOCOL: protocolo de transporte (gRPC)
# - OTEL_TRACES_EXPORTER: exportar traces via OTLP (protocolo padrão OTel)
export OTEL_SERVICE_NAME="checkout-api"
export OTEL_EXPORTER_OTLP_ENDPOINT="http://localhost:4317"
export OTEL_EXPORTER_OTLP_PROTOCOL="grpc"
export OTEL_TRACES_EXPORTER="otlp"
# Roda a aplicação instrumentada (na porta 8000)
opentelemetry-instrument uvicorn app:app --reload --port 8000
A partir de agora, cada vez que alguém bater em http://localhost:8000/
ou http://localhost:8000/lento, sua aplicação vai gerar um trace e
enviá-lo para o Collector (que então repassa para o Jaeger).
Passo 4: subir o Collector, Jaeger e Prometheus com Docker
Para não instalar tudo à mão, use este docker-compose.yml que sobe os
três componentes conectados:
# docker-compose.yml — pilha de observabilidade local
version: "3.8"
services:
# Collector: recebe dados via OTLP e distribui para os backends
otel-collector:
image: otel/opentelemetry-collector-contrib:0.108.0
command: ["--config=/etc/otelcol/config.yaml"]
volumes:
- ./otel-collector-config.yaml:/etc/otelcol/config.yaml
ports:
- "4317:4317" # OTLP gRPC (onde sua app envia)
- "4318:4318" # OTLP HTTP (alternativa)
# Jaeger: visualizador de traces (UI na porta 16686)
jaeger:
image: jaegertracing/all-in-one:1.60
environment:
- COLLECTOR_OTLP_ENABLED=true
ports:
- "16686:16686" # interface web do Jaeger
- "4317" # Jaeger também aceita OTLP direto
# Prometheus: armazena e consulta métricas (UI na porta 9090)
prometheus:
image: prom/prometheus:v2.54.1
volumes:
- ./prometheus.yml:/etc/prometheus/prometheus.yml
ports:
- "9090:9090"
E o arquivo de configuração do Collector (otel-collector-config.yaml):
# otel-collector-config.yaml — define os pipelines do Collector
receivers:
otlp:
protocols:
grpc:
endpoint: 0.0.0.0:4317
http:
endpoint: 0.0.0.0:4318
processors:
batch: # agrupa spans antes de enviar (eficiência)
timeout: 5s
send_batch_size: 1000
exporters:
otlp/jaeger: # envia traces para o Jaeger
endpoint: jaeger:4317
tls:
insecure: true
prometheus: # expõe métricas em formato Prometheus
endpoint: 0.0.0.0:8889
service:
pipelines:
traces: # pipeline de traces: recebe → processa → exporta
receivers: [otlp]
processors: [batch]
exporters: [otlp/jaeger]
metrics: # pipeline de métricas
receivers: [otlp]
processors: [batch]
exporters: [prometheus]
Suba tudo com:
docker compose up -d
Visualizando traces no Jaeger {#jaeger}
Jaeger (pronuncia-se "iéguer", é uma palavra alemã que significa "caçador") é uma ferramenta open source de UI para visualizar traces. Foi criada pela Uber e hoje é mantida pela CNCF — mesma casa do OpenTelemetry e do Kubernetes.
Depois de subir a pilha e fazer algumas requisições na sua API, abra o
navegador em http://localhost:16686. Você verá:
- Seletor de serviço (canto superior esquerdo): um dropdown onde
você escolhe qual serviço quer inspecionar. Deve aparecer
checkout-api(o nome que definimos emOTEL_SERVICE_NAME). - Filtros de busca: pode filtrar por tag (ex.:
http.status_code=500), por duração mínima, por operação. - Botão "Find Traces": lista os traces recentes que casam com os filtros.
- Detalhe do trace: ao clicar num trace, vê-se a cascata (waterfall) de spans com a linha do tempo e a hierarquia pai-filho.
O que procurar no Jaeger
| Sinal visual no Jaeger | O que provavelmente significa |
|---|---|
| Um span muito mais largo que os outros | Gargalo de latência — investigue essa operação |
| Span vermelho (com erro) | Exceção capturada — clique para ver a stack |
| Muitos spans filhos em série | Talvez possa paralelizar as chamadas |
| Span de chamada externa dominando | Backend/API de terceiros lento |
| Trace com poucos spans | Instrumentação faltando — serviço "cego" |
Dica de ouro: copie o
trace_idque aparece no detalhe de um trace no Jaeger e cole na sua ferramenta de logs (Loki, Elasticsearch, etc.). Com isso, você vê todas as mensagens de log geradas por aquela requisição específica, em todos os serviços. É a mágica do "logs com contexto de trace" que mencionamos antes.
Coletando métricas com Prometheus {#prometheus}
Prometheus é um banco de dados de séries temporais (TSDB — Time Series DataBase) e um sistema de coleta de métricas. É o padrão de fato para métricas no mundo cloud native. Diferente do Jaeger (que recebe traces por push), o Prometheus funciona por pull: ele vai até sua aplicação e pergunta "me dá suas métricas" em intervalos regulares (scrape).
Para que o Prometheus encontre sua aplicação, ela precisa expor um
endpoint HTTP (geralmente /metrics) com as métricas num formato de
texto específico. No nosso setup, é o Collector que expõe esse
endpoint (na porta 8889, conforme a config), agregando métricas de
todas as apps.
Arquivo prometheus.yml:
# prometheus.yml — configura o scrape do Prometheus
global:
scrape_interval: 15s # coleta métricas a cada 15s
scrape_configs:
- job_name: "otel-collector"
static_configs:
- targets: ["otel-collector:8889"] # onde o Collector expõe métricas
Abra http://localhost:9090 para acessar a UI do Prometheus. Algumas
queries úteis para começar (na linguagem PromQL, a query language do
Prometheus):
# Taxa de requisições por segundo nos últimos 5 minutos
rate(http_server_request_duration_seconds_count[5m])
# p99 da latência das requisições
histogram_quantile(0.99, rate(
http_server_request_duration_seconds_bucket[5m]
))
# Erros 500 por minuto
sum(rate(http_server_request_duration_seconds_count{
http_status_code="500"
}[1m])) by (service_name)
Não se assuste com a sintaxe — PromQL parece estranha no começo, mas segue uma lógica consistente. O importante agora é saber que essas consultas alimentam dashboards (no Grafana, por exemplo) e alertas.
Erros comuns (e como evitá-los) {#erros}
1. Instrumentar tudo e não filtrar nada
O erro: ligar a instrumentação automática em tudo, sem ajustar amostragem, e descobrir no fim do mês que você tem 500GB de traces que não consegue pagar para armazenar.
A solução: use amostragem (sampling). Em produção, na maioria dos casos, você não precisa de 100% dos traces — 1% a 10% costumam ser suficientes para detectar padrões. Reserve amostragem de 100% só para traces com erro. O OpenTelemetry suporta isso via tail sampling no Collector (decide depois de o trace terminar, mantendo os que tiveram erro).
# Exemplo de tail sampling no Collector — mantém 100% dos traces com erro
processors:
tail_sampling:
decision_wait: 10s
policies:
- name: errors
type: status_code
status_code:
status_codes: [ERROR]
- name: sample-rest
type: probabilistic
probabilistic:
sampling_percentage: 5
2. Esconder o trace_id dos logs
O erro: usar logging tradicional sem integrar com o OTel. Quando um incidente acontece, você tem traces no Jaeger e logs no Loki, mas nenhum jeito de conectá-los. Cada ferramenta vira uma ilha.
A solução: sempre use LoggingInstrumentor (ou equivalente na sua
linguagem) para injetar trace_id e span_id em cada log. Custo quase
zero, benefício enorme.
3. Usar a média para latência
O erro: montar um dashboard de "latência média" e achar que está tudo bem quando ela dá 200ms. O problema: a média esconde os outliers. Se 99 pessoas tiveram 50ms e 1 teve 10 segundos, a média parece ok, mas aquele 1 usuário teve uma experiência terrível.
A solução: acompanhe percentis — p50, p90, p95 e p99. O p99 te diz "1% dos seus usuários está sofrendo quanto?".
4. Alertar em sintoma, não em causa
O erro: criar um alerta "latência > 1s" sem ter uma forma de saber por que a latência subiu. O alerta dispara, a equipe acorda, e ninguém sabe onde olhar.
A solução: alertas em métricas (sintoma) devem sempre ter um caminho claro para traces e logs (causa). Se um alerta dispara, o primeiro link na notificação deveria ser uma query do Jaeger pré-filtrada para o serviço e janela de tempo em questão.
5. Tratar observabilidade como "coisa de SRE"
O erro: achar que observabilidade é responsabilidade só do time de SRE (Site Reliability Engineering, engenharia de confiabilidade) ou DevOps. Os desenvolvedores escrevem o código "cego" e jogam o problema de depuração por cima do muro.
A solução: observabilidade é responsabilidade de quem escreve o código. Se você escreve um serviço, você é quem mais entende quais spans, métricas e logs fazem sentido ali. Instrumentar deve fazer parte da definição de pronto (DoD — Definition of Done) de qualquer feature.
6. Espalhar nomes de spans inconsistentes
O erro: um desenvolvedor chama o span de GET /users, outro de
buscar_usuarios, outro de UserController.list. O mesmo trabalho
aparece com três nomes diferentes no Jaeger, dificultando buscas.
A solução: defina uma convenção de nomenclatura. O OpenTelemetry
recomenda o padrão {tipo}.{operação}.{atributo} — ex.:
http.server.request, db.query.select, messaging.publish. Padronize
e documente.
Conclusão e próximos passos {#conclusao}
Observabilidade não é uma ferramenta que você compra — é uma capacidade que você constrói combinando dados certos (traces, métricas, logs), ferramentas certas (OpenTelemetry como padrão, Jaeger e Prometheus como backends) e práticas certas (instrumentar desde o início, conectar logs a traces, alertar com caminho para a causa).
Comece pequeno:
- Instrumente um serviço com OpenTelemetry (use nosso exemplo do FastAPI como ponto de partida).
- Suba a pilha local com o docker-compose que mostramos.
- Veja um trace no Jaeger — esse "aha moment" vale mais que mil palavras.
- Adicione métricas e monte um dashboard simples no Grafana.
- Integre logs com
trace_ide sinta a diferença ao depurar.
A beleza de usar OpenTelemetry é que, quando você crescer e quiser trocar Jaeger por Tempo, ou Prometheus por Mimir, ou adicionar um backend comercial como Datadog — seu código não muda. Você instrumentou uma vez e fica livre para escolher o melhor destino para seus dados.
A Inicialize Tec pode te ajudar com isso
Implementar observabilidade do zero pode parecer intimidador — há muitas decisões de arquitetura, configuração de Collector, escolha de backends, padronização entre times. A Inicialize Tec ajuda empresas a saírem do "alarme de incêndio" e chegarem à observabilidade de verdade: instrumentação com OpenTelemetry, pilhas com Jaeger/Prometheus/Grafana ou soluções comerciais, dashboards e alertas que realmente reduzem MTTD e MTTR.
Fale com a gente para uma consultoria de observabilidade sob medida para o seu stack. Transformamos "por que está lento?" em resposta em minutos, não em horas.