Observabilidade no Spring Boot em 2026: OpenTelemetry, Tracing Distribuído e Perguntas de Entrevista

Guia completo sobre observabilidade no Spring Boot com OpenTelemetry e tracing distribuído. Configuração OTLP, Observation API, propagação de contexto e perguntas técnicas para entrevistas backend.

Observabilidade no Spring Boot com OpenTelemetry e tracing distribuído

A observabilidade no Spring Boot combina logging, métricas e tracing distribuído em um sistema unificado que revela como as requisições fluem através dos microsserviços. Desde o Spring Boot 3, o framework adotou o Micrometer Tracing (substituindo o Spring Cloud Sleuth) e introduziu a Observation API, que fornece um único ponto de instrumentação que emite tanto métricas quanto traces.

O Padrão Observation API

O Spring Boot recomenda usar Observation.observe() em vez de chamar o OpenTelemetry diretamente. Uma única chamada de instrumentação produz métricas via Micrometer e traces via bridge OpenTelemetry, reduzindo duplicação de código e garantindo nomenclatura consistente de tags entre os sinais.

Como a Observation API Conecta Micrometer e OpenTelemetry

A Observation API atua como uma fachada sobre métricas e tracing. Quando o código chama Observation.createNotStarted(), o Spring Boot roteia a observação para os handlers registrados: MeterObservationHandler para métricas do Micrometer e TracingObservationHandler para traces distribuídos. Essa arquitetura significa que instrumentar uma vez exporta para todos os lugares.

A dependência bridge micrometer-tracing-bridge-otel conecta o Micrometer Tracing ao SDK do OpenTelemetry. Os traces fluem através do SdkTracerProvider do OpenTelemetry e exportam via OTLP para backends como Jaeger, Tempo, ou qualquer coletor compatível com OpenTelemetry.

ObservabilityConfig.javajava
@Configuration
public class ObservabilityConfig {

    @Bean
    public ObservationRegistryCustomizer<ObservationRegistry> addLowCardinalityTags() {
        return registry -> registry.observationConfig()
            .observationHandler(new ObservationTextPublisher()); // Logs observations to console
    }
}

A configuração acima registra um ObservationHandler que registra cada observação em logs. Em produção, os handlers auto-configurados enviam dados para os registros do Micrometer e exportadores do OpenTelemetry sem código adicional.

Dependências Necessárias para Tracing no Spring Boot 3.4

O Spring Boot 3.4 requer dependências explícitas para tracing distribuído. O spring-boot-starter-actuator fornece a Observation API, mas o tracing precisa do bridge OpenTelemetry e um exportador.

xml
<!-- pom.xml -->
<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-actuator</artifactId>
    </dependency>
    <dependency>
        <groupId>io.micrometer</groupId>
        <artifactId>micrometer-tracing-bridge-otel</artifactId>
    </dependency>
    <dependency>
        <groupId>io.opentelemetry</groupId>
        <artifactId>opentelemetry-exporter-otlp</artifactId>
    </dependency>
</dependencies>

O artefato micrometer-tracing-bridge-otel faz a ponte entre o Micrometer Tracing e a API do OpenTelemetry. O opentelemetry-exporter-otlp envia spans para os coletores usando o protocolo OTLP sobre HTTP ou gRPC. O Spring Boot gerencia o alinhamento de versões através de sua gestão de dependências.

Configuração da Exportação OTLP para Jaeger ou Tempo

O Spring Boot auto-configura um OtlpHttpSpanExporter quando a dependência OTLP está presente. O exportador envia traces para o endpoint especificado no application.yml.

yaml
# application.yml
management:
  tracing:
    sampling:
      probability: 1.0  # 100% sampling for dev, reduce in production
  otlp:
    tracing:
      endpoint: http://localhost:4318/v1/traces
  opentelemetry:
    resource-attributes:
      service.name: order-service
      deployment.environment: staging

Os resource-attributes anexam metadados a cada span. Backends como Grafana Tempo e Jaeger usam esses atributos para agrupar traces por serviço e ambiente. Definir sampling.probability como 1.0 captura todas as requisições durante desenvolvimento, mas sistemas de produção tipicamente amostram entre 1% e 10% para controlar custos de armazenamento.

Criar Observations Personalizadas com Tags de Cardinalidade Baixa e Alta

