Spring Boot Observability nel 2026: OpenTelemetry, Distributed Tracing e Domande per Colloqui

Padroneggiare l'observability di Spring Boot con OpenTelemetry e Micrometer Tracing. Configurare l'export OTLP, utilizzare la Observation API e prepararsi ai colloqui.

Spring Boot Observability nel 2026: OpenTelemetry, Distributed Tracing e Domande per Colloqui

L'observability di Spring Boot combina logging, metriche e distributed tracing in un sistema unificato che rivela come le richieste fluiscono attraverso i microservizi. A partire da Spring Boot 3, il framework ha adottato Micrometer Tracing (sostituendo Spring Cloud Sleuth) e ha introdotto la Observation API, che fornisce un singolo punto di strumentazione che emette sia metriche che trace.

Il Pattern della Observation API

Spring Boot raccomanda l'utilizzo di Observation.observe() piuttosto che chiamare OpenTelemetry direttamente. Una singola chiamata di strumentazione produce metriche via Micrometer e trace via il bridge OpenTelemetry, riducendo la duplicazione del codice e garantendo una nomenclatura dei tag consistente tra tutti i segnali.

Come la Observation API Collega Micrometer e OpenTelemetry

La Observation API agisce come una facciata su metriche e tracing. Quando il codice chiama Observation.createNotStarted(), Spring Boot instrada l'observation agli handler registrati: MeterObservationHandler per le metriche Micrometer e TracingObservationHandler per i distributed trace. Questa architettura significa strumentare una volta, esportare ovunque.

La dipendenza bridge micrometer-tracing-bridge-otel connette Micrometer Tracing all'SDK di OpenTelemetry. I trace fluiscono attraverso SdkTracerProvider di OpenTelemetry ed esportano via OTLP verso backend come Jaeger, Tempo o qualsiasi collector compatibile con OpenTelemetry.

ObservabilityConfig.javajava
@Configuration
public class ObservabilityConfig {

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

La configurazione sopra registra un ObservationHandler che logga ogni observation. In produzione, gli handler configurati automaticamente inviano i dati ai registry Micrometer e agli esportatori OpenTelemetry senza codice aggiuntivo.

Dipendenze Richieste per Spring Boot 3.4 Tracing

Spring Boot 3.4 richiede dipendenze esplicite per il distributed tracing. Lo spring-boot-starter-actuator fornisce la Observation API, ma il tracing necessita del bridge OpenTelemetry e di un esportatore.

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>

L'artefatto micrometer-tracing-bridge-otel collega Micrometer Tracing all'API di OpenTelemetry. L'opentelemetry-exporter-otlp invia gli span ai collector usando il protocollo OTLP via HTTP o gRPC. Spring Boot gestisce l'allineamento delle versioni attraverso il suo dependency management.

Configurare l'Export OTLP verso Jaeger o Tempo

Spring Boot configura automaticamente un OtlpHttpSpanExporter quando la dipendenza OTLP è presente. L'esportatore invia i trace all'endpoint specificato in 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

I resource-attributes allegano metadati a ogni span. Backend come Grafana Tempo e Jaeger usano questi attributi per raggruppare i trace per servizio e ambiente. Impostare sampling.probability a 1.0 cattura tutte le richieste durante lo sviluppo, ma i sistemi di produzione tipicamente campionano tra l'1% e il 10% per controllare i costi di storage.

Creare Observation Personalizzate con Tag Low e High Cardinality

Le observation supportano due tipi di tag: low cardinality per le metriche (valori limitati come metodi HTTP o codici di stato) e high cardinality per i trace (valori illimitati come ID utente o ID richiesta).

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());
    }
}

I tag low cardinality appaiono sia nelle metriche che nei trace. I tag high cardinality appaiono solo nei trace perché le metriche con dimensioni illimitate fanno esplodere lo storage. Questa distinzione previene le bombe di cardinalità in Prometheus mantenendo ricchi i dettagli dei trace.

Domanda da Colloquio: Cardinalità dei Tag

