10 de nov. de 2025·7 min de leitura

Rastreamento Go OpenTelemetry para visibilidade ponta a ponta da API

Rastreamento Go OpenTelemetry explicado com passos práticos para correlacionar traces, métricas e logs em requisições HTTP, jobs em background e chamadas a terceiros.

Rastreamento Go OpenTelemetry para visibilidade ponta a ponta da API

O que rastreamento ponta a ponta significa para uma API Go

Um trace é a linha do tempo de uma requisição enquanto ela atravessa seu sistema. Começa quando uma chamada de API chega e termina quando você envia a resposta.

Dentro de um trace há spans. Um span é um passo cronometrado, como “parse request”, “run SQL” ou “call payment provider”. Spans também podem carregar detalhes úteis, como um código de status HTTP, um identificador de usuário seguro ou quantas linhas uma query retornou.

“Ponta a ponta” significa que o trace não para no seu primeiro handler. Ele segue a requisição pelos lugares onde os problemas geralmente se escondem: middleware, consultas ao banco de dados, chamadas ao cache, jobs em background, APIs de terceiros (pagamentos, email, mapas) e outros serviços internos.

Rastreamento é mais valioso quando problemas são intermitentes. Se uma em cada 200 requisições fica lenta, os logs costumam parecer idênticos para casos rápidos e lentos. Um trace torna a diferença óbvia: uma requisição passou 800 ms esperando uma chamada externa, foi re-tentada duas vezes e depois iniciou um job de acompanhamento.

Logs também são difíceis de conectar entre serviços. Você pode ter uma linha de log na API, outra em um worker e nada entre elas. Com tracing, esses eventos compartilham o mesmo trace ID, então você pode seguir a cadeia sem adivinhar.

Traces, métricas e logs: como se encaixam

Traces, métricas e logs respondem perguntas diferentes.

Traces mostram o que aconteceu para uma requisição real. Eles dizem onde o tempo foi gasto no handler, nas chamadas ao banco, nos lookups de cache e nas requisições a terceiros.

Métricas mostram a tendência. São a melhor ferramenta para alertas porque são estáveis e baratas de agregar: percentis de latência, taxa de requisições, taxa de erro, profundidade de filas e saturação.

Logs são o “por quê” em texto claro: falhas de validação, entradas inesperadas, casos de borda e decisões que seu código tomou.

A vantagem real é a correlação. Quando o mesmo trace ID aparece em spans e em logs estruturados, você pode saltar de um log de erro para o trace exato e ver imediatamente qual dependência ficou lenta ou qual etapa falhou.

Um modelo mental simples

Use cada sinal para o que ele faz melhor:

  • Métricas dizem que algo está errado.
  • Traces mostram onde o tempo foi gasto para uma requisição.
  • Logs explicam o que seu código decidiu e por quê.

Exemplo: seu endpoint POST /checkout começa a dar timeout. Métricas mostram o p95 de latência subindo. Um trace mostra que a maior parte do tempo está dentro de uma chamada ao provedor de pagamento. Um log correlacionado dentro daquele span mostra tentativas por causa de um 502, o que aponta para ajustes de backoff ou um incidente upstream.

Antes de adicionar código: nomes, amostragem e o que rastrear

Um pouco de planejamento antecipado facilita buscas nos traces depois. Sem isso, você ainda coleta dados, mas perguntas básicas ficam difíceis: “Isso foi em staging ou prod?” “Qual serviço iniciou o problema?”

Comece com identidade consistente. Escolha um service.name claro para cada API Go (por exemplo, checkout-api) e um único campo de ambiente como deployment.environment=dev|staging|prod. Mantenha esses valores estáveis. Se os nomes mudarem no meio da semana, gráficos e buscas vão parecer sistemas diferentes.

Em seguida, decida a amostragem. Traçar todas as requisições é ótimo em desenvolvimento, mas muitas vezes caro em produção. Uma abordagem comum é amostrar uma pequena porcentagem do tráfego normal e manter traces para erros e requisições lentas. Se você já sabe que certos endpoints têm alto volume (health checks, polling), trace-os menos ou não os trace.

Por fim, concorde sobre o que será tagueado nos spans e o que você nunca coletará. Mantenha uma lista curta de atributos permitidos que ajudem a conectar eventos entre serviços e escreva regras de privacidade simples.

Boas tags geralmente incluem IDs estáveis e informações grosseiras da requisição (template da rota, método, código de status). Evite payloads sensíveis completamente: senhas, dados de pagamento, emails completos, tokens de autenticação e bodies inteiros. Se for necessário incluir valores relacionados ao usuário, faça hash ou redija-os antes de adicionar.