As observations suportam dois tipos de tags: cardinalidade baixa para métricas (valores limitados como métodos HTTP ou códigos de status) e cardinalidade alta para traces (valores ilimitados como IDs de usuário ou IDs de requisição).

PaymentService.javajava
@Service
public class PaymentService {

    private final ObservationRegistry observationRegistry;

    public PaymentService(ObservationRegistry observationRegistry) {
        this.observationRegistry = observationRegistry;
    }

    public PaymentResult processPayment(PaymentRequest request) {
        return Observation.createNotStarted("payment.process", observationRegistry)
            .lowCardinalityKeyValue("payment.method", request.getMethod().name())  // enum: CARD, BANK_TRANSFER
            .lowCardinalityKeyValue("currency", request.getCurrency())              // bounded: USD, EUR, GBP
            .highCardinalityKeyValue("payment.id", request.getPaymentId())          // unique per request
            .highCardinalityKeyValue("customer.id", request.getCustomerId())        // high cardinality
            .observe(() -> executePayment(request));
    }

    private PaymentResult executePayment(PaymentRequest request) {
        // Payment gateway call
        return new PaymentResult(true, "TXN-" + UUID.randomUUID());
    }
}

Tags de cardinalidade baixa aparecem tanto em métricas quanto em traces. Tags de cardinalidade alta aparecem apenas em traces porque métricas com dimensões ilimitadas explodem o armazenamento. Essa distinção previne as bombas de cardinalidade do Prometheus enquanto mantém os traces ricos em detalhes.

Pergunta de Entrevista: Cardinalidade de Tags

Entrevistadores frequentemente perguntam por que algumas tags devem ser excluídas das métricas. A resposta envolve cardinalidade: uma tag de ID de usuário em uma métrica cria uma série temporal por usuário, potencialmente milhões de séries. Backends de monitoramento têm dificuldade com alta cardinalidade, levando ao esgotamento de memória e consultas lentas. Traces lidam com alta cardinalidade através de amostragem, tornando-os o lugar correto para identificadores específicos de requisição.

Pronto para mandar bem nas entrevistas de Spring Boot?

Pratique com nossos simuladores interativos, flashcards e testes tecnicos.

Propagação do Contexto de Trace Através de Fronteiras Assíncronas

O tracing distribuído requer propagação de contexto. Headers HTTP carregam IDs de trace entre serviços, mas operações assíncronas como métodos @Async ou cadeias de CompletableFuture podem perder contexto se não configuradas corretamente.

O Spring Boot 3.4 fornece propagação automática para streams reativos e métodos @Async quando configurado:

yaml
# application.yml
spring:
  reactor:
    context-propagation: auto
  task:
    execution:
      propagate-context: true

Para beans TaskExecutor personalizados, envolvê-los com ContextPropagatingTaskDecorator:

AsyncConfig.javajava
@Configuration
@EnableAsync
public class AsyncConfig {

    @Bean
    public TaskExecutor taskExecutor() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        executor.setCorePoolSize(10);
        executor.setMaxPoolSize(50);
        executor.setTaskDecorator(new ContextPropagatingTaskDecorator());
        executor.initialize();
        return executor;
    }
}

O decorator copia a Observation atual e o contexto de trace para a thread assíncrona. Sem ele, spans iniciados em tarefas async não têm pai, quebrando a árvore de traces.

Uso das Anotações @Observed e @NewSpan

O Spring Boot 3.4 suporta observabilidade declarativa através de anotações. Habilitar o processamento de anotações adicionando o weaver do AspectJ:

xml
<!-- pom.xml -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-aop</artifactId>
</dependency>
yaml
# application.yml
management:
  observations:
    annotations:
      enabled: true
InventoryService.javajava
@Service
public class InventoryService {

    @Observed(name = "inventory.check", contextualName = "checkStock")
    public StockLevel checkStock(String productId) {
        // Database query
        return new StockLevel(productId, 42);
    }

    @NewSpan("inventory.reserve")  // Creates a child span under the current trace
    public ReservationResult reserveStock(String productId, int quantity) {
        // Update inventory
        return new ReservationResult(true, "RES-" + System.currentTimeMillis());
    }
}

@Observed cria tanto uma métrica (timer) quanto um span. @NewSpan cria apenas um span, útil quando a operação já tem métricas em outro lugar. Ambas as anotações lidam automaticamente com o registro de exceções e o status do span.

