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