Passo a passo: adicionar OpenTelemetry a uma API HTTP Go

Você vai configurar um tracer provider uma vez na inicialização. Isso decide para onde os spans vão e quais atributos de recurso são adicionados a cada span.

1) Inicialize OpenTelemetry

Certifique-se de definir service.name. Sem isso, traces de serviços diferentes se misturam e os gráficos ficam difíceis de ler.

// main.go (startup)
exp, _ := stdouttrace.New(stdouttrace.WithPrettyPrint())

res, _ := resource.New(context.Background(),
	resource.WithAttributes(
		semconv.ServiceName("checkout-api"),
	),
)

tp := sdktrace.NewTracerProvider(
	 sdktrace.WithBatcher(exp),
	 sdktrace.WithResource(res),
)

otel.SetTracerProvider(tp)

Essa é a base para rastreamento Go OpenTelemetry. Em seguida, você precisa de um span por requisição recebida.

2) Adicione middleware HTTP e capture campos-chave

Use middleware HTTP que inicie um span automaticamente e registre o código de status e a duração. Defina o nome do span usando o template de rota (como /users/:id), não a URL bruta, ou você terá milhares de caminhos únicos.

Busque uma linha de base limpa: um span de servidor por requisição, nomes de span baseados em rota, status HTTP capturado, falhas de handler refletidas como erro no span e duração visível no visualizador de trace.

3) Torne falhas óbvias

Quando algo der errado, retorne um erro e marque o span atual como falhado. Isso faz o trace se destacar mesmo antes de você olhar os logs.

Nos handlers, você pode fazer:

span := trace.SpanFromContext(r.Context())
span.RecordError(err)
span.SetStatus(codes.Error, err.Error())

4) Verifique IDs de trace localmente

Execute a API e acesse um endpoint. Faça log do trace ID vindo do contexto da requisição uma vez para confirmar que ele muda a cada requisição. Se estiver sempre vazio, seu middleware não está usando o mesmo contexto que o handler recebe.

Carregue o contexto através do DB e chamadas a terceiros

Mapeie a lógica com fluxos visuais
Transforme fluxos de negócio em serviços que você pode rastrear por handlers, jobs e dependências.
Construir agora

A visibilidade ponta a ponta se quebra no momento em que você perde context.Context. O contexto da requisição deve ser o fio que você passa para cada chamada ao DB, HTTP e helper. Se você o substituir por context.Background() ou esquecer de passar adiante, seu trace vira trabalhos separados e sem relação.

Para HTTPs de saída, use um transport instrumentado para que cada Do(req) vire um span filho sob a requisição atual. Encaminhe os cabeçalhos W3C de trace nas requisições outbound para que serviços downstream anexem seus spans ao mesmo trace.

Chamadas ao banco precisam do mesmo tratamento. Use um driver instrumentado ou envolva as chamadas com spans ao redor de QueryContext e ExecContext. Registre apenas detalhes seguros. Você quer encontrar queries lentas sem vazar dados.

Atributos úteis e de baixo risco incluem um nome de operação (por exemplo, SELECT user_by_id), nome da tabela ou modelo, contagem de linhas (apenas contagem), duração, número de tentativas e um tipo de erro grosseiro (timeout, canceled, constraint).

Timeouts fazem parte da história, não só falhas. Defina-os com context.WithTimeout para chamadas ao DB e a terceiros, e deixe cancelamentos borbulharem. Quando uma chamada for cancelada, marque o span como erro e adicione uma razão curta como deadline_exceeded.

Rastreando jobs em background e filas

Construa APIs Go rastreáveis
Crie um backend Go que você pode instrumentar de ponta a ponta conforme sua API cresce.
Experimentar AppMaster

Trabalho em background é onde os traces frequentemente param. Uma requisição HTTP termina, então um worker processa uma mensagem depois em outra máquina sem contexto compartilhado. Se você não fizer nada, terá duas histórias: o trace da API e um trace do job que parece ter começado do nada.

A solução é simples: quando você enfileira um job, capture o contexto de trace atual e armazene-o nos metadados do job (payload, headers ou atributos, dependendo da fila). Quando o worker iniciar, extraia esse contexto e inicie um novo span como filho da requisição original.

Propague contexto com segurança

Copie apenas o contexto de trace, não dados do usuário.

  • Injete apenas identificadores de trace e flags de amostragem (estilo W3C traceparent).
  • Mantenha isso separado dos campos de negócio (por exemplo, um campo dedicado "otel" ou "trace").
  • Trate como entrada não confiável ao ler de volta (valide o formato, lide com dados faltantes).
  • Evite colocar tokens, emails ou bodies de requisição nos metadados do job.

Spans a adicionar (sem transformar traces em ruído)