Gli intervistatori spesso chiedono perché alcuni tag dovrebbero essere esclusi dalle metriche. La risposta riguarda la cardinalità: un tag ID utente su una metrica crea una time series per utente, potenzialmente milioni di series. I backend di monitoring faticano con l'alta cardinalità, portando a esaurimento della memoria e query lente. I trace gestiscono l'alta cardinalità attraverso il sampling, rendendoli il posto corretto per identificatori specifici della richiesta.

Pronto a superare i tuoi colloqui su Spring Boot?

Pratica con i nostri simulatori interattivi, flashcards e test tecnici.

Propagare il Contesto Trace Attraverso Confini Asincroni

Il distributed tracing richiede la propagazione del contesto. Gli header HTTP trasportano gli ID trace tra i servizi, ma le operazioni asincrone come i metodi @Async o le catene di CompletableFuture possono perdere il contesto se non configurate.

Spring Boot 3.4 fornisce propagazione automatica per i reactive stream e i metodi @Async quando configurato:

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

Per i bean TaskExecutor personalizzati, vanno wrappati con 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;
    }
}

Il decorator copia l'Observation corrente e il contesto trace nel thread asincrono. Senza di esso, gli span avviati in task asincroni non hanno parent, rompendo l'albero dei trace.

Usare le Annotazioni @Observed e @NewSpan

Spring Boot 3.4 supporta l'observability dichiarativa attraverso le annotazioni. L'elaborazione delle annotazioni viene abilitata aggiungendo il weaver 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 crea sia una metrica (timer) che uno span. @NewSpan crea solo uno span, utile quando l'operazione ha già metriche altrove. Entrambe le annotazioni gestiscono automaticamente la registrazione delle eccezioni e lo stato dello span.

Componenti Auto-Strumentati in Spring Boot 3.4

Spring Boot 3.4 auto-strumenta diversi componenti senza modifiche al codice:

ComponenteNome ObservationCosa Cattura
Spring MVChttp.server.requestsPercorso richiesta, metodo, stato, eccezione
WebClienthttp.client.requestsChiamate HTTP in uscita
RestClienthttp.client.requestsChiamate HTTP in uscita (Spring 6.1+)
Spring Kafkaspring.kafka.listenerGruppo consumer, topic, partizione
Spring Data JPAspring.data.repositoryMetodo repository, tempo query
Scheduled Tasksspring.schedulingNome task, tempo di esecuzione

La documentazione di Spring Boot Actuator elenca tutti i componenti auto-strumentati. Librerie di terze parti come Datasource Micrometer aggiungono il tracing delle query JDBC.

Filtrare le Observation per Ridurre il Rumore

Gli health check, le readiness probe e le risorse statiche generano rumore nei trace. Vanno filtrati usando dei predicate:

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;
    }
}

In alternativa, le observation possono essere disabilitate per nome nella configurazione:

yaml
# application.yml
management:
  observations:
    enable:
      spring.security: false  # Disable Spring Security observations
      http.server.requests.actuator: false  # Custom predicate name
Contesto da Colloquio: Trace Sampling vs. Filtering

Il sampling riduce la percentuale di trace raccolti su tutti gli endpoint. Il filtering rimuove endpoint specifici completamente. Il filtering viene usato per endpoint che non forniscono mai valore diagnostico (health check, scraping delle metriche). Il sampling viene usato per controllare i costi preservando dati rappresentativi.

Correlare i Log con gli ID Trace

Spring Boot 3.4 aggiunge automaticamente trace e span ID all'MDC (Mapped Diagnostic Context) quando Micrometer Tracing è attivo. Logback e Log4j2 possono includere questi ID nell'output dei log:

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>

Con questo pattern, ogni riga di log include il trace ID. Cercare i log per trace ID restituisce tutti i messaggi di una singola richiesta attraverso tutti i servizi, correlando i log con gli span in Jaeger o Tempo.

Domande Comuni da Colloquio sulla Spring Boot Observability

Gli intervistatori valutano sia la comprensione concettuale che l'esperienza pratica con l'observability. Queste domande appaiono frequentemente nei ruoli backend senior.

