Um cliente liga: “às 14h32 eu tentei pagar e deu erro”. Você tem trinta minutos pra descobrir o que aconteceu.
Se o sistema tiver os identificadores certos, isso é uma consulta. Se não tiver, é arqueologia em cinco serviços com relógios ligeiramente diferentes.
Os três identificadores costumam ser tratados como sinônimos. Não são, e cada um responde uma pergunta diferente.
Os três
Trace ID identifica uma operação de ponta a ponta, atravessando todos os serviços. Nasce na borda, na primeira requisição, e viaja em toda chamada subsequente. É a linha que amarra tudo.
Span ID identifica uma unidade de trabalho dentro do trace. Uma chamada HTTP, uma query, um processamento. Cada span tem um pai, e a árvore de spans é o que mostra onde o tempo foi gasto.
Correlation ID identifica um fluxo de negócio, que pode durar muito mais que uma requisição. Um pedido que passa por checkout, autorização, captura e envio ao longo de três dias tem quatro traces diferentes e um correlation ID só.
A confusão mais comum é usar trace ID pra fazer o trabalho de correlation ID. Funciona enquanto tudo acontece numa requisição só. Quebra no momento em que entra fila, retry ou processamento assíncrono, que é justamente quando você mais precisa.
Um exemplo concreto
Correlation ID: ord_9f2b4c (o pedido, do começo ao fim)
Trace 1 (checkout, 14h32)
span: POST /orders
span: validar estoque
span: INSERT orders
span: publicar no outbox
Trace 2 (worker consome, 14h32:04)
span: processar pagamento
span: POST gateway/charge
span: INSERT ledger_entry
Trace 3 (webhook do gateway, 14h33:11)
span: POST /webhooks/gateway
span: atualizar status
Três traces separados, três momentos, um pedido. Sem o correlation ID, você tem três investigações independentes e nenhuma forma automática de ligar uma na outra.
Como propagar
Na borda, gere o trace ID se não vier um, e gere ou receba o correlation ID:
@Injectable()
export class ContextMiddleware implements NestMiddleware {
use(req: Request, res: Response, next: NextFunction) {
const traceId = req.headers["traceparent"]
? parseTraceparent(req.headers["traceparent"]).traceId
: randomTraceId();
const correlationId =
(req.headers["x-correlation-id"] as string) ?? `req_${randomUUID()}`;
res.setHeader("x-correlation-id", correlationId);
// AsyncLocalStorage: o contexto segue o fluxo sem passar parâmetro
context.run({ traceId, correlationId }, () => next());
}
}
Devolver o correlation ID no cabeçalho de resposta é a coisa mais barata e mais útil dessa lista. O frontend exibe numa mensagem de erro, o usuário manda o código pro suporte, e a busca vira imediata.
AsyncLocalStorage é o que evita ter que passar contexto como parâmetro por dez camadas. É a diferença entre esse padrão ser adotado e ser abandonado na segunda semana.
Onde quase todo mundo perde o rastro
Na fila. A mensagem vai pro broker e o contexto morre. Coloque os identificadores nos metadados da mensagem e restaure no consumidor:
await queue.add("process-payment", payload, {
headers: { traceId: ctx.traceId, correlationId: ctx.correlationId },
});
No retry. A segunda tentativa precisa do mesmo correlation ID e de um trace novo. Trace é a tentativa, correlation é o negócio.
No job agendado. Cron não tem requisição de origem. Gere um correlation ID por execução e registre no log inicial. Sem isso, todo log de job vira um mar indistinguível.
No log. De nada adianta ter o identificador se ele não aparece na linha:
logger.info("pagamento autorizado", {
correlationId: ctx.correlationId,
traceId: ctx.traceId,
orderId: order.id,
});
Log estruturado, sempre. Log em texto livre com o ID no meio da frase funciona pra leitura humana e não funciona pra busca.
Observabilidade e rastreabilidade não são a mesma coisa
Vale separar, porque os dois times pedem coisas parecidas e precisam de coisas diferentes.
Observabilidade é operacional. Responde por que está lento, onde quebrou, quantos erros por minuto. Vive na ferramenta de tracing, tem retenção de dias ou semanas, e amostragem é aceitável: guardar 10% dos traces bem-sucedidos é prática comum e economiza muito.
Rastreabilidade é de negócio. Responde o que aconteceu com aquele pedido, quem aprovou, em que ordem. Vive no seu banco, tem retenção de anos, e amostragem é inaceitável. Escrevi sobre esse desenho em audit trail que aguenta uma auditoria.
O erro caro é usar a ferramenta de observabilidade como fonte de rastreabilidade. Seis meses depois o dado expirou, ou nunca existiu porque a amostragem descartou justamente aquele trace.
Comece pelo mais barato
Se o sistema não tem nada disso, a ordem que dá mais retorno:
Correlation ID gerado na borda, propagado, presente em todo log estruturado e devolvido no cabeçalho de resposta. Isso resolve a maior parte das investigações de suporte e leva um dia pra implementar.
Depois OpenTelemetry pro trace e span, quando a pergunta virar “por que está lento” em vez de “o que aconteceu com esse pedido”.
Fazer na ordem inversa é comum, e é como se acaba com um painel bonito de latência e nenhuma resposta pra pergunta que o cliente faz.