Tracing distribuído: seguindo o caos entre serviços
Instrumentar uma aplicação sozinha resolve metade do problema. A outra metade aparece quando essa aplicação chama outro serviço, que chama mais um, e o rastro que existia dentro do primeiro processo simplesmente para de existir assim que a chamada atravessa a rede, a menos que alguém tenha propagado esse contexto de propósito.
O que precisa atravessar a rede junto com a requisição
Dentro de um único processo, um span filho sabe automaticamente quem é o span pai, porque tudo acontece no mesmo contexto de execução. Assim que uma chamada HTTP sai para outro serviço, essa relação de parentesco só sobrevive se as informações necessárias para reconstruí-la viajarem junto na própria requisição, dentro de headers específicos.
W3C Trace Context: o padrão que faz isso funcionar entre fornecedores diferentes
traceparent e tracestate são os dois headers definidos pela especificação W3C Trace Context, hoje o formato padrão para propagar contexto de rastreamento entre serviços, independente de qual ferramenta de observabilidade cada serviço usa por trás. O traceparent carrega o identificador do trace inteiro, o identificador do span pai e flags de amostragem, num formato compacto de string. O tracestate carrega informação adicional específica de fornecedor, sem interferir na interpretação padrão do traceparent.
traceparent: 00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01
Esse formato único é o que permite um trace atravessar, por exemplo, um serviço instrumentado com OpenTelemetry chamando outro que usa uma ferramenta de observabilidade comercial diferente, e ainda assim manter a mesma árvore de span coerente do início ao fim, porque os dois lados falam o mesmo protocolo de propagação.
O .NET já propaga isso sozinho, na maioria dos casos
A boa notícia é que instrumentar HttpClient com AddHttpClientInstrumentation() (parte do pacote OpenTelemetry.Instrumentation.Http) já injeta o header traceparent automaticamente em toda requisição de saída, associando o novo span da chamada HTTP ao span ativo no momento da chamada, sem exigir código manual para montar esse header.
builder.Services.AddOpenTelemetry()
.WithTracing(tracing => tracing
.AddSource("MinhaAplicacao.Pedidos")
.AddAspNetCoreInstrumentation()
.AddHttpClientInstrumentation()
.AddOtlpExporter());
Do lado de quem recebe a requisição, AddAspNetCoreInstrumentation() lê o traceparent recebido e cria o span daquela requisição já como filho do span de origem, em vez de começar uma árvore nova desconectada do trace original. Entre serviços .NET instrumentados dessa forma, a propagação acontece sem intervenção manual em nenhum dos dois lados.
Correlation ID não é a mesma coisa que trace ID, mas resolve um problema parecido
Antes de tracing distribuído se popularizar, o padrão comum era gerar um Correlation ID por conta própria, propagá-lo manualmente num header customizado, e usar esse valor para filtrar log espalhado entre serviços. Isso ainda funciona e continua aparecendo em sistemas que não adotaram OpenTelemetry, mas exige disciplina manual em cada ponto de chamada para não quebrar a cadeia. O trace-id dentro do traceparent cumpre esse mesmo papel de correlacionar tudo que pertence à mesma operação, com a vantagem de já vir padronizado e propagado automaticamente pela instrumentação, sem exigir que cada serviço reinvente seu próprio esquema de correlação.
Onde isso ainda quebra
Propagação automática cobre chamadas HTTP feitas via HttpClient instrumentado. Ela não cobre, por padrão, comunicação assíncrona via fila (uma mensagem publicada numa fila SQS ou RabbitMQ não carrega traceparent automaticamente, a menos que a aplicação inclua esse header manualmente na mensagem publicada e recupere no consumidor) nem chamada para um serviço que não entende o protocolo de propagação. Nesses pontos, o trace se quebra em dois pedaços desconectados, a menos que alguém trate a propagação de contexto como parte explícita do contrato de mensagem, não como algo que “simplesmente funciona” por padrão.
Seguir o caos exige que o contexto viaje, não só que ele exista
Ter cada serviço bem instrumentado individualmente não é suficiente para reconstruir o caminho de uma operação que passa por vários deles. O valor real de tracing distribuído só aparece quando o contexto de rastreamento atravessa cada fronteira de rede, HTTP, fila, ou qualquer outro protocolo usado entre os serviços, e isso exige tratar propagação como parte do desenho da comunicação entre serviços, não como detalhe que a instrumentação resolve sozinha em todo caso.