Pular para o conteúdo

OpenTelemetry

Intermediário20 min de leituraobservabilidade

Antes do OpenTelemetry, trocar de ferramenta de observabilidade significava reinstrumentar todos os serviços: o agente era do fornecedor e o código também. OTel separa as duas coisas — a aplicação emite dados em um formato padrão e para onde eles vão é configuração. É o que torna possível sair de um fornecedor caro sem pedir um trimestre de trabalho a cada time.

  • API: as chamadas que o seu código faz (tracer.Start, counter.Add). Estável e sem dependência de backend. Biblioteca de terceiros instrumentada com a API não força nada.
  • SDK: a implementação — amostragem, processamento em lote, exportação. Configurada por variável de ambiente, geralmente sem tocar no código.
  • Collector: um processo separado que recebe, transforma e envia. É onde você resolve o que não deve morar na aplicação: redigir dado sensível, amostrar pela cauda, cortar atributo caro, mandar para dois destinos durante uma migração.

Comece pela automática: framework HTTP, cliente de banco e fila já rendem trace e métrica úteis sem uma linha de código.

Janela do terminal
# variáveis que o SDK lê — o mesmo nome em todas as linguagens
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_TRACES_SAMPLER=parentbased_traceidratio
export OTEL_TRACES_SAMPLER_ARG=0.1

Depois acrescente spans manuais só onde a lógica de negócio é a resposta que falta:

ctx, span := tracer.Start(ctx, "autorizar-pagamento")
defer span.End()
span.SetAttributes(
attribute.String("gateway", gateway),
attribute.String("pedido.tipo", tipo), // baixa cardinalidade: dá para agrupar
)
if err != nil {
span.RecordError(err)
span.SetStatus(codes.Error, "gateway recusou") // sem isso, o trace parece bem-sucedido
}

Marcar o status de erro é o passo que quase todo mundo esquece — e é justamente o que o tail sampling e os painéis usam para achar o trace que importa.

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

memory_limiter primeiro e batch por último é a ordem recomendada; sem o limitador, um pico de tráfego derruba o Collector e você perde telemetria exatamente durante o incidente.

Implante em duas camadas quando o volume crescer: um agent por nó (DaemonSet), que coleta perto da aplicação, e um gateway central (Deployment com várias réplicas), onde ficam amostragem por cauda e exportação.

Atributo com nome padronizado é o que faz painel e alerta funcionarem em serviços que times diferentes escreveram: http.request.method, http.route, url.path, db.system.name, service.name, deployment.environment. Invente nome próprio só para o que é do seu domínio, com prefixo da empresa. As convenções ainda evoluem — fixe a versão da biblioteca e leia o changelog antes de subir, porque renomear atributo quebra painel.

Migre por serviço, não em big bang, e use o Collector para exportar para os dois destinos durante a transição. Assim você compara números antes de desligar o antigo — e descobre cedo que o “erro” que sumiu era só uma definição diferente de erro. Ordem que costuma funcionar: traces primeiro (onde o ganho é maior), métricas depois, logs por último.

Próximo passo: com dado bom entrando, decida o que merece acordar alguém em Alertas que não viram ruído.