Componentes Auto-Instrumentados no Spring Boot 3.4

O Spring Boot 3.4 auto-instrumenta vários componentes sem mudanças de código:

ComponenteNome da ObservationO Que Captura
Spring MVChttp.server.requestsCaminho da requisição, método, status, exceção
WebClienthttp.client.requestsChamadas HTTP de saída
RestClienthttp.client.requestsChamadas HTTP de saída (Spring 6.1+)
Spring Kafkaspring.kafka.listenerGrupo de consumidores, tópico, partição
Spring Data JPAspring.data.repositoryMétodo do repositório, tempo de consulta
Tarefas Agendadasspring.schedulingNome da tarefa, tempo de execução

A documentação do Spring Boot Actuator lista todos os componentes auto-instrumentados. Bibliotecas de terceiros como Datasource Micrometer adicionam tracing de consultas JDBC.

Filtrar Observations para Reduzir Ruído

Health checks, probes de disponibilidade e recursos estáticos geram ruído nos traces. Filtrá-los usando predicados:

ObservationFilterConfig.javajava
@Configuration
public class ObservationFilterConfig {

    @Bean
    public ObservationPredicate noHealthChecks() {
        return (name, context) -> !name.equals("http.server.requests")
            || !isHealthEndpoint(context);
    }

    private boolean isHealthEndpoint(Observation.Context context) {
        if (context instanceof ServerRequestObservationContext http) {
            String path = http.getCarrier().getRequestURI();
            return path.startsWith("/actuator/health") || path.startsWith("/actuator/ready");
        }
        return false;
    }
}

Alternativamente, desabilitar observations por nome na configuração:

yaml
# application.yml
management:
  observations:
    enable:
      spring.security: false  # Disable Spring Security observations
      http.server.requests.actuator: false  # Custom predicate name
Contexto de Entrevista: Amostragem vs Filtragem

A amostragem reduz a porcentagem de traces coletados em todos os endpoints. A filtragem remove endpoints específicos completamente. Usar filtragem para endpoints que nunca fornecem valor diagnóstico (health checks, scraping de métricas). Usar amostragem para controlar custos enquanto preserva dados representativos.

Correlação de Logs com IDs de Trace

O Spring Boot 3.4 automaticamente adiciona IDs de trace e span ao MDC (Mapped Diagnostic Context) quando o Micrometer Tracing está ativo. Logback e Log4j2 podem incluir esses IDs na saída dos logs:

xml
<!-- logback-spring.xml -->
<configuration>
    <appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
        <encoder>
            <pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - traceId=%X{traceId} spanId=%X{spanId} - %msg%n</pattern>
        </encoder>
    </appender>
    <root level="INFO">
        <appender-ref ref="CONSOLE" />
    </root>
</configuration>

Com esse padrão, cada linha de log inclui o ID de trace. Buscar logs por ID de trace retorna todas as mensagens de uma única requisição através de todos os serviços, correlacionando logs com spans no Jaeger ou Tempo.

Perguntas de Entrevista Comuns sobre Observabilidade no Spring Boot

Entrevistadores avaliam tanto a compreensão conceitual quanto a experiência prática com observabilidade. Essas perguntas aparecem frequentemente para cargos backend sênior.

P: Qual é a diferença entre Micrometer Tracing e OpenTelemetry?

Micrometer Tracing é uma API agnóstica de fornecedor para tracing distribuído, similar a como o Micrometer abstrai métricas. OpenTelemetry é uma implementação e protocolo específicos. O Spring Boot usa Micrometer Tracing como API e faz bridge para OpenTelemetry para exportação via micrometer-tracing-bridge-otel. Essa camada permite que aplicações troquem de backend sem mudanças de código.

P: Por que o Spring recomenda a Observation API em vez da instrumentação direta do OpenTelemetry?

A Observation API fornece um único ponto de instrumentação que emite tanto métricas quanto traces. Chamadas diretas ao OpenTelemetry produzem apenas traces. Usar Observation.observe() gera uma métrica timer e um span a partir do mesmo código, reduzindo duplicação e garantindo nomenclatura consistente.

P: Como depurar um trace que mostra uma lacuna entre spans?

