Uma API de pagamentos cai às 3h da manhã. Você abre o log e lê "Error processing request". A linha não traz request ID, traz timestamp num formato diferente dos outros eventos e omite o valor que estava sendo processado. Ela não devolve nada para a investigação.
Logging estruturado trata cada evento como objeto com campos nomeados, em vez de string montada à mão. O ganho aparece na filtragem: "todos os erros de pagamento do usuário usr_xyz789" vira uma consulta indexada.
Uma linha JSON por evento
Cada evento ocupa uma linha e repete os mesmos campos. Ferramenta de observabilidade indexa cada campo e aceita filtros cruzados.
{
"level": "error",
"timestamp": "2025-12-18T14:30:00.123Z",
"message": "Failed to process payment",
"service": "payment-api",
"request_id": "req_abc123",
"user_id": "usr_xyz789",
"error": {
"name": "PaymentGatewayError",
"message": "Card declined",
"stack": "PaymentGateway.ts:45:12"
},
"context": {
"amount": 15000,
"currency": "BRL",
"payment_method": "credit_card"
}
}
O mesmo incidente em texto corrido gravaria "Erro ao processar pagamento". O objeto acima responde a três perguntas distintas: erros de pagamento daquele usuário, recusas de cartão nos últimos cinco minutos, taxa de falha do gateway X.
Campos de que o debug depende
- level: error, warn, info ou debug, com o mesmo vocabulário em todo o sistema. console.log não tem nível e não entra em filtro.
- timestamp: RFC 3339 em UTC. O RFC 3339 fixa o formato que ferramentas de log reconhecem sem parser customizado.
- service: o microserviço que emitiu o evento. Em arquitetura distribuída, a mensagem sozinha não identifica a origem.
- request_id: o identificador da requisição, propagado por todo o caminho.
- user_id: o usuário afetado, quando a operação tiver um.
- error: nome, mensagem e stack trace num objeto, cada um no seu campo.
- context: os dados da operação que explicam o erro, como valor, moeda e método de pagamento.
Implementação com pino
O pino emite JSON e serializa campos extras sem custo de concatenação de string. A documentação do pino descreve child() como a forma de fixar contexto por requisição.
import pino from 'pino'
const logger = pino({
level: process.env.LOG_LEVEL || 'info',
formatters: {
level: (label) => ({ level: label }),
},
timestamp: pino.stdTimeFunctions.isoTime,
})
app.use((req, res, next) => {
req.log = logger.child({
request_id: crypto.randomUUID(),
method: req.method,
url: req.url,
})
const start = Date.now()
res.on('finish', () => {
req.log.info({
status_code: res.statusCode,
duration_ms: Date.now() - start,
}, 'Request completed')
})
next()
})
Toda linha gerada durante a requisição carrega request_id, método e URL. O alerta que aponta um request_id devolve, na busca, a sequência completa de eventos daquela chamada.
Request ID e trace context
O request_id nasce na borda: o gateway ou a API de entrada lê X-Request-Id do header ou gera um novo, e repassa em cada chamada interna. O W3C Trace Context padroniza esse repasse com os headers traceparent e tracestate, e o mesmo conceito aparece no modelo de logs do OpenTelemetry.
const requestId = req.headers['x-request-id'] || crypto.randomUUID()
await fetch('http://user-service/api/users', {
headers: { 'X-Request-Id': requestId },
})
Com Datadog, Elasticsearch ou Loki, o filtro por request_id monta a linha do tempo da requisição, serviço a serviço.
O que entra no log e o que fica de fora
Entra: erro, warning, evento de negócio com valor (pagamento aprovado, conta criada, email enviado) e duração de query e de resposta.
Fica de fora: senha, token, número de cartão, variável de ambiente e o passo a passo de uma operação que correu bem. Dado sensível em log vira incidente de segurança com alcance maior que o bug original, porque log tem retenção longa e cópia em mais de um lugar.
// errado
logger.info({ password: user.password, token: jwt })
// certo
logger.info({ user_id: user.id, action: 'login', ip: req.ip })
Níveis na prática
Error: falha que exige alguém olhar agora, com alerta configurado. Warn: comportamento inesperado que merece investigação sem parar o sistema. Info: evento normal do domínio. Debug: detalhe de desenvolvimento, desligado em produção e ligado por variável de ambiente durante uma investigação.
Log responde o quê, métrica responde quanto
Um log de erro explica o caso. A taxa de erros por endpoint mede a dimensão do problema. O capítulo 6 do Google SRE Book trata latência, tráfego, erros e saturação como os sinais que monitoram um serviço; o capítulo 10 descreve a investigação que começa nos dados agregados e desce até o caso individual. Os dois se apoiam no mesmo request_id.
Referências
- pino (documentação do logger)
- RFC 3339, Date and Time on the Internet (Klyne e Newman, 2002)
- W3C Trace Context
- Logs signal (OpenTelemetry documentation)
- Site Reliability Engineering, cap. 6 e cap. 10 (Google, O'Reilly, 2016)
Curtiu o conteúdo?
Construo produtos web e soluções com IA do jeito certo — arquitetura sólida, código sustentável e entrega real.
Vamos conversar