OpenTelemetry: instrumentação padronizada
1. O Problema da Instrumentação Fragmentada
1.1. Heterogeneidade de formatos e protocolos
Em arquiteturas distribuídas modernas, cada time frequentemente escolhe sua própria stack de observabilidade. Um serviço pode exportar traces no formato Jaeger, outro no formato Zipkin, enquanto métricas vão para o Prometheus. O resultado é um ecossistema fragmentado onde comparar o comportamento de serviços distintos exige ferramentas diferentes, dashboards desconexos e conhecimento especializado em múltiplos protocolos.
# Exemplo de fragmentação: três serviços com instrumentações diferentes
Serviço A → Jaeger (UDP Thrift)
Serviço B → Zipkin (HTTP JSON)
Serviço C → Prometheus (text/plain)
1.2. Acoplamento entre código de negócio e bibliotecas de telemetria
Cada biblioteca de telemetria introduz dependências diretas no código de negócio. Se o time decide migrar de Jaeger para Zipkin, é necessário alterar chamadas de API, inicialização de clientes e configurações de exportação em cada serviço. Esse acoplamento torna a troca de provedores de observabilidade um esforço de refatoração significativo.
# Código acoplado a Jaeger
import io.jaegertracing.Configuration;
Tracer tracer = Configuration.fromEnv().getTracer();
Span span = tracer.buildSpan("operacao").start();
1.3. Dificuldade de comparar métricas e traces entre times e serviços
Sem uma nomenclatura padronizada, o nome de uma operação "criarPedido" pode ser "createOrder" em outro serviço, impossibilitando a correlação automática. A ausência de contexto compartilhado impede que arquitetos identifiquem gargalos que atravessam domínios.
2. OpenTelemetry como Padrão Arquitetural
2.1. Visão geral: uma especificação única e multi-provedor
OpenTelemetry (OTel) é uma especificação aberta que define APIs, SDKs e protocolos para coleta de traces, métricas e logs. Sua principal contribuição arquitetural é fornecer uma camada de abstração que desacopla o código de negócio dos provedores de observabilidade.
2.2. Componentes-chave: API, SDK, Collector e protocolo OTLP
A arquitetura do OTel é composta por quatro elementos fundamentais:
- API: Define interfaces genéricas para criação de spans, métricas e logs.
- SDK: Implementa a API com comportamentos configuráveis (amostragem, processamento em lote).
- Collector: Componente intermediário que recebe, processa e exporta dados.
- OTLP: Protocolo binário eficiente para transporte de telemetria.
# Configuração mínima de OTel SDK (Java)
OpenTelemetrySdk.builder()
.setTracerProvider(
SdkTracerProvider.builder()
.addSpanProcessor(BatchSpanProcessor.builder(OtlpGrpcSpanExporter.builder().build()).build())
.build()
)
.build();
2.3. Decisão arquitetural: abstrair o vendor de observabilidade via OTel
Ao adotar OTel, a arquitetura de software ganha independência de vendor. O Collector pode rotear dados para Jaeger, Zipkin, Datadog, New Relic ou sistemas próprios sem alterar uma linha de código nos serviços.
3. Instrumentação Manual vs. Automática
3.1. Instrumentação automática: agentes e bibliotecas de injeção
A instrumentação automática utiliza agentes (Java, .NET, Python) que interceptam chamadas a bibliotecas conhecidas (HTTP, gRPC, JDBC) e criam spans automaticamente. É ideal para adoção rápida em sistemas legados.
# Instrumentação automática Java via agent
java -javaagent:opentelemetry-javaagent.jar \
-Dotel.service.name=meu-servico \
-Dotel.exporter.otlp.endpoint=http://collector:4318 \
-jar meu-servico.jar
3.2. Instrumentação manual: criação de spans, métricas customizadas
Para lógica de negócio específica, a instrumentação manual oferece controle granular. Permite adicionar atributos semânticos, criar spans aninhados e registrar métricas customizadas.
// Instrumentação manual com atributos de negócio
Tracer tracer = openTelemetry.getTracer("meu-servico");
Span span = tracer.spanBuilder("processarPagamento")
.setAttribute("tipo_pagamento", "cartao_credito")
.setAttribute("valor", 150.00)
.startSpan();
try (Scope scope = span.makeCurrent()) {
// lógica de negócio
} finally {
span.end();
}
3.3. Trade-offs: controle vs. manutenção, granularidade vs. esforço
A instrumentação automática cobre 80% dos casos com esforço mínimo, mas não captura lógica de domínio. A manual oferece riqueza semântica, porém exige manutenção contínua. A recomendação arquitetural é usar ambas: automática para infraestrutura, manual para operações de negócio críticas.
4. Propagação de Contexto e Rastreamento Distribuído
4.1. O papel do contexto (W3C Trace Context) na correlação entre serviços
O W3C Trace Context define headers padronizados (traceparent, tracestate) que carregam o identificador único do trace entre serviços. Sem essa propagação, cada serviço gera traces isolados, impossibilitando o rastreamento ponta a ponta.
4.2. Propagação síncrona (HTTP/gRPC) e assíncrona (mensageria)
A propagação síncrona ocorre automaticamente via interceptors HTTP/gRPC. Em sistemas assíncronos (Kafka, RabbitMQ), o contexto precisa ser serializado e desserializado manualmente.
# Propagação de contexto em mensageria (Kafka)
// Produtor: injeta contexto no header da mensagem
TextMapPropagator propagator = OpenTelemetry.getGlobalPropagator();
propagator.inject(Context.current(), kafkaHeaders, (headers, key, value) -> headers.add(key, value));
// Consumidor: extrai contexto do header
Context extractedContext = propagator.extract(Context.current(), kafkaHeaders, (headers, key) -> {
for (Header header : headers) {
if (header.key().equals(key)) return header.value();
}
return null;
});
4.3. Desafios arquiteturais: bagagem, headers e serviços legados
Serviços legados que não entendem headers W3C quebram a cadeia de traces. A solução arquitetural é usar o Collector como proxy de tradução ou implementar adaptadores nos gateways de entrada.
5. O Collector como Ponto de Integração e Filtro
5.1. Arquitetura do Collector: receivers, processors, exporters
O Collector segue um pipeline configurável: recebe dados via receivers (OTLP, Jaeger, Prometheus), processa via processors (filtro, amostragem, transformação) e exporta via exporters.
# Configuração YAML do Collector
receivers:
otlp:
protocols:
grpc:
http:
processors:
batch:
filter:
spans:
include:
match_type: regexp
services: ["servico-critico"]
exporters:
jaeger:
endpoint: jaeger:14250
prometheus:
endpoint: "0.0.0.0:8889"
service:
pipelines:
traces:
receivers: [otlp]
processors: [batch, filter]
exporters: [jaeger]
5.2. Processamento em pipeline: amostragem, redação de dados sensíveis, transformação
O Collector permite aplicar amostragem head-based ou tail-based, redigir campos sensíveis (CPF, cartão de crédito) e transformar nomes de spans para padronização entre times.
5.3. Estratégias de deploy: agent sidecar vs. gateway centralizado
Como sidecar (um Collector por pod/serviço), reduz latência de rede e oferece isolamento. Como gateway centralizado, simplifica gerenciamento e aplica políticas uniformes. A escolha depende da escala e da necessidade de governança.
6. Integração com ADRs e Fitness Functions
6.1. ADR: decisão de adotar OpenTelemetry como camada de abstração
O Architecture Decision Record (ADR) deve registrar:
- Contexto: Fragmentação de ferramentas de observabilidade.
- Decisão: Adotar OTel como padrão único de instrumentação.
- Consequências: Independência de vendor, esforço inicial de migração, necessidade de treinamento.
6.2. Fitness functions para validar a presença de spans e métricas
Fitness functions automatizadas podem verificar se todo novo serviço exporta spans OTel com atributos obrigatórios.
# Fitness function: verificar presença de span de health check
função fitness_otel_healthcheck(serviço):
trace = consultar_otel(serviço, "GET /health")
retornar trace.não_vazio E trace.span.tem_atributo("http.status_code")
6.3. Governança: garantir que novos serviços sigam o padrão OTel
Estabelecer pipelines de CI/CD que rejeitem deploys de serviços sem instrumentação OTel. Utilizar o Collector como ponto de verificação: se um serviço não envia dados OTLP, o pipeline gera alerta.
7. Impacto na Idempotência e Observabilidade
7.1. Correlação entre traces e idempotência: como spans ajudam a detectar retentativas
Spans com atributos de idempotência (ex: idempotency_key) permitem rastrear requisições duplicadas. Um trace que mostra múltiplas execuções do mesmo idempotency_key indica problema de idempotência.
7.2. Métricas de idempotência (taxa de duplicatas) exportadas via OTel
// Métrica customizada de taxa de duplicatas
Meter meter = openTelemetry.getMeter("meu-servico");
DoubleCounter duplicatasCounter = meter
.counterBuilder("requisicoes.duplicadas")
.setDescription("Taxa de requisições com idempotency_key repetida")
.build();
duplicatasCounter.add(1, Attributes.of(AttributeKey.stringKey("servico"), "pagamento"));
7.3. Logs estruturados com contexto OTel: unificação de logs, métricas e traces
Ao correlacionar logs com trace_id e span_id, é possível navegar do log ao trace e à métrica. O Collector pode ingerir logs via OTLP, unificando os três pilares da observabilidade.
8. Considerações Finais e Próximos Passos
8.1. Migração gradual de sistemas legados para OTel
Iniciar pelos serviços críticos com instrumentação automática. Em paralelo, configurar o Collector para aceitar formatos legados (Jaeger, Zipkin) e converter para OTLP. Gradualmente, migrar a instrumentação manual.
8.2. Custos de infraestrutura: amostragem adaptativa e cardinalidade
A cardinalidade excessiva de métricas (muitos atributos únicos) pode elevar custos de armazenamento. Utilizar amostragem adaptativa baseada em latência ou erro, e limitar atributos de alta cardinalidade no Collector.
8.3. A evolução do OpenTelemetry no ecossistema de arquitetura de software
O OTel está convergindo logs, métricas e traces em um único padrão. A tendência é que se torne o padrão de facto para observabilidade, assim como REST/HTTP se tornou para APIs. Arquitetos devem investir na adoção agora para evitar dívida técnica futura.
Referências
- OpenTelemetry Official Documentation — Documentação oficial completa da especificação, SDKs e Collector.
- W3C Trace Context Specification — Especificação do padrão W3C para propagação de contexto de traces.
- OpenTelemetry Collector Architecture — Guia detalhado sobre receivers, processors e exporters do Collector.
- Instrumenting Java Applications with OpenTelemetry — Tutorial prático de instrumentação automática e manual em Java.
- OpenTelemetry and Idempotency: A Practical Guide — Artigo técnico sobre correlação entre traces e idempotência em sistemas distribuídos.
- Fitness Functions for Observability — Artigo de Martin Fowler sobre como usar fitness functions para validar padrões de observabilidade.
- OpenTelemetry Semantic Conventions — Convenções semânticas para padronização de atributos em traces, métricas e logs.