Renan Montanari Blog
Voltar ao blog
ObservabilidadeBack-end

Registro Guardado Não É Pergunta Respondida

O cliente diz que o pagamento falhou ontem e você tem registro de tudo, mas não consegue responder o que aconteceu. Como um identificador por requisição liga os logs e transforma uma investigação em uma consulta.

Renan Montanari

Renan Montanari

Engenheiro de Software Full-Stack

6 min de leitura

Introdução: Tudo Foi Registrado. Nada Foi Respondido.

O cliente diz que tentou pagar ontem, por volta das três da tarde, e não conseguiu.

Você tem registro de tudo. Mesmo assim, não consegue responder o que aconteceu.

Porque registro guardado não é a mesma coisa que pergunta respondida.

Por Que Os Logs Não Conversam Entre Si

Um log costuma contar o que aconteceu dentro de uma peça do sistema, isolada. Aquele pagamento, porém, passou por várias peças:

  1. A tela, que enviou a requisição.
  2. Seu servidor, que validou e processou.
  3. O meio de pagamento, que autorizou ou recusou.
  4. O servidor de novo, que recebeu a resposta e atualizou o pedido.

São quatro registros, em quatro lugares, sem nada que ligue um ao outro. Procurar “o pagamento desse cliente” significa cruzar horários, IDs de pedido e palpites.

A Correção Pequena Que Muda Tudo

A solução é simples de descrever: cada requisição recebe um identificador único, e esse identificador aparece em todo registro que ela gerar.

Esse identificador costuma ser chamado de correlation ID ou request ID. Ele é criado na entrada do sistema e propagado para tudo o que a requisição toca.

import { randomUUID } from 'node:crypto';
import { AsyncLocalStorage } from 'node:async_hooks';

// Contexto que acompanha a requisição, inclusive em operações assíncronas
export const requestContext = new AsyncLocalStorage<{ requestId: string }>();

// Middleware: cria o ID na entrada, ou reaproveita o que veio do cliente ou do gateway
export function requestIdMiddleware(req, res, next) {
  const requestId = req.headers['x-request-id'] ?? randomUUID();
  res.setHeader('x-request-id', requestId);

  requestContext.run({ requestId }, () => next());
}

// Logger que sempre inclui o ID, sem depender de quem chama
export function log(level: 'info' | 'error', message: string, data: object = {}) {
  const store = requestContext.getStore();

  console[level](JSON.stringify({
    message,
    requestId: store?.requestId,
    timestamp: new Date().toISOString(),
    ...data,
  }));
}

Repare no que isso resolve:

  • Quem escreve o log não precisa lembrar de passar o ID. O contexto acompanha a requisição sozinho.
  • Operações assíncronas herdam o contexto. O AsyncLocalStorage mantém o ID mesmo depois de await.
  • O ID volta para o cliente no header de resposta. Se ele mandar o print de um erro, o ID está ali.

Em projetos maiores, esse papel costuma ser feito por ferramentas de tracing distribuído, como OpenTelemetry. A ideia é a mesma. O código acima mostra o princípio sem depender de infraestrutura.

Quando “Buscar o Cliente” Vira Dez Segundos

Com o ID presente em todo registro, a investigação muda de forma:

  • Antes: cruzar logs de três sistemas, filtrar por horário, tentar adivinhar qual pedido era.
  • Depois: pegar o ID que o cliente enviou e filtrar. Todos os registros da requisição aparecem em ordem.

A diferença não é de ferramenta cara. É de decisão tomada antes de o problema acontecer.

Trade-offs: O ID Não Pode Vazar Nada

Alguns cuidados:

  • O ID não deve conter dado pessoal. Use UUID aleatório, não CPF, e-mail ou número de pedido.
  • Aceitar o ID de fora exige cuidado. Se o cliente ou um gateway enviar um x-request-id, valide o formato antes de registrar. Caso contrário, alguém pode poluir os logs.
  • Mensagem de erro para o usuário deve ser genérica. O ID ajuda o suporte, mas não precisa revelar detalhes internos do erro.

Checklist: Sua Investigação Leva Dez Minutos?

  • Cada requisição recebe um identificador único?
  • Esse identificador aparece em todos os logs que a requisição gera?
  • Ele é propagado para chamadas a serviços externos e filas?
  • O cliente ou o suporte consegue ver esse ID quando algo falha?
  • Você consegue reconstruir o caminho de uma requisição em minutos?

Conclusão: Pergunte Antes do Incidente

Quanto tempo o seu time levaria para descobrir por que um cliente específico falhou ontem?

Se a resposta for “horas”, o problema não é falta de registro. É falta de ligação entre os registros.

Um ID por requisição custa pouco para implementar, e transforma uma investigação em uma consulta.

Gostou? Compartilhe