Destino Dev

Blog

Instrumentando Spring Boot com OpenTelemetry: traces, métricas e logs passo a passo

Por John Pires · 19 de março de 2026

Instrumentando Spring Boot com OpenTelemetry: traces, métricas e logs passo a passo

Por que aprender isso em 2026

Observabilidade deixou de ser luxo e virou requisito. Em 2026, equipes trabalham com microservices, virtual threads e alta escalabilidade: saber como traçar requisições, medir comportamentos e correlacionar logs é essencial para entregar software confiável. OpenTelemetry é o padrão aberto para reunir traces, métricas e logs; aprender a integrá-lo ao Spring Boot economiza tempo e reduz dependência de fornecedores.

Analogia simples: imagine o sistema como uma cidade complexa. Traces são as rotas dos carros, métricas são os contadores de veículos nas avenidas e logs são as câmeras que registram eventos. Sozinhos cada fonte ajuda, juntas permitem entender o que causou um congestionamento.

Para quem está começando, o objetivo aqui é prático: montar um ambiente local, instrumentar endpoints Spring Boot com spans e métricas, e correlacionar logs com trace ids. Vou mostrar duas abordagens: usar o javaagent para auto-instrumentação e montar uma configuração manual com o SDK para entender o que acontece por baixo.

Este artigo foca em Java com Spring Boot, exemplos claros e funcionais. No final você terá um mini-projeto que emite spans, contador de requisições e adiciona trace id nos logs para triagem rápida.

Configuração do ambiente

Requisitos mínimos

Instale Java 17+ (recomendado Java 21 para virtual threads) e Maven. Para visualizar dados localmente use o OpenTelemetry Collector e um backend simples como Tempo (traces) e Prometheus/Grafana (métricas). Opcionalmente, use o exporter de logging para ver spans no console sem infra adicional.

Comandos rápidos:

  • Java: java -version
  • Maven: mvn -v
  • Docker para rodar collector: docker run --rm -p 4317:4317 -p 4318:4318 otel/opentelemetry-collector:latest

Dependências do projeto

Use Spring Initializr com spring-boot-starter-web. No pom.xml adicione dependências do OpenTelemetry SDK e exporters. Abaixo, um trecho de pom.xml minimalista. Ele garante que você tenha a API e o SDK para criar spans e métricas. Contexto: esse pom é para experimento local; em produção prefira dependências gerenciadas.

<dependencies>
  <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
  </dependency>

  <dependency>
    <groupId>io.opentelemetry</groupId>
    <artifactId>opentelemetry-api</artifactId>
    <version>1.30.0</version>
  </dependency>

  <dependency>
    <groupId>io.opentelemetry</groupId>
    <artifactId>opentelemetry-sdk</artifactId>
    <version>1.30.0</version>
  </dependency>

  <dependency>
    <groupId>io.opentelemetry</groupId>
    <artifactId>opentelemetry-exporter-logging</artifactId>
    <version>1.30.0</version>
  </dependency>
</dependencies>

O que esse bloco faz: adiciona a API (para criar spans), o SDK (para processar e exportar) e o exporter de logging (imprime spans no console). O que acontece ao usar essas dependências: você poderá iniciar um SdkTracerProvider local e ver spans detalhados sem precisar de collector.

Alternativa rápida: usar o opentelemetry javaagent. Basta baixar o jar do agente e iniciar sua app com -javaagent:path/to/opentelemetry-javaagent.jar. O agente auto-instrumenta muitas bibliotecas, incluindo spring-web, jdbc e clientes HTTP.

# Executar com agente
java -javaagent:opentelemetry-javaagent.jar -jar target/myapp.jar

O que acontece: o agente injeta instrumentação sem alterar código. Útil para começar, mas não substitui controle fino sobre spans customizados.

Hello World progressivo

Aplicação Spring Boot básica