D: Qual è la differenza tra Micrometer Tracing e OpenTelemetry?

Micrometer Tracing è un'API vendor-neutral per il distributed tracing, simile a come Micrometer astrae le metriche. OpenTelemetry è un'implementazione specifica e un protocollo. Spring Boot usa Micrometer Tracing come API e si collega a OpenTelemetry per l'export via micrometer-tracing-bridge-otel. Questa stratificazione permette alle applicazioni di cambiare backend senza modifiche al codice.

D: Perché Spring raccomanda la Observation API rispetto alla strumentazione diretta di OpenTelemetry?

La Observation API fornisce un singolo punto di strumentazione che emette sia metriche che trace. Le chiamate dirette a OpenTelemetry producono solo trace. Usare Observation.observe() genera una metrica timer e uno span dallo stesso codice, riducendo la duplicazione e garantendo una nomenclatura consistente.

D: Come si fa il debug di un trace che mostra un gap tra gli span?

I gap indicano strumentazione mancante o propagazione del contesto interrotta. Bisogna verificare se il codice usa operazioni asincrone senza ContextPropagatingTaskDecorator. Verificare che i client HTTP siano strumentati (WebClient, RestClient, o un RestTemplate wrappato manualmente). Per le code di messaggi, confermare che gli header trace si propaghino attraverso le proprietà dei messaggi.

D: Cosa succede se si aggiunge un tag high cardinality a una metrica?

Ogni valore tag unico crea una nuova time series. Un tag ID utente con milioni di utenti crea milioni di series, esaurendo la memoria in Prometheus o altri backend TSDB. La soluzione è usare highCardinalityKeyValue() invece di lowCardinalityKeyValue(), che aggiunge il tag solo ai trace dove l'alta cardinalità è prevista.

Costruire una Pipeline di Distributed Tracing con Docker Compose

Uno stack di observability locale aiuta a validare la strumentazione prima del deploy in produzione. Questo esempio usa l'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]

Il collector riceve gli span dall'applicazione Spring Boot e li inoltra a Jaeger. Questa architettura permette di aggiungere esportatori (Tempo, Zipkin, backend cloud) senza modificare il codice dell'applicazione.

Inizia a praticare!

Metti alla prova le tue conoscenze con i nostri simulatori di colloquio e test tecnici.

Punti Chiave per la Spring Boot Observability

  • La Observation API unifica metriche e tracing: una chiamata Observation.observe() produce sia una metrica timer che uno span, eliminando la doppia strumentazione.
  • Aggiungere micrometer-tracing-bridge-otel e opentelemetry-exporter-otlp per abilitare l'export OTLP. Spring Boot 3.4 configura automaticamente l'esportatore HTTP con l'endpoint da management.otlp.tracing.endpoint.
  • Usare lowCardinalityKeyValue() per le dimensioni che appaiono nelle metriche (valori limitati). Usare highCardinalityKeyValue() per i dati solo dei trace (ID utente, ID richiesta).
  • Abilitare la propagazione del contesto per il codice asincrono con spring.task.execution.propagate-context=true e wrappare gli executor personalizzati con ContextPropagatingTaskDecorator.
  • Filtrare gli endpoint rumorosi come /actuator/health usando bean ObservationPredicate per mantenere i trace focalizzati sulle operazioni di business.
  • I pattern dei log dovrebbero includere %X{traceId} per correlare le righe di log con i distributed trace nel backend di observability.
  • Le domande da colloquio sull'observability si concentrano sulla cardinalità, i fallimenti della propagazione del contesto e la distinzione tra la Observation API e l'uso diretto di OpenTelemetry.
Sfida del giorno

Sapresti trovare il bug in Spring Boot?

Uno snippet reale, un bug nascosto, un tentativo al giorno. Senza account per provare.

Anthony Fillion-Maillet

Scritto da

Anthony Fillion-Maillet

Fondatore di SharpSkill

Sviluppatore fullstack da oltre 10 anni. Guida SharpSkill e risponde di tutto ciò che vi viene pubblicato.

Aggiornato il 19 settembre 2026

Condividi

Articoli correlati