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