Pular para o conteúdo

Instrumentar um serviço com OpenTelemetry

Intermediário18 min de leituraobservabilidade

Ao final deste guia, o serviço emite traces, métricas e logs que se conectam pelo mesmo trace_id — e você troca o backend de observabilidade mudando configuração, não código.

Antes de escrever qualquer span, ligue o que vem pronto: framework HTTP, cliente de banco, cliente de fila. Isso já rende a maior parte do valor.

Janela do terminal
# Python
pip install opentelemetry-distro opentelemetry-exporter-otlp
opentelemetry-bootstrap -a install
opentelemetry-instrument python -m app # o processo passa a ser instrumentado
# Node
npm i @opentelemetry/auto-instrumentations-node @opentelemetry/sdk-node
node --require @opentelemetry/auto-instrumentations-node/register app.js
# Java
java -javaagent:opentelemetry-javaagent.jar -jar app.jar

Em Go e Rust não há agente automático: use as bibliotecas de instrumentação do framework (otelhttp, otelgin) — são poucas linhas e o efeito é o mesmo.

2. Configure o exportador por variável de ambiente

Seção intitulada “2. Configure o exportador por variável de ambiente”

Os mesmos nomes valem em todas as linguagens, o que é justamente a graça do padrão.

Janela do terminal
export OTEL_SERVICE_NAME=pedidos
export OTEL_RESOURCE_ATTRIBUTES=service.namespace=loja,deployment.environment=prod,service.version=1.8.3
export OTEL_EXPORTER_OTLP_ENDPOINT=http://otel-collector.observabilidade:4317
export OTEL_EXPORTER_OTLP_PROTOCOL=grpc
export OTEL_TRACES_SAMPLER=parentbased_traceidratio
export OTEL_TRACES_SAMPLER_ARG=0.1 # 10% para começar

service.version e deployment.environment parecem detalhe e são o que permite responder “a latência subiu depois da 1.8.3?” — preencha desde o começo.

Mandar direto da aplicação para o backend funciona e amarra você a ele. O Collector no meio é o que dá liberdade — e é onde você redige dado sensível.

receivers:
otlp: { protocols: { grpc: { endpoint: 0.0.0.0:4317 }, http: { endpoint: 0.0.0.0:4318 } } }
processors:
memory_limiter: { check_interval: 1s, limit_percentage: 75 }
attributes/limpeza:
actions:
- { key: http.request.header.authorization, action: delete }
- { key: usuario.email, action: hash }
batch: { timeout: 5s, send_batch_size: 1024 }
exporters:
otlphttp/tempo: { endpoint: http://tempo:4318 }
prometheusremotewrite: { endpoint: http://mimir:9009/api/v1/push }
service:
pipelines:
traces: { receivers: [otlp], processors: [memory_limiter, attributes/limpeza, batch], exporters: [otlphttp/tempo] }
metrics: { receivers: [otlp], processors: [memory_limiter, batch], exporters: [prometheusremotewrite] }

Implante como DaemonSet (um por nó) para começar. memory_limiter primeiro e batch por último é a ordem recomendada.

4. Acrescente spans manuais onde a lógica importa

Seção intitulada “4. Acrescente spans manuais onde a lógica importa”

Só depois de a instrumentação automática estar funcionando, e só onde ela não alcança: regra de negócio, chamada a sistema legado, processamento em lote.

from opentelemetry import trace
tracer = trace.get_tracer(__name__)
with tracer.start_as_current_span("autorizar-pagamento") as span:
span.set_attribute("gateway", gateway) # baixa cardinalidade: dá para agrupar
span.set_attribute("pedido.tipo", tipo)
try:
resultado = gateway_client.autorizar(pedido)
except GatewayError as e:
span.record_exception(e)
span.set_status(Status(StatusCode.ERROR), "gateway recusou") # não esqueça disto
raise

Marcar o status de erro é o passo mais esquecido — e é o que faz o trace aparecer nas buscas por falha e na amostragem por cauda.

Atributo é para o que você vai filtrar ou agrupar. Nada de token, CPF, e-mail ou corpo de requisição.

Inclua o trace_id em todo log de requisição:

span = trace.get_current_span().get_span_context()
logger.info("pedido criado", extra={
"trace_id": format(span.trace_id, "032x"),
"span_id": format(span.span_id, "016x"),
"pedido_id": pedido.id,
})

No Grafana, configure derivedFields no datasource do Loki para transformar esse campo em link para o trace. A partir daí, log de erro e trace lento ficam a um clique um do outro.

Janela do terminal
# o trace chegou inteiro? procure pelo trace_id de um log recente
curl -s "http://tempo:3200/api/traces/<trace_id>" | jq '.batches | length'
# o Collector está descartando algo?
curl -s http://otel-collector:8888/metrics | grep -E 'otelcol_(receiver_accepted|exporter_sent|processor_dropped)'

Comece com 10% e ajuste pelo volume. Quando o custo apertar, migre para amostragem por cauda no Collector — assim você guarda todo erro e toda cauda lenta, e amostra o resto: veja tracing distribuído.

Sintoma Causa provável
Nenhum dado chega Endpoint ou protocolo errado (4317 é gRPC, 4318 é HTTP)
Trace quebrado em pedaços Contexto não propagado: fila, cliente HTTP feito à mão, proxy removendo traceparent
Serviço aparece como unknown_service OTEL_SERVICE_NAME ausente
Métricas sem chegar ao Prometheus Pipeline de metrics faltando no Collector
Collector reiniciando Sem memory_limiter, ou lote grande demais
Latência da aplicação piorou Exportação síncrona ou fila cheia — use o processador em lote
  • Instrumentação automática ativa.
  • service.name, service.version e deployment.environment preenchidos.
  • Collector no caminho, com redação de dado sensível.
  • Spans manuais só onde agregam, com status de erro marcado.
  • trace_id presente nos logs e link configurado no Grafana.
  • Amostragem definida e verificada.