Traces legíveis geralmente têm alguns spans significativos, não dezenas de minúsculos. Crie spans ao redor de limites e “pontos de espera”. Um bom ponto de partida é um span enqueue no handler da API e um span job.run no worker.

Adicione um pequeno conjunto de contexto: número de tentativa, nome da fila, tipo do job e tamanho do payload (não o conteúdo). Se houver retries, registre-os como spans ou eventos separados para que você veja os delays de backoff.

Tarefas agendadas também precisam de um pai. Se não houver requisição de entrada, crie um span raiz para cada execução e marque-o com o nome da agenda.

Correlacione logs com traces (e mantenha logs seguros)

Traces dizem onde o tempo foi gasto. Logs dizem o que aconteceu e por quê. A maneira mais simples de conectá-los é adicionar trace_id e span_id a cada entrada de log como campos estruturados.

Em Go, pegue o span ativo de context.Context e enriqueça seu logger uma vez por requisição (ou job). Então cada linha de log aponta para um trace específico.

span := trace.SpanFromContext(ctx)
sc := span.SpanContext()
logger := baseLogger.With(
  "trace_id", sc.TraceID().String(),
  "span_id",  sc.SpanID().String(),
)
logger.Info("charge_started", "order_id", orderID)

Isso é suficiente para saltar de uma entrada de log para o span exato que estava rodando quando o log foi gravado. Também torna óbvio quando falta contexto: trace_id estará vazio.

Mantenha logs úteis sem vazar PII

Logs costumam viver mais tempo e viajar mais do que traces, então seja mais rígido. Prefira identificadores estáveis e resultados: user_id, order_id, payment_provider, status e error_code. Se precisar logar entrada do usuário, redija-a primeiro e limite tamanhos.

Facilite agrupar erros

Use nomes de eventos e tipos de erro consistentes para que você possa contar e buscar por eles. Se a redação mudar toda vez, o mesmo problema parecerá muitos diferentes.

Adicione métricas que realmente ajudam a encontrar problemas

Prototipe um checkout rastreado
Crie um serviço de checkout com limites claros para spans, novas tentativas e timeouts.
Começar a construir

Métricas são seu sistema de aviso precoce. Em uma configuração que já usa Go OpenTelemetry tracing, métricas devem responder: com que frequência, quão grave e desde quando.

Comece com um conjunto pequeno que funciona para quase toda API: contagem de requisições, contagem de erros (por classe de status), percentis de latência (p50, p95, p99), requisições em andamento e latência de dependências para seu DB e chamadas a terceiros chave.

Para manter métricas alinhadas com traces, use os mesmos templates de rota e nomes. Se seus spans usam /users/{id}, suas métricas também devem. Assim, quando um gráfico mostrar “p95 para /checkout subiu”, você pode ir direto para traces filtrados por essa rota.

Cuidado com labels (atributos). Uma label ruim pode explodir custos e tornar dashboards inúteis. Template de rota, método, classe de status e nome do serviço geralmente são seguros. IDs de usuário, emails, URLs completos e mensagens de erro brutas geralmente não são.

Adicione algumas métricas customizadas para eventos críticos de negócio (por exemplo, checkout started/completed, falhas de pagamento por grupo de códigos, sucesso vs retry de jobs). Mantenha o conjunto pequeno e remova o que você não usa.

Exportando telemetria e fazer rollout com segurança

Exportar é onde OpenTelemetry vira algo real. Seu serviço tem que enviar spans, métricas e logs para algum lugar confiável sem atrasar requisições.

Para desenvolvimento local, mantenha simples. Um exporter para console (ou OTLP para um collector local) deixa você ver traces rapidamente e validar nomes de spans e atributos. Em produção, prefira OTLP para um agente ou OpenTelemetry Collector perto do serviço. Isso te dá um lugar único para lidar com retries, roteamento e filtragem.

Batching importa. Envie telemetria em lotes com intervalo curto, com timeouts apertados para que uma rede lenta não bloqueie sua app. Telemetria não deve ficar no caminho crítico. Se o exporter não der conta, ele deve descartar dados em vez de acumular memória.

Amostragem mantém custos previsíveis. Comece com amostragem head-based (por exemplo, 1–10% das requisições), depois adicione regras simples: sempre amostrar erros e sempre amostrar requisições lentas acima de um limite. Se você tem jobs de alto volume, amostre-os em taxas menores.

Faça rollout em passos pequenos: dev com 100% de amostragem, staging com tráfego realista e amostragem menor, e então produção com amostragem conservadora e alertas em falhas do exporter.

Erros comuns que arruinam a visibilidade ponta a ponta

