Quando sua API cai às 3h da manhã, você abre os logs e vê: "Error processing request". Sem request ID, sem timestamp padronizado, sem contexto do que estava acontecendo. Esse log não ajuda a resolver nada.
Logging estruturado resolve isso: cada log é um objeto JSON com campos padronizados que ferramentas de observabilidade conseguem indexar, filtrar e correlacionar.
O formato que funciona
Cada log deve ser uma linha JSON com campos consistentes:
{
"level": "error",
"timestamp": "2026-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"
}
}
Comparar com o log não estruturado: "Erro ao processar pagamento". O formato estruturado permite buscar "todos os erros de pagamento do usuário xyz789", ou "erros de cartão recusado nos últimos 5 minutos", ou "qual a taxa de falha do gateway X".
Os campos que importam
- level: error, warn, info, debug. Padronize. Nunca use console.log em produção.
- timestamp: ISO 8601, sempre em UTC. Evita problemas de timezone.
- service: qual microserviço gerou o log. Obrigatório em arquitetura distribuída.
- request_id: ID único da requisição. Propaga entre serviços. Permite correlacionar logs do mesmo fluxo.
- user_id: quando aplicável, o ID do usuário afetado.
- error: objeto com nome, mensagem, e stack trace.
- context: dados específicos da operação que ajudam no debug.
Implementação com pino (Node.js)
Pino é o logger padrão para Node.js em produção. Rápido, estruturado, e com suporte nativo a JSON:
import pino from 'pino'
const logger = pino({
level: process.env.LOG_LEVEL || 'info',
formatters: {
level: (label) => ({ level: label }),
},
timestamp: pino.stdTimeFunctions.isoTime,
})
// Middleware de request
app.use((req, res, next) => {
req.id = crypto.randomUUID()
req.log = logger.child({
request_id: req.id,
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()
})
Request ID: o fio condutor
O request_id é o campo mais importante para debug em sistemas distribuídos. Ele precisa nascer na primeira camada e propagar por todas as chamadas:
// Gerar no gateway/API
const requestId = req.headers['x-request-id'] || crypto.randomUUID()
// Propagar em chamadas internas
await fetch('http://user-service/api/users', {
headers: { 'X-Request-Id': requestId }
})
Com ferramentas como Datadog, Elasticsearch, ou Loki, você pode buscar todos os logs de um request_id específico e ver o fluxo completo de uma requisição, de ponta a ponta.
O que logar e o que não logar
Logar: erros, warnings, eventos de negócio importantes (pagamento aprovado, conta criada), métricas de performance (duração de queries, tempo de resposta).
Não logar: dados sensíveis (senhas, tokens, dados de cartão), dados PII sem necessidade, fluxo normal de operações (excessivo), variáveis de ambiente.
// ❌ Nunca
logger.info({ password: user.password, token: jwt })
// ✅ Correto
logger.info({ user_id: user.id, action: 'login', ip: req.ip })
Log levels na prática
Error: algo falhou e precisa de atenção. Alerta imediato.
Warn: algo inesperado mas não crítico. Merece investigação.
Info: eventos normais do sistema. Pagamento processado, email enviado.
Debug: detalhes para desenvolvimento. Desabilitado em produção.
Em produção, rode com level: 'info'. Ative debug temporariamente via variável de ambiente quando precisar investigar algo específico.
Correlação com métricas
Logs estruturados combinados com métricas (Prometheus, Datadog) permitem dashboards que mostram: taxa de erros por endpoint, latência percentil, throughput por serviço. O log dá o o quê aconteceu, a métrica dá o quanto.
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