O que Observabilidade e Instrumentação faz?
Visão Geral
Código que você não pode observar é código que você não pode operar. Observabilidade é a capacidade de responder "o que o sistema está fazendo e por quê?" de fora, usando a telemetria que o código emite. Instrumentação não é um complemento pós-lançamento — é escrita junto com a funcionalidade, da mesma forma que os testes. Se uma funcionalidade for lançada sem telemetria, o primeiro bug relatado pelo usuário se torna uma arqueologia em vez de uma consulta.
Quando Usar
- Construir qualquer funcionalidade que será executada em produção
- Adicionar um novo serviço, endpoint, job em segundo plano ou integração externa
- Um incidente de produção demorou muito para diagnosticar ("não conseguimos descobrir o que aconteceu")
- Configurar ou revisar regras de alerta
- Revisar um PR que adiciona E/S, retentativas, filas ou chamadas entre serviços
NÃO para:
- Diagnosticar uma falha acontecendo agora — use o skill
debugging-and-error-recovery(observabilidade é o que torna esse skill rápido na próxima vez) - Perfilamento e otimização de lentidão medida — use o skill
performance-optimization - Checklists de monitoramento de lançamento e gatilhos de rollback — veja o skill
shipping-and-launch; este skill cobre a instrumentação que os alimenta
Processo
1. Defina "funcionando" antes de instrumentar
Telemetria sem uma pergunta é ruído. Antes de adicionar qualquer instrumentação, escreva de 2 a 4 perguntas que um engenheiro de plantão fará sobre esta funcionalidade:
FUNCIONALIDADE: retentativa de pagamento no checkout
PERGUNTAS QUE O PLANTÃO FARÁ:
1. Qual fração de pagamentos é bem-sucedida na primeira tentativa versus após retentativas?
2. Quando um pagamento falha permanentemente, por quê? (erro do provedor? tempo limite? validação?)
3. O provedor de pagamento está mais lento que o normal?
→ Cada sinal abaixo deve ajudar a responder uma destas.
Se você não consegue nomear as perguntas, não está pronto para instrumentar — você registrará tudo e não aprenderá nada.
2. Escolha o sinal certo para cada pergunta
| Sinal | Respostas | Perfil de custo | Exemplo |
|---|---|---|---|
| Log estruturado | "O que aconteceu neste caso específico?" | Por evento; cresce com o tráfego | payment_failed com código de erro do provedor |
| Métrica | "Com que frequência / rapidez, em agregado?" | Fixo por série; barato para consultar | p99 de latência de chamadas ao provedor |
| Trace | "Onde o tempo foi gasto entre serviços?" | Por requisição; geralmente amostrado | Um checkout lento, detalhado por salto |
Regra geral: métricas dizem que algo está errado, traces dizem onde, logs dizem por quê.
3. Logs estruturados
Registre eventos, não prosa. Cada linha de log é um objeto JSON com um nome de evento estável e campos legíveis por máquina:
// RUIM: interpolação de string — não consultável, inconsistente
logger.info(`Pagamento ${id} falhou para o usuário ${userId} após ${n} retentativas`);
// BOM: nome de evento estável + campos estruturados
logger.warn({
event: 'payment_failed',
paymentId: id,
provider: 'stripe',
errorCode: err.code,
attempt: n,
}, 'pagamento falhou');
Níveis de log — use-os consistentemente:
| Nível | Significado | Ação do plantão |
|---|---|---|
error | Invariante quebrada; alguém pode precisar agir | Investigar |
warn | Degradado, mas tratado (retentativa bem-sucedida, fallback usado) | Observar tendências |
info | Evento de negócio significativo (pedido realizado, job concluído) | Nenhuma |
debug | Detalhe de diagnóstico | Desativado em produção por padrão |
IDs de correlação são obrigatórios. Gere (ou aceite) um ID de requisição no limite do sistema e anexe-o a cada linha de log, span e chamada de saída. Sem ele, você não pode reconstruir uma única requisição a partir de logs intercalados:
// Express: logger filho por requisição, ID propagado downstream
app.use((req, res, next) => {
req.id = req.headers['x-request-id'] ?? crypto.randomUUID();
req.log = logger.child({ requestId: req.id });
res.setHeader('x-request-id', req.id);
next();
});
Quando vários pontos de entrada escrevem em um único log, nomeie o ponto de entrada. Um ID de correlação identifica uma execução; ele não diz qual caminho de código a iniciou. O mesmo job alcançado por um agendador, por um endpoint de replay e por uma execução manual de CLI produz linhas intercambiáveis em um único coletor, então atribuir uma linha recai na eliminação — cruzar o histórico do agendador, a tabela de processos, um log de implantação — e esse argumento só é válido enquanto esses registros externos ainda existirem. Rotule o ponto de entrada onde a execução começa, ao lado do ID de correlação, e propague ambos da mesma forma:
// Um helper para cada ponto de entrada: o próprio logger da execução carrega ambos os campos.
// `entryPoint`, não `source` — ECS reserva `source.*` para campos de rede.
export const runLog = (entryPoint: 'scheduler' | 'replay_endpoint' | 'cli', runId: string) =>
logger.child({ entryPoint, requestId: runId });
// tick do agendador -> runLog('scheduler', crypto.randomUUID())
// POST /jobs/:id/replay -> runLog('replay_endpoint', req.id)
// invocação CLI -> runLog('cli', process.env.RUN_ID ?? crypto.randomUUID())
Ambos os campos devem cruzar os mesmos limites que o ID de correlação — metadados de fila, cabeçalhos HTTP — ou um trabalhador redescobre o ponto de entrada e adivinha. Um campo que meramente se correlaciona com um ponto de entrada é uma dica, não uma atribuição: qualquer coisa que possa invocar o job pode reproduzi-lo.
Nunca registre segredos, tokens, senhas ou PII completa. Esta é uma regra rígida do skill security-and-hardening — pipelines de telemetria são um caminho clássico de vazamento de dados. Use uma lista de permissões de campos; não registre corpos de requisição inteiros.
4. Métricas
Para serviços orientados a requisições, instrumente RED em cada endpoint e cada dependência externa: Rate (requisições/s), Errors (taxa de falha), Duration (histograma de latência, não média). Para recursos (filas, pools, hosts), use USE: Utilization, Saturation, Errors.
Assim como com tracing, o caminho neutro de fornecedor é a API de métricas do OpenTelemetry (mesmo SDK e contexto da etapa 5). O exemplo abaixo usa prom-client do Prometheus — uma escolha comum de backend, não a única; as regras de RED/USE e cardinalidade são idênticas de qualquer forma.
import { Histogram } from 'prom-client';
const httpDuration = new Histogram({
name: 'http_request_duration_seconds',
help: 'Duração da requisição HTTP',
labelNames: ['method', 'route', 'status_class'], // '2xx', não '200'
buckets: [0.05, 0.1, 0.25, 0.5, 1, 2.5, 5],
});
Cardinalidade é o modo de falha. Cada combinação única de rótulos é uma série temporal separada. Rótulos devem vir de conjuntos pequenos e fixos (template de rota, classe de status, nome do provedor). Nunca use IDs de usuário, URLs brutas, mensagens de erro ou outros valores ilimitados como rótulos — isso pertence a logs e traces.
OK como rótulo: route="/api/tasks/:id" status_class="5xx" provider="stripe"
NUNCA um rótulo: user_id, email, request_id, URL completa, texto de mensagem de erro
Nunca acompanhe médias, sempre percentis: uma média esconde os 1% de usuários que estão tendo uma experiência terrível. Use histogramas e leia p50/p95/p99.
5. Tracing distribuído
Use OpenTelemetry — é o padrão neutro de fornecedor, e a auto-instrumentação cobre HTTP, gRPC e clientes de banco de dados comuns com código quase zero:
// tracing.ts — deve ser importado antes de qualquer outra coisa
import { NodeSDK } from '@opentelemetry/sdk-node';
import { getNodeAutoInstrumentations } from '@opentelemetry/auto-instrumentations-node';
const sdk = new NodeSDK({
serviceName: 'checkout-service',
instrumentations: [getNodeAutoInstrumentations()],
});
sdk.start();
Adicione spans manuais apenas em torno de unidades de trabalho internas significativas (por exemplo, applyDiscounts, chargeProvider) e anexe os atributos pelos quais o plantão filtrará. Propague o contexto através de cada limite assíncrono — cabeçalhos HTTP, metadados de mensagens de fila — ou o trace morre na lacuna. Amostre com base na cabeça a uma taxa baixa por padrão; mantenha 100% dos erros se seu backend suportar amostragem de cauda.
6. Alertas
Alerta sobre sintomas que os usuários sentem, não sobre causas:
SINTOMA (pagina): CAUSA (dashboard, não uma página):
taxa de erro > 1% por 5 min CPU a 85%
latência p99 > 2s um pod reiniciou
idade da fila > 10 min disco a 70%
Alertas baseados em causa disparam quando nada está errado e perdem falhas que você não previu. Alertas baseados em sintoma disparam exatamente quando os usuários são prejudicados, independentemente da causa.
Regras para cada alerta que você criar:
- Deve ser acionável. Se a resposta for "ignorar, ele se cura sozinho", exclua o alerta.
- Ele vincula a um runbook — mesmo três linhas: o que significa, primeira consulta a executar, caminho de escalação.
- Tem um limite e duração justificados pelo SLO ou por dados históricos, não por um palpite.
- Use apenas duas severidades: página (voltado para o usuário, aja agora) e ticket (degradação, aja esta semana). Um terceiro nível se torna ruído que treina as pessoas a ignorar tudo.
Escrevendo Runbooks
A regra 2 acima exige que cada alerta vincule a um runbook. O trabalho de um runbook é responder a três perguntas sem exigir que o leitor pense: o que está acontecendo, o que verificar primeiro e para quem ligar se isso não resolver. Armazene em docs/runbooks/ nomeado conforme o alerta.
Runbook mínimo viável (três linhas):
# Runbook: Alta Taxa de Erro em /api/tasks
**Significa:** Pool de conexões de BD provavelmente esgotado ou uma implantação ruim.
**Primeira verificação:** `SELECT count(*) FROM pg_stat_activity WHERE backend_type = 'client backend';`
— se contagem > limite do pool, veja Passo 2. (Substitua pelo equivalente do seu banco de dados.)
**Escalar para:** #db-oncall ou a rotação de plantão de engenharia.
Quando expandir além de três linhas: adicione etapas apenas quando a primeira verificação sozinha não for suficiente para decidir. Um runbook de cinco etapas que cobre as três causas mais comuns é melhor do que um documento de vinte etapas que cobre cada caso extremo e é lido superficialmente.
Mantenha os runbooks atualizados. Atualize o runbook como parte do fechamento de cada incidente em que foi usado — um runbook desatualizado gera falsa confiança. Se uma etapa estava errada ou faltando, corrija-a antes de marcar o incidente como resolvido.
7. Verifique a própria telemetria
Instrumentação é código; pode estar errada. Antes de considerar o trabalho concluído, acione os caminhos e veja a saída real:
- Force um erro em staging → encontre-o nos logs por
requestId, confirme que os campos são estruturados (não[object Object]) - Envie tráfego de teste → confirme que as séries de métricas aparecem com os rótulos esperados e valores sensatos
- Siga uma requisição entre serviços na interface de tracing → sem spans quebrados
- Dispare cada novo alerta uma vez (abaixe o limite temporariamente) → confirme que ele atinge o canal certo e o link do runbook funciona
Racionalizações Comuns
| Racionalização | Realidade |
|---|---|
| "Adicionarei logging depois que funcionar" | "Depois" se torna "depois do primeiro incidente", que é o momento mais caro para descobrir que você está cego. Instrumente enquanto constrói. |
| "Mais logs = mais observabilidade" | Ruído não estruturado torna incidentes mais lentos, não mais rápidos. Três eventos consultáveis vencem trezentas linhas de prosa. |
| "console.log serve por enquanto" | Saída não estruturada não pode ser filtrada, correlacionada ou alertada. O logger estruturado custa cinco minutos extras uma vez. |
| "Podemos apenas olhar os dashboards quando algo quebrar" | Dashboards construídos sem perguntas definidas mostram tudo, exceto a resposta. Comece com as perguntas do plantão. |
| "Alerta sobre tudo importante, ajustaremos depois" | Um pager barulhento treina as pessoas a ignorá-lo. O ajuste nunca acontece; a página real perdida, sim. |
| "ID do usuário como um rótulo de métrica facilita a depuração" | Também faz seu backend de métricas cair. Consultas de alta cardinalidade pertencem a logs e traces. |
| "Tracing é exagero para nossos dois serviços" | Dois serviços já significam perguntas de latência entre serviços que logs não podem responder. Auto-instrumentação torna o custo trivial. |
Bandeiras Vermelhas
- Um PR de funcionalidade com retentativas, filas ou chamadas externas e zero nova telemetria
- Linhas de log construídas por interpolação de string em vez de campos estruturados
- Nenhum ID de correlação/requisição — cada linha de log é um órfão
- Um fluxo de log alimentado por um agendador, um webhook e execuções manuais, sem campo nomeando qual produziu a linha
- Métricas rotuladas com IDs de usuário, URLs brutas ou texto de mensagem de erro (bomba de cardinalidade)
- Latência rastreada como uma média sem percentis
- Alertas que disparam diariamente e são reconhecidos sem ação
- Alertas sobre causas (CPU, memória) paginando humanos enquanto a taxa de erro voltada para o usuário não é monitorada
- Segredos, tokens ou corpos de requisição completos aparecendo em logs
- "Funciona na minha máquina" como a única evidência de que uma funcionalidade em produção está saudável
Verificação
Após instrumentar uma funcionalidade, confirme:
- As perguntas do plantão para esta funcionalidade estão escritas, e cada sinal mapeia para uma
- Toda saída de log é estruturada (JSON), com nomes de evento estáveis e um ID de correlação em cada linha
- Cada coletor de log escrito por mais de um ponto de entrada carrega um campo de ponto de entrada, definido onde a execução começa e propagado com o ID de correlação, em vez de inferido downstream
- Nenhum segredo, token ou PII não redigido em nenhuma linha de log (verificação pontual da saída real)
- Métricas RED existem para cada novo endpoint e cada dependência externa, com conjuntos de rótulos limitados
- Latência é um histograma; p95/p99 são consultáveis
- Uma única requisição pode ser seguida de ponta a ponta na interface de tracing sem spans quebrados
- Cada novo alerta é baseado em sintoma, tem um link de runbook e foi disparado em teste uma vez
- Uma falha induzida em staging foi localizada apenas por meio de telemetria, sem ler o código-fonte
Para a versão resumida desta lista, incluindo o gate de instrumentação pré-lançamento, veja ../../references/observability-checklist.md.