O que é distributed tracing e por que logs não são suficientes
Distributed tracing é a técnica de acompanhar o caminho completo que uma requisição percorre em um sistema composto por múltiplos serviços, registrando o tempo e o resultado de cada operação ao longo da cadeia. Em sistemas monolíticos, um stack trace já era suficiente para entender o que aconteceu durante uma requisição. Em arquiteturas de microsserviços, uma única operação de usuário pode atravessar dezenas de serviços, cada um com seu próprio banco de dados, fila de mensagens e chamadas externas — e os logs de cada serviço ficam isolados sem correlação automática. Sem tracing distribuído, debugar um problema de latência significa manualmente correlacionar timestamps de logs em múltiplos sistemas, um processo lento e propenso a erros. O tracing resolve isso propagando um identificador único pela requisição inteira.
Spans e traces — a estrutura de um trace
Um trace é composto por spans, onde cada span representa uma operação individual com tempo de início, duração, metadados e status. O span raiz corresponde à requisição original do usuário, e cada serviço que processa essa requisição cria um span filho, formando uma árvore hierárquica de operações. Um span típico registra: o nome da operação, o serviço que a executou, timestamps de início e fim, atributos como URL, método HTTP e código de status, e eventos que ocorreram durante a operação. A visualização de um trace em ferramentas como Jaeger ou Zipkin mostra um diagrama de Gantt com todos os spans da requisição, tornando imediatamente visível onde o tempo foi gasto e onde ocorreram erros.
Trace ID e Span ID — propagando contexto entre serviços
A propagação de contexto é o mecanismo que conecta spans de diferentes serviços em um único trace. Quando o serviço A faz uma chamada HTTP para o serviço B, ele inclui no header da requisição o Trace ID (identificador único do trace inteiro) e o Span ID do span atual (que se tornará o parent span do próximo). O padrão W3C Trace Context define os headers traceparent e tracestate que devem ser propagados, garantindo interoperabilidade entre sistemas instrumentados com diferentes ferramentas. Em sistemas com filas de mensagens, o contexto é propagado nas propriedades da mensagem, garantindo que o trace traverse também comunicações assíncronas. Sem propagação correta, os spans de diferentes serviços não se conectam e o trace fica fragmentado.
Instrumentação automática vs manual
Instrumentação automática usa agentes e bibliotecas que interceptam chamadas de rede, banco de dados e frameworks populares para criar spans sem alterações no código de aplicação. Para Node.js, o pacote @opentelemetry/auto-instrumentations-node instrumenta automaticamente Express, HTTP, gRPC, MongoDB, Redis e dezenas de outras bibliotecas. Para Java, o agente OpenTelemetry é um JAR que se anexa à JVM e instrumenta frameworks Spring, JDBC e Kafka. A instrumentação manual é necessária quando se quer adicionar contexto específico de negócio — como o ID do pedido ou o tipo de operação — a um span, usando a API do OpenTelemetry para criar spans customizados e adicionar atributos. A combinação de auto-instrumentação com spans manuais estratégicos oferece visibilidade completa sem overhead de desenvolvimento excessivo.
Jaeger e Zipkin — ferramentas de tracing
Jaeger e Zipkin são as duas principais ferramentas open-source para coleta, armazenamento e visualização de traces distribuídos. O Jaeger foi desenvolvido pelo Uber e doado à CNCF, com suporte a sampling adaptativo, múltiplos backends de armazenamento (Cassandra, Elasticsearch, Badger) e uma UI rica para análise de traces. O Zipkin tem origem no Twitter e é mais simples de configurar, com backend em memória adequado para desenvolvimento local. Ambos recebem traces via protocolo OTLP (OpenTelemetry Protocol) ou formatos nativos, e oferecem funcionalidades de busca por serviço, operação, tags e duração. Para produção em grande escala, Jaeger com armazenamento em Elasticsearch ou Cassandra é a escolha mais comum no ecossistema CNCF.
OpenTelemetry como padrão de instrumentação
OpenTelemetry (OTel) emergiu como o padrão de facto para instrumentação de observabilidade, unificando o que antes eram projetos separados: OpenTracing para traces e OpenCensus para métricas. O OTel fornece SDKs para as principais linguagens, um protocolo de exportação neutro (OTLP), e um Collector que recebe telemetria de aplicações e a encaminha para backends como Jaeger, Zipkin, Datadog ou Grafana Tempo. A vantagem de usar OTel é o desacoplamento entre instrumentação e backend: é possível trocar de Jaeger para Datadog sem alterar o código de instrumentação, apenas reconfigurando o exporter do Collector. Isso evita o lock-in em fornecedores de observabilidade específicos.
Sampling — não guardar 100% dos traces
Em sistemas de alto tráfego, guardar 100% dos traces seria proibitivo em termos de custo de storage e overhead de I/O. Sampling é a estratégia de decidir quais traces guardar, com duas abordagens principais: head-based sampling, onde a decisão é feita no início da requisição com uma probabilidade configurável (ex: 1% dos traces), e tail-based sampling, onde o trace completo é avaliado ao final para decidir se vale a pena guardar — tipicamente preservando 100% dos traces com erros e uma amostra dos traces normais. O OpenTelemetry Collector suporta tail-based sampling com regras configuráveis. A taxa de sampling deve ser calibrada considerando o volume de tráfego, o custo de storage e a visibilidade necessária para debugging eficaz.
Correlação com logs e métricas
O valor máximo do tracing é realizado quando traces são correlacionados com logs e métricas no mesmo contexto de observabilidade. Ao incluir o Trace ID nos logs de cada serviço, é possível navegar de um trace com erro diretamente para os logs gerados durante aquela requisição específica, sem filtrar manualmente por timestamp. Ferramentas como Grafana permitem criar links diretos entre traces no Tempo e logs no Loki usando o Trace ID como chave de correlação. Métricas agregadas podem alertar sobre degradação de performance, e o trace permite drill-down no span exato que causou a lentidão. Essa tríade — logs, métricas e traces — é o que se chama de observabilidade completa ou "three pillars of observability".
Tracing em banco de dados e filas
Spans de banco de dados capturam informações críticas como a query SQL executada, o tempo de execução, o número de linhas retornadas e se houve uso de índice. A instrumentação automática do OpenTelemetry para drivers de banco — como o driver JDBC para Java ou os pacotes para Prisma e Mongoose — cria esses spans automaticamente. Para filas de mensagens como Kafka ou RabbitMQ, o contexto de tracing é injetado nas propriedades da mensagem pelo produtor e extraído pelo consumidor, garantindo que o trace conecte o serviço que publicou a mensagem ao serviço que a processou. Isso é fundamental para entender a latência em arquiteturas event-driven onde o processamento acontece de forma assíncrona.
Conclusão
Distributed tracing transforma a experiência de debugging em sistemas distribuídos de uma busca manual em múltiplos arquivos de log para uma navegação visual pelo caminho exato de cada requisição. A adoção do OpenTelemetry como padrão de instrumentação garante flexibilidade de backend e evita lock-in em ferramentas proprietárias. Sampling inteligente permite manter visibilidade sem custos proibitivos de armazenamento. Quando combinado com logs estruturados e métricas correlacionadas pelo Trace ID, o tracing distribído eleva o nível de observabilidade do sistema a ponto de tornar problemas de performance detectáveis antes que usuários percebam. Continue em: Fundamentos obrigatórios antes de produção.
Vídeos — Distributed Tracing
Distributed Tracing Explicado
OpenTelemetry na Prática
Observabilidade em Microsserviços
Conceitos — Tracing Distribuído
Trace
Conjunto de spans que representam o caminho completo de uma requisição
Span
Unidade de trabalho com nome, timestamps, atributos e status
Trace ID
Identificador único propagado por todos os serviços da requisição
Head-based Sampling
Decisão de amostrar no início da requisição por probabilidade
Tail-based Sampling
Decisão de amostrar após o trace completo, preservando erros
Posts — Observabilidade
@bytebytego
Reels — Sistemas e Arquitetura
@bytebytego
ByteByteGo no Facebook
Tweets — Sistemas Distribuídos
Como testar que sua API é resiliente e segura para produção real
Ver post completo no X →Implementando padrões de resiliência em .NET Core com exemplos reais
Ver post completo no X →Vertical Slice Architecture — organizando sistemas para escala
Ver post completo no X →5 anos com Clean Architecture — lições de sistemas em produção
Ver post completo no X →Design de APIs resilientes — retry, backoff e idempotência juntos
Ver post completo no X →Monolito vs Microsserviços — como escolher para cada contexto
Ver post completo no X →O que dizem
Tracing me mostrou em segundos um N+1 escondido que levava 3 segundos por requisição.
Correlacionar trace com logs pelo Trace ID mudou completamente nosso processo de debugging.
Jaeger com tail-based sampling salvou nosso storage sem perder visibilidade de erros.