Lacunas indicam instrumentação ausente ou propagação de contexto quebrada. Verificar se o código usa operações async sem ContextPropagatingTaskDecorator. Verificar se os clientes HTTP estão instrumentados (WebClient, RestClient, ou um RestTemplate envolvido manualmente). Para filas de mensagens, confirmar que os headers de trace se propagam através das propriedades da mensagem.

P: O que acontece se adicionar uma tag de alta cardinalidade a uma métrica?

Cada valor único de tag cria uma nova série temporal. Uma tag de ID de usuário com milhões de usuários cria milhões de séries, esgotando a memória no Prometheus ou outros backends TSDB. A solução é usar highCardinalityKeyValue() em vez de lowCardinalityKeyValue(), o que adiciona a tag apenas aos traces onde alta cardinalidade é esperada.

Construir um Pipeline de Tracing Distribuído com Docker Compose

Uma stack de observabilidade local ajuda a validar a instrumentação antes de implantar em produção. Este exemplo usa o OpenTelemetry Collector e Jaeger:

yaml
# docker-compose.yml
services:
  otel-collector:
    image: otel/opentelemetry-collector-contrib:0.102.0
    command: ["--config", "/etc/otel-collector-config.yml"]
    volumes:
      - ./otel-collector-config.yml:/etc/otel-collector-config.yml
    ports:
      - "4318:4318"   # OTLP HTTP
      - "4317:4317"   # OTLP gRPC

  jaeger:
    image: jaegertracing/jaeger:2.3
    ports:
      - "16686:16686"  # UI
    environment:
      - COLLECTOR_OTLP_ENABLED=true

  app:
    build: .
    environment:
      - MANAGEMENT_OTLP_TRACING_ENDPOINT=http://otel-collector:4318/v1/traces
    depends_on:
      - otel-collector
yaml
# otel-collector-config.yml
receivers:
  otlp:
    protocols:
      http:
        endpoint: 0.0.0.0:4318
      grpc:
        endpoint: 0.0.0.0:4317

processors:
  batch:
    timeout: 1s
    send_batch_size: 1024

exporters:
  otlp/jaeger:
    endpoint: jaeger:4317
    tls:
      insecure: true

service:
  pipelines:
    traces:
      receivers: [otlp]
      processors: [batch]
      exporters: [otlp/jaeger]

O coletor recebe spans da aplicação Spring Boot e os encaminha para o Jaeger. Essa arquitetura permite adicionar exportadores (Tempo, Zipkin, backends cloud) sem alterar o código da aplicação.

Comece a praticar!

Teste seus conhecimentos com nossos simuladores de entrevista e testes tecnicos.

Pontos-Chave para Observabilidade no Spring Boot

  • A Observation API unifica métricas e tracing: uma única chamada Observation.observe() produz tanto uma métrica timer quanto um span, eliminando instrumentação dupla.
  • Adicionar micrometer-tracing-bridge-otel e opentelemetry-exporter-otlp para habilitar exportação OTLP. O Spring Boot 3.4 auto-configura o exportador HTTP com o endpoint de management.otlp.tracing.endpoint.
  • Usar lowCardinalityKeyValue() para dimensões que aparecem em métricas (valores limitados). Usar highCardinalityKeyValue() para dados apenas em traces (IDs de usuário, IDs de requisição).
  • Habilitar propagação de contexto para código async com spring.task.execution.propagate-context=true e envolver executors personalizados com ContextPropagatingTaskDecorator.
  • Filtrar endpoints ruidosos como /actuator/health usando beans ObservationPredicate para manter os traces focados em operações de negócio.
  • Os padrões de log devem incluir %X{traceId} para correlacionar linhas de log com traces distribuídos no backend de observabilidade.
  • As perguntas de entrevista sobre observabilidade focam em cardinalidade, falhas de propagação de contexto e a distinção entre a Observation API e o uso direto do OpenTelemetry.
Desafio do dia

Você saberia encontrar o bug em Spring Boot?

Um trecho real, um bug escondido, uma tentativa por dia. Sem conta para testar.

Anthony Fillion-Maillet

Escrito por

Anthony Fillion-Maillet

Fundador da SharpSkill

Desenvolvedor fullstack há mais de 10 anos. Dirige a SharpSkill e responde por tudo o que é publicado aqui.

Atualizado em 19 de setembro de 2026

Tags

#spring-boot
#observability
#opentelemetry
#distributed-tracing
#micrometer

Compartilhar

Artigos relacionados