Blog

Correlation ID, trace ID e span ID: quem é quem.

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.

← Voltar pro blog