Tenha o código-fonte
Gere código pronto para produção que você pode exportar e instrumentar do jeito que sua equipe prefere.
Experimentar AppMaster

A visibilidade ponta a ponta falha na maioria das vezes por razões simples: os dados existem, mas não se conectam.

Os problemas que quebram tracing distribuído em Go costumam ser:

  • Perder o contexto entre camadas. Um handler cria um span, mas uma chamada ao DB, cliente HTTP ou goroutine usa context.Background() em vez do contexto da requisição.
  • Retornar erros sem marcar spans. Se você não registrar o erro e ajustar o status do span, os traces ficam “verdes” mesmo quando usuários veem 500s.
  • Instrumentar tudo. Se cada helper vira um span, traces viram ruído e custam mais.
  • Adicionar atributos de alta cardinalidade. URLs completas com IDs, emails, valores SQL brutos, bodies ou strings de erro podem criar milhões de valores únicos.
  • Julgar performance por médias. Incidentes aparecem em percentis (p95/p99) e taxa de erro, não na latência média.

Um teste rápido é pegar uma requisição real e segui-la por todas as fronteiras. Se você não consegue ver um único trace ID fluindo pela requisição de entrada, pela query do DB, pela chamada a terceiros e pelo worker assíncrono, você ainda não tem visibilidade ponta a ponta.

Checklist prático de “pronto”

Padronize nomes de serviço
Gere serviços Go reais e mantenha nomes e tags de ambiente consistentes entre apps.
Começar

Você está perto quando consegue ir do relato de um usuário até a requisição exata e depois segui-la por cada salto.

  • Pegue uma linha de log da API e localize o trace exato por trace_id. Confirme que logs mais profundos da mesma requisição (DB, cliente HTTP, worker) carregam o mesmo contexto de trace.
  • Abra o trace e verifique o aninhamento: um span de servidor HTTP no topo, com spans filhos para chamadas ao DB e APIs externas. Uma lista plana geralmente significa contexto perdido.
  • Inicie um job em background a partir de uma requisição de API (como enviar um recibo por email) e confirme que o span do worker se liga de volta à requisição.
  • Verifique métricas básicas: contagem de requisições, taxa de erro e percentis de latência. Confirme que dá para filtrar por rota ou operação.
  • Escaneie atributos e logs por segurança: sem senhas, tokens, números de cartão completos ou dados pessoais brutos.

Um teste simples é simular um checkout lento onde o provedor de pagamento está atrasado. Você deve ver um trace com um span externo claramente rotulado e um pico de métricas no p95 de latência para a rota de checkout.

Se você gera backends Go (por exemplo, com AppMaster), ajuda tornar esse checklist parte da rotina de release para que novos endpoints e workers continuem rastreáveis conforme a aplicação cresce. AppMaster (appmaster.io) gera serviços Go reais, então você pode padronizar uma configuração OpenTelemetry e levá-la entre serviços e jobs em background.

Exemplo: depurando um checkout lento entre serviços

Uma mensagem de cliente diz: “O checkout às vezes fica travado.” Você não consegue reproduzir sob demanda, exatamente quando o rastreamento Go OpenTelemetry se mostra útil.

Comece pelas métricas para entender o formato do problema. Veja taxa de requisições, taxa de erro e p95 ou p99 de latência para o endpoint de checkout. Se a lentidão acontece em curtos surtos e apenas para uma fatia das requisições, geralmente aponta para uma dependência, enfileiramento ou comportamento de retry em vez de CPU.

Em seguida, abra um trace lento no mesmo intervalo de tempo. Um trace costuma ser suficiente. Um checkout saudável pode levar 300 a 600 ms fim a fim. Um ruim pode levar 8 a 12 segundos, com a maior parte do tempo dentro de um único span.

Um padrão comum é: o handler da API é rápido, o trabalho no DB está ok, então um span do provedor de pagamento mostra retries com backoff, e uma chamada downstream espera atrás de um lock ou fila. A resposta pode ainda retornar 200, então alertas baseados apenas em erros nunca disparam.

Logs correlacionados então contam o caminho exato em linguagem simples: “retrying Stripe charge: timeout”, seguido por “db tx aborted: serialization failure”, seguido por “retry checkout flow”. Isso indica claramente que alguns pequenos problemas se combinam em uma má experiência ao usuário.

Depois de encontrar o gargalo, consistência é o que mantém tudo legível com o tempo. Padronize nomes de spans, atributos (hash de user ID seguro, order ID, nome da dependência) e regras de amostragem entre serviços para que todos leiam traces da mesma forma.

Fácil de começar
Criar algo espantoso

Experimente o AppMaster com plano gratuito.
Quando estiver pronto, você poderá escolher a assinatura adequada.

Comece