OpenTelemetry: padrão unificado para trace, log e métrica
1. Visão geral
Quando um sistema se divide em múltiplos serviços, uma requisição pode passar por cinco, dez processos diferentes. Quando há um erro, você precisa saber por onde a requisição foi, em qual passo ela ficou lenta e quais logs pertencem àquela requisição. OpenTelemetry (OTel) é um padrão aberto que resolve exatamente isso. Ele unifica a forma de coletar traces, métricas e logs, depois os envia para qualquer backend.
Ao terminar de ler este artigo, você entenderá os conceitos essenciais do OTel e por que está recebendo tanta atenção. Você também saberá como integrar OTel em uma aplicação Spring Boot 3, executar um Collector, escrever testes para a parte de instrumentação e reconhecer quando não deve usá-lo.
2. O que é OpenTelemetry?
OpenTelemetry é um projeto de código aberto sob o CNCF. Ele define como criar, descrever e transmitir dados de telemetria. OTel não é um local de armazenamento nem uma interface de visualização. Essa parte fica a cargo de backends como Jaeger, Prometheus, Grafana Tempo/Loki ou serviços comerciais.
2.1. Três tipos de sinais
- Trace: a jornada de uma requisição. Cada trace é composto de múltiplos spans, sendo cada span uma unidade de trabalho, como “chamar PaymentService” ou “consultar tabela orders”. Um span tem hora de início, hora de término e atributos.
- Métrica: números agregados ao longo do tempo, como requisições por segundo, latência p99 ou número de pedidos falhados.
- Log: registros de eventos discretos. OTel adiciona
traceIdespanIdao log, permitindo que você pule de uma linha de log para o trace exato dela.
O maior valor está na correlação. Todos os três sinais compartilham um contexto comum e uma descrição comum de recursos, como service.name.
2.2. Componentes principais
| Componente | Função |
|---|---|
| API | Interface para que o código crie spans e métricas, independente de vendor |
| SDK | Implementação da API: sampling, batching, export |
| OTLP | Protocolo de transmissão de dados (gRPC ou HTTP/protobuf) |
| Collector | Processo independente que recebe, processa e encaminha telemetria |
| Semantic conventions | Convenções de nomes para atributos como http.request.method, db.system… |
| Instrumentação | Bibliotecas ou agent que geram automaticamente telemetria para frameworks populares |
2.3. Context propagation
Para que spans em múltiplos serviços se juntem em um único trace, o contexto deve acompanhar a requisição. Com HTTP, OTel usa por padrão o padrão W3C Trace Context pelo header traceparent:
traceparent: 00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01
Esse header tem quatro partes: versão, traceId, spanId do span pai e flag de sampling. O serviço que recebe a requisição lê esse header e cria um span filho pertencendo ao mesmo trace.
3. Por que OpenTelemetry está recebendo atenção?
- Neutral com vendor: você instrumenta o código uma vez. Se quiser trocar backend, só muda a configuração do exporter ou do Collector, sem alterar o código.
- Unifica padrões antigos: OTel nasceu da fusão de OpenTracing e OpenCensus. A comunidade não precisa mais escolher entre dois caminhos.
- Um padrão para os três sinais: anteriormente, cada tipo de dado tinha seu próprio agent e formato.
- Ecossistema amplo: muitos frameworks, cloud providers e ferramentas de observability suportam OTLP. Spring Boot 3 também se integra via Micrometer.
- Collector flexível: você filtra dados sensíveis, coleta amostras e roteia para múltiplos backends na camada de infraestrutura, sem tocar na aplicação.
4. Aplicando OpenTelemetry em Spring Boot 3
Spring Boot 3 usa Micrometer Observation como camada de abstração. Você escreve uma observation, Micrometer gera métrica (timer) e span. A ponte para OTel cuida da exportação.
4.1. Dependência
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<!-- Ponte Micrometer Tracing -> OpenTelemetry -->
<dependency>
<groupId>io.micrometer</groupId>
<artifactId>micrometer-tracing-bridge-otel</artifactId>
</dependency>
<!-- Exportar trace via OTLP -->
<dependency>
<groupId>io.opentelemetry</groupId>
<artifactId>opentelemetry-exporter-otlp</artifactId>
</dependency>
<!-- Exportar métrica via OTLP -->
<dependency>
<groupId>io.micrometer</groupId>
<artifactId>micrometer-registry-otlp</artifactId>
</dependency>
<!-- Para usar em testes -->
<dependency>
<groupId>io.micrometer</groupId>
<artifactId>micrometer-observation-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
Você não precisa especificar versões. Spring Boot gerencia as versões das bibliotecas Micrometer e OpenTelemetry através de um BOM.
4.2. Configuração
spring:
application:
name: order-service
management:
tracing:
sampling:
probability: 1.0 # dev: amostrar 100%; produção deve ser reduzido
otlp:
tracing:
endpoint: http://localhost:4318/v1/traces
metrics:
export:
url: http://localhost:4318/v1/metrics
step: 30s
logging:
pattern:
level: "%5p [${spring.application.name:},%X{traceId:-},%X{spanId:-}]"
O significado de cada grupo de configuração:
spring.application.nameé usado comoservice.name. Este é o atributo mais importante para o backend diferenciar os serviços.management.tracing.sampling.probabilitydefine a proporção de traces que são registrados.- Os dois endpoints
otlpapontam para o Collector via OTLP/HTTP, a porta padrão é 4318. logging.pattern.levelinseretraceIdespanIddo MDC em cada linha de log. Essa é a forma mais simples de correlacionar logs com traces.
4.3. Instrumentar regras de negócio usando a API Observation
Requisições HTTP que chegam ao controller e chamadas via RestClient/WebClient já são instrumentadas automaticamente pelo Spring. A parte que você precisa adicionar são os passos de negócio importantes:
public record Order(String id, String paymentMethod, long amount) {}
@FunctionalInterface
public interface PaymentService {
String charge(Order order);
}
@Service
public class CheckoutService {
private static final Logger log = LoggerFactory.getLogger(CheckoutService.class);
private final ObservationRegistry registry;
private final PaymentService paymentService;
public CheckoutService(ObservationRegistry registry, PaymentService paymentService) {
this.registry = registry;
this.paymentService = paymentService;
}
public String checkout(Order order) {
return Observation.createNotStarted("order.checkout", registry)
.lowCardinalityKeyValue("payment.method", order.paymentMethod())
.highCardinalityKeyValue("order.id", order.id())
.observe(() -> {
log.info("Processando pedido {}", order.id());
return paymentService.charge(order);
});
}
}
Pontos importantes no código acima:
Observation.createNotStarted("order.checkout", registry)cria uma observation chamadaorder.checkout.lowCardinalityKeyValueadiciona um atributo com poucos valores (CARD,CASH,WALLET). Esse atributo aparece tanto em métrica quanto em span.highCardinalityKeyValueadiciona um atributo com muitos valores como ID do pedido. Ele apenas é anexado ao span, não inflaciona a métrica.observe(...)abre o span, mede o tempo, registra exceção se houver e fecha o span quando o supplier retorna.
Ao executar, o log ficará parecido com:
INFO [order-service,4bf92f3577b34da6a3ce929d0e0e4736,a1b2c3d4e5f60718] ... Processando pedido A-1001
No lado do backend você recebe dois dados:
- Um span
order.checkoutque fica sob o span HTTP da requisição. - Uma métrica de timer
order.checkout, dividida porpayment.method. Dessa métrica você calcula número de chamadas, tempo total e tempo máximo.
Nota: Prefira a API Observation em vez de chamar diretamente
io.opentelemetry.api.trace.Tracerno código Spring. Seu código não ficará dependente de uma biblioteca de tracing específica. Uma única medição também gera métrica e trace.
4.4. Alternativa: Java agent
Se não quiser modificar código nem adicionar dependências, você pode usar o OpenTelemetry Java agent. O agent instrumenta automaticamente muitas bibliotecas populares como JDBC, HTTP client e Kafka:
export OTEL_SERVICE_NAME=order-service
export OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:4318
export OTEL_TRACES_SAMPLER=parentbased_traceidratio
export OTEL_TRACES_SAMPLER_ARG=0.1
java -javaagent:./opentelemetry-javaagent.jar -jar order-service.jar
O agent é adequado para aplicações legadas ou quando o time de operações quer centralizar o controle da instrumentação. O tradeoff é que você tem menos controle fino, o tempo de startup aumenta e o agent funciona como uma “caixa preta” interferindo no bytecode.
Nota: Não execute o Java agent e o bridge Micrometer simultaneamente para o mesmo tipo de sinal. Você receberá spans duplicados ou contexto corrompido.
5. Executando OpenTelemetry Collector
A aplicação deve enviar dados para o Collector em vez de enviá-los diretamente ao backend. O Collector é onde você faz batch, filtra e roteia dados. Abaixo está uma configuração mínima:
receivers:
otlp:
protocols:
grpc:
endpoint: 0.0.0.0:4317
http:
endpoint: 0.0.0.0:4318
processors:
batch: {}
attributes/strip-pii:
actions:
- key: customer.email
action: delete
exporters:
debug:
verbosity: detailed
service:
pipelines:
traces:
receivers: [otlp]
processors: [attributes/strip-pii, batch]
exporters: [debug]
metrics:
receivers: [otlp]
processors: [batch]
exporters: [debug]
logs:
receivers: [otlp]
processors: [batch]
exporters: [debug]
Essa configuração tem quatro partes:
receivers.otlprecebe dados via gRPC (4317) e HTTP (4318).attributes/strip-piiremove o atributocustomer.emailantes dos dados saírem da sua infraestrutura.batchagrupa dados antes de enviar para reduzir o número de requisições.- O exporter
debugimprime dados no console para você verificar. Em produção, você o substitui pelo exporter do seu backend. Versões antigas do Collector usavamloggingcomo nome desse exporter.
Inicie o Collector com Docker:
docker run --rm -p 4317:4317 -p 4318:4318 \
-v "$(pwd)/otel-collector.yaml:/etc/otelcol-contrib/config.yaml" \
otel/opentelemetry-collector-contrib
Faça uma chamada de teste para a API de checkout e verifique o console do Collector. Você verá spans assim:
Span #0
Trace ID : 4bf92f3577b34da6a3ce929d0e0e4736
Parent ID : 9a8b7c6d5e4f3a2b
Name : order.checkout
Attributes:
-> payment.method: Str(CARD)
-> order.id: Str(A-1001)
6. Testando a instrumentação
Instrumentação também é código, então também precisa ser testada. TestObservationRegistry permite que você verifique observations sem iniciar o contexto do Spring ou o Collector:
import io.micrometer.observation.tck.TestObservationRegistry;
import io.micrometer.observation.tck.TestObservationRegistryAssert;
import org.junit.jupiter.api.Test;
import static org.assertj.core.api.Assertions.assertThat;
class CheckoutServiceTest {
@Test
void whenCheckout_thenObservationIsRecordedWithPaymentMethod() {
TestObservationRegistry registry = TestObservationRegistry.create();
PaymentService paymentService = order -> "PAID-" + order.id();
CheckoutService service = new CheckoutService(registry, paymentService);
String result = service.checkout(new Order("A-1001", "CARD", 250_000));
assertThat(result).isEqualTo("PAID-A-1001");
TestObservationRegistryAssert.assertThat(registry)
.hasObservationWithNameEqualTo("order.checkout")
.that()
.hasLowCardinalityKeyValue("payment.method", "CARD")
.hasHighCardinalityKeyValue("order.id", "A-1001")
.hasBeenStarted()
.hasBeenStopped();
}
}
Esse teste confirma três coisas:
- O resultado de negócio está correto.
- A observation
order.checkoutfoi criada com os atributos corretos. - A observation foi iniciada e encerrada. Um span que esquecer de encerrar é um erro muito difícil de detectar em produção.
7. Erros comuns
- Explosão de cardinality: colocar
userId,orderIdou URL com parâmetros em tags de métrica faz o número de séries de métricas aumentar exponencialmente e os custos de armazenamento disparam. Esses valores devem ir apenas em spans. - Amostragem 100% em produção: volume de dados e custos aumentam linearmente com o tráfego. Reduza
probabilityou use tail sampling no Collector para manter traces de erros ou traces lentos. - Perda de contexto ao executar de forma assíncrona: com
@Async,CompletableFutureou thread pools criados manualmente, o contexto não segue automaticamente para a nova thread. Você precisa envolver o executor com um mecanismo de propagação de contexto, comoContextExecutorServiceda biblioteca context-propagation, ou usar um executor gerenciado pelo Spring que já está pré-configurado. - Vazamento de dados sensíveis: emails, números de cartão ou tokens podem vazar em atributos ou logs. Filtre no código e adicione uma camada de filtragem no Collector.
- Nomes arbitrários: não seguir as semantic conventions torna dashboards e alertas difíceis de reutilizar entre serviços.
- Ignorar o Collector: enviar direto da aplicação para o backend significa que toda vez que trocar backend precisa fazer deploy de todos os serviços.
8. Quando NÃO usar (ou ainda não precisa)
- Aplicação pequena, uma instância, poucas dependências: logs estruturados com algumas métricas do Actuator geralmente são suficientes. Distributed tracing quase não traz valor quando não há nada “distribuído”.
- Time ainda sem backend e pessoa de operações: OTel é apenas um duto. Se ninguém for olhar os dados, você só gasta CPU, rede e dinheiro com armazenamento.
- Sistema extremamente sensível a latência ou recursos: como aplicações embarcadas ou hot paths medidos em microsegundos. Meça o overhead antes de ativar instrumentação em larga escala.
- Esperar que OTel substitua todas as ferramentas existentes de uma vez: o nível de maturidade de cada sinal e de cada linguagem pode variar. Verifique o status de suporte do que você pretende usar e faça a migração gradualmente, começando com tracing.
9. Conclusão
OpenTelemetry unifica como criamos e transmitimos traces, métricas e logs através de uma API comum, protocolo OTLP e Collector. Isso permite separar a instrumentação no código da escolha de backend.
Com Spring Boot 3, o caminho mais simples tem quatro passos. Primeiro, use Micrometer Observation para uma única medição gerar métrica e span. Segundo, exporte via OTLP para o Collector. Terceiro, coloque traceId nos logs. Quarto, escreva testes para as observations importantes. Paralelamente, controle cardinality, sampling e dados sensíveis desde o início.
Se seu sistema ainda é pequeno, não se apresse. Quando o número de serviços aumentar e a depuração começar a consumir horas, OTel é uma base que vale a pena investir.