Vamos começar com um endpoint simples que responde 'hello' e registrar um span manualmente para entender o ciclo do span. Primeiro, um Application.java padrão e um controller com injeção do tracer via API OpenTelemetry.

// contexto: classe Spring Boot principal
package com.example.demo;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class DemoApplication {
  public static void main(String[] args) {
    SpringApplication.run(DemoApplication.class, args);
  }
}

O que acontece: a aplicação Spring sobe normalmente. Próximo passo é criar um controller que cria spans e atualiza um contador de métricas.

Criando span e contador

Abaixo um controller que cria um span manual e usa um contador do Meter para medir requisições. Contexto: usamos GlobalOpenTelemetry para obter tracer e meter. Após o bloco de código explico o comportamento observado.

// contexto: controller com trace manual e contador
package com.example.demo.controller;

import io.opentelemetry.api.GlobalOpenTelemetry;
import io.opentelemetry.api.trace.Span;
import io.opentelemetry.api.trace.Tracer;
import io.opentelemetry.api.metrics.Meter;
import io.opentelemetry.api.metrics.LongCounter;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class HelloController {
  private final Tracer tracer = GlobalOpenTelemetry.getTracer("com.example.demo.HelloController");
  private final Meter meter = GlobalOpenTelemetry.getMeter("com.example.demo.metrics");
  private final LongCounter requestCounter = meter.longCounterBuilder("hello_requests_total").build();

  @GetMapping("/hello")
  public String hello() {
    Span span = tracer.spanBuilder("hello.handler").startSpan();
    try (var scope = span.makeCurrent()) {
      requestCounter.add(1);
      span.setAttribute("handler.version", "v1");
      // simula trabalho
      return "hello";
    } finally {
      span.end();
    }
  }
}

O que acontece: a chamada a /hello cria um span chamado 'hello.handler', incrementa o contador e termina o span. Se o exporter estiver o logging exporter, você verá a saída do span no console. Se estiver enviando ao collector, verá o span no backend configurado.

Mini-projeto prático

O mini-projeto une tudo: inicialização do SDK, controller instrumentado, correlação de logs com trace id e um endpoint que simula uma chamada a banco (sincrônica). O objetivo é rodar localmente e observar spans e métricas no console ou collector.

Configuração do SDK (manual)

Para aprender o fluxo, monta-se um config que cria SdkTracerProvider, adiciona LoggingSpanExporter e registra o provedor global. Código abaixo:

// contexto: configuração manual do OpenTelemetry SDK
package com.example.demo.config;

import io.opentelemetry.api.GlobalOpenTelemetry;
import io.opentelemetry.sdk.OpenTelemetrySdk;
import io.opentelemetry.sdk.resources.Resource;
import io.opentelemetry.sdk.trace.SdkTracerProvider;
import io.opentelemetry.sdk.trace.export.SimpleSpanProcessor;
import io.opentelemetry.exporter.logging.LoggingSpanExporter;
import jakarta.annotation.PostConstruct;
import org.springframework.context.annotation.Configuration;

@Configuration
public class OpenTelemetryConfig {
  @PostConstruct
  public void initOpenTelemetry() {
    var resource = Resource.getDefault();
    var loggingExporter = new LoggingSpanExporter();
    var tracerProvider = SdkTracerProvider.builder()
      .addSpanProcessor(SimpleSpanProcessor.create(loggingExporter))
      .setResource(resource)
      .build();

    var openTelemetrySdk = OpenTelemetrySdk.builder().setTracerProvider(tracerProvider).buildAndRegisterGlobal();

    // O que acontece: registramos o SDK global. Spans criados via GlobalOpenTelemetry serão processados
    // e exportados pelo LoggingSpanExporter para o console.
  }
}

O que acontece: ao iniciar a aplicação, o PostConstruct configura o SDK e o registra globalmente. Spans criados com GlobalOpenTelemetry.getTracer(...).spanBuilder(...) serão exportados por logging.

Correndo logs com trace id

Logs isolados têm pouca utilidade. A prática é anexar o trace id ao MDC do logger para correlacionar. O trecho abaixo mostra como extrair trace id do span atual e colocar no MDC antes de logar.

// contexto: utilitário simples para adicionar trace id ao MDC
package com.example.demo.logging;

import io.opentelemetry.api.trace.Span;
import io.opentelemetry.api.trace.SpanContext;
import org.slf4j.MDC;

public class TraceLogging {
  public static void putTraceIdInMdc() {
    Span current = Span.current();
    SpanContext ctx = current.getSpanContext();
    if (ctx.isValid()) {
      MDC.put("traceId", ctx.getTraceId());
    } else {
      MDC.remove("traceId");
    }
  }
}

O que acontece: chame TraceLogging.putTraceIdInMdc() no início de cada request (por exemplo, um filtro Spring) e configure o pattern de log (logback) para imprimir %X{traceId}. Assim cada log ficará legível com o trace id associado.

Erros comuns, armadilhas e dicas práticas

Erros comuns

Listo abaixo problemas frequentes ao começar com OpenTelemetry e Spring Boot e como resolvê-los.

  1. Sem spans sendo exportados: verifica se o SDK foi registrado globalmente ou se você esqueceu de iniciar o agente quando depende do javaagent.
  2. Trace id não aparece nos logs: não colocou o trace id no MDC via filtro; lembre-se de limpar o MDC para evitar poluição entre threads.
  3. Métricas faltando: alguns exporters e versões do SDK exigem configuração específica do MeterProvider; confirme a versão e registre o meter globalmente.
  4. Dupla instrumentação: não rode agente e SDK manual simultaneamente sem entender o comportamento; você pode gerar spans duplicados.

Dicas práticas

Use o logging exporter para desenvolvimento rápido. Para produção, envie ao Collector via OTLP. Configure recursos (service.name) para identificar serviços no backend. Teste com chamadas locais e verifique os IDs de trace em logs para validar correlação.

Tip: se usar virtual threads, lembre-se de usar context propagation compatível. OpenTelemetry usa Context para propagar span atual; verifique compatibilidade da versão com virtual threads.

Outro ponto: timeouts de exportação. Em ambientes com alto volume, prefira batch span processors para reduzir overhead e enviar spans em lote.

Recursos para continuar estudando

Depois de entender o fluxo básico, aprofunde em:

  • Documentação oficial OpenTelemetry Java
  • OpenTelemetry Collector: pipelines, receivers e exporters
  • Micrometer + OpenTelemetry bridge para métricas integradas ao ecossistema Spring
  • Instrumentação automática com javaagent e personalização via configuration

Pratique adicionando spans a chamadas de banco de dados, filas e chamadas HTTP para ver traces distribuídos. Use um backend como Tempo + Grafana para visualizar sequências de spans e prometheus/grafana para métricas.

Estude também como amostrar spans (sampling) para controlar custo e volume em produção. Entenda as diferenças entre sampler probabilístico e baseado em cabeçalho (trace-flags).

Conclusão prática

Resumo objetivo: configure o ambiente, escolha auto-instrumentação para começar rápido ou SDK manual para controle. Instrumente endpoints com spans simples, exponha métricas básicas e adicione trace id aos logs. Valide localmente com LoggingSpanExporter e depois configure o Collector para produção.

Próximo passo recomendado: rode a aplicação com -javaagent e compare com a versão manual. Observe duplicações e compreenda o fluxo de context propagation. Isso solidifica a compreensão e prepara para instrumentar sistemas reais em 2026.

Se precisar, posso fornecer um projeto Git minimal com o pom.xml completo, configuração do collector em docker-compose e instruções passo a passo para rodar e visualizar spans e métricas localmente.

Assuntos: spring-boot, opentelemetry, tracing, metrics, logs

Leia também