Observabilité Spring Boot en 2026 : OpenTelemetry, Tracing Distribué et Questions d'Entretien

Guide complet sur l'observabilité Spring Boot avec OpenTelemetry et le tracing distribué. Configuration OTLP, Observation API, propagation de contexte et questions techniques pour entretiens backend.

Observabilité Spring Boot avec OpenTelemetry et tracing distribué

L'observabilité Spring Boot combine la journalisation, les métriques et le tracing distribué dans un système unifié qui révèle comment les requêtes traversent les microservices. Depuis Spring Boot 3, le framework a adopté Micrometer Tracing (remplaçant Spring Cloud Sleuth) et introduit l'Observation API, qui fournit un point d'instrumentation unique émettant à la fois des métriques et des traces.

Le Pattern Observation API

Spring Boot recommande d'utiliser Observation.observe() plutôt que d'appeler OpenTelemetry directement. Un seul appel d'instrumentation produit des métriques via Micrometer et des traces via le bridge OpenTelemetry, réduisant la duplication de code et assurant une nomenclature cohérente des tags entre les signaux.

Comment l'Observation API Fait le Pont entre Micrometer et OpenTelemetry

L'Observation API agit comme une façade sur les métriques et le tracing. Quand le code appelle Observation.createNotStarted(), Spring Boot route l'observation vers les handlers enregistrés : MeterObservationHandler pour les métriques Micrometer et TracingObservationHandler pour les traces distribuées. Cette architecture signifie qu'une seule instrumentation exporte partout.

La dépendance bridge micrometer-tracing-bridge-otel connecte Micrometer Tracing au SDK OpenTelemetry. Les traces passent par le SdkTracerProvider d'OpenTelemetry et s'exportent via OTLP vers des backends comme Jaeger, Tempo, ou tout collecteur compatible OpenTelemetry.

ObservabilityConfig.javajava
@Configuration
public class ObservabilityConfig {

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

La configuration ci-dessus enregistre un ObservationHandler qui journalise chaque observation. En production, les handlers auto-configurés envoient les données aux registres Micrometer et aux exporteurs OpenTelemetry sans code supplémentaire.

Dépendances Requises pour le Tracing Spring Boot 3.4

Spring Boot 3.4 nécessite des dépendances explicites pour le tracing distribué. Le spring-boot-starter-actuator fournit l'Observation API, mais le tracing requiert le bridge OpenTelemetry et un exporteur.

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'artefact micrometer-tracing-bridge-otel fait le pont entre Micrometer Tracing et l'API OpenTelemetry. L'opentelemetry-exporter-otlp envoie les spans aux collecteurs via le protocole OTLP sur HTTP ou gRPC. Spring Boot gère l'alignement des versions via sa gestion des dépendances.

Configuration de l'Export OTLP vers Jaeger ou Tempo

Spring Boot auto-configure un OtlpHttpSpanExporter quand la dépendance OTLP est présente. L'exporteur envoie les traces au endpoint spécifié dans 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

Les resource-attributes attachent des métadonnées à chaque span. Les backends comme Grafana Tempo et Jaeger utilisent ces attributs pour grouper les traces par service et environnement. Définir sampling.probability à 1.0 capture toutes les requêtes en développement, mais les systèmes de production échantillonnent typiquement entre 1% et 10% pour contrôler les coûts de stockage.

Créer des Observations Personnalisées avec Tags de Cardinalité Basse et Haute

Les observations supportent deux types de tags : cardinalité basse pour les métriques (valeurs bornées comme les méthodes HTTP ou codes de statut) et cardinalité haute pour les traces (valeurs non bornées comme les IDs utilisateur ou IDs de requête).

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

Les tags de cardinalité basse apparaissent dans les métriques et les traces. Les tags de cardinalité haute n'apparaissent que dans les traces car les métriques avec des dimensions non bornées explosent le stockage. Cette distinction prévient les bombes de cardinalité Prometheus tout en gardant les traces riches en détails.

Question d'Entretien : Cardinalité des Tags

Les recruteurs demandent souvent pourquoi certains tags doivent être exclus des métriques. La réponse concerne la cardinalité : un tag d'ID utilisateur sur une métrique crée une série temporelle par utilisateur, potentiellement des millions de séries. Les backends de monitoring peinent avec la haute cardinalité, menant à l'épuisement mémoire et des requêtes lentes. Les traces gèrent la haute cardinalité via l'échantillonnage, en faisant le bon endroit pour les identifiants spécifiques aux requêtes.

Prêt à réussir tes entretiens Spring Boot ?

Entraîne-toi avec nos simulateurs interactifs, fiches express et tests techniques.

Propagation du Contexte de Trace à Travers les Frontières Asynchrones

Le tracing distribué nécessite la propagation de contexte. Les headers HTTP transportent les IDs de trace entre services, mais les opérations asynchrones comme les méthodes @Async ou les chaînes CompletableFuture peuvent perdre le contexte si mal configurées.

Spring Boot 3.4 fournit une propagation automatique pour les flux réactifs et les méthodes @Async quand configuré :

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

Pour les beans TaskExecutor personnalisés, les envelopper avec 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;
    }
}

Le décorateur copie l'Observation courante et le contexte de trace dans le thread asynchrone. Sans lui, les spans démarrés dans les tâches async n'ont pas de parent, cassant l'arbre de trace.

Utilisation des Annotations @Observed et @NewSpan

Spring Boot 3.4 supporte l'observabilité déclarative via les annotations. Activer le traitement des annotations en ajoutant le 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 crée à la fois une métrique (timer) et un span. @NewSpan ne crée qu'un span, utile quand l'opération a déjà des métriques ailleurs. Les deux annotations gèrent automatiquement l'enregistrement des exceptions et le statut du span.

Composants Auto-Instrumentés dans Spring Boot 3.4

Spring Boot 3.4 auto-instrumente plusieurs composants sans changement de code :

ComposantNom d'ObservationCe Qui Est Capturé
Spring MVChttp.server.requestsChemin de requête, méthode, statut, exception
WebClienthttp.client.requestsAppels HTTP sortants
RestClienthttp.client.requestsAppels HTTP sortants (Spring 6.1+)
Spring Kafkaspring.kafka.listenerGroupe de consommateurs, topic, partition
Spring Data JPAspring.data.repositoryMéthode du repository, temps de requête
Tâches Planifiéesspring.schedulingNom de la tâche, temps d'exécution

La documentation Spring Boot Actuator liste tous les composants auto-instrumentés. Les bibliothèques tierces comme Datasource Micrometer ajoutent le tracing des requêtes JDBC.

Filtrer les Observations pour Réduire le Bruit

Les health checks, les probes de disponibilité et les ressources statiques génèrent du bruit dans les traces. Les filtrer avec des prédicats :

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

Alternativement, désactiver les observations par nom dans la configuration :

yaml
# application.yml
management:
  observations:
    enable:
      spring.security: false  # Disable Spring Security observations
      http.server.requests.actuator: false  # Custom predicate name
Contexte d'Entretien : Échantillonnage vs Filtrage

L'échantillonnage réduit le pourcentage de traces collectées sur tous les endpoints. Le filtrage supprime complètement des endpoints spécifiques. Utiliser le filtrage pour les endpoints qui ne fournissent jamais de valeur diagnostique (health checks, scraping de métriques). Utiliser l'échantillonnage pour contrôler les coûts tout en préservant des données représentatives.

Corrélation des Logs avec les IDs de Trace

Spring Boot 3.4 ajoute automatiquement les IDs de trace et de span au MDC (Mapped Diagnostic Context) quand Micrometer Tracing est actif. Logback et Log4j2 peuvent inclure ces IDs dans la sortie des 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>

Avec ce pattern, chaque ligne de log inclut l'ID de trace. Rechercher les logs par ID de trace retourne tous les messages d'une seule requête à travers tous les services, corrélant les logs avec les spans dans Jaeger ou Tempo.

Questions d'Entretien Courantes sur l'Observabilité Spring Boot

Les recruteurs évaluent à la fois la compréhension conceptuelle et l'expérience pratique de l'observabilité. Ces questions apparaissent fréquemment pour les postes backend senior.

Q : Quelle est la différence entre Micrometer Tracing et OpenTelemetry ?

Micrometer Tracing est une API vendor-neutre pour le tracing distribué, similaire à la façon dont Micrometer abstrait les métriques. OpenTelemetry est une implémentation et un protocole spécifiques. Spring Boot utilise Micrometer Tracing comme API et fait le pont vers OpenTelemetry pour l'export via micrometer-tracing-bridge-otel. Cette couche permet aux applications de changer de backend sans modification de code.

Q : Pourquoi Spring recommande-t-il l'Observation API plutôt que l'instrumentation OpenTelemetry directe ?

L'Observation API fournit un point d'instrumentation unique qui émet à la fois des métriques et des traces. Les appels OpenTelemetry directs ne produisent que des traces. Utiliser Observation.observe() génère une métrique timer et un span depuis le même code, réduisant la duplication et assurant une nomenclature cohérente.

Q : Comment débugger une trace qui montre un écart entre les spans ?

Les écarts indiquent une instrumentation manquante ou une propagation de contexte cassée. Vérifier si le code utilise des opérations async sans ContextPropagatingTaskDecorator. Vérifier que les clients HTTP sont instrumentés (WebClient, RestClient, ou un RestTemplate manuellement enveloppé). Pour les files de messages, confirmer que les headers de trace se propagent à travers les propriétés des messages.

Q : Que se passe-t-il si on ajoute un tag de haute cardinalité à une métrique ?

Chaque valeur de tag unique crée une nouvelle série temporelle. Un tag d'ID utilisateur avec des millions d'utilisateurs crée des millions de séries, épuisant la mémoire dans Prometheus ou d'autres backends TSDB. La solution est d'utiliser highCardinalityKeyValue() au lieu de lowCardinalityKeyValue(), ce qui n'ajoute le tag qu'aux traces où la haute cardinalité est attendue.

Construire un Pipeline de Tracing Distribué avec Docker Compose

Une stack d'observabilité locale aide à valider l'instrumentation avant le déploiement en production. Cet exemple utilise le OpenTelemetry Collector et 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]

Le collecteur reçoit les spans de l'application Spring Boot et les transmet à Jaeger. Cette architecture permet d'ajouter des exporteurs (Tempo, Zipkin, backends cloud) sans changer le code applicatif.

Passe à la pratique !

Teste tes connaissances avec nos simulateurs d'entretien et tests techniques.

Points Clés pour l'Observabilité Spring Boot

  • L'Observation API unifie métriques et tracing : un seul appel Observation.observe() produit à la fois une métrique timer et un span, éliminant la double instrumentation.
  • Ajouter micrometer-tracing-bridge-otel et opentelemetry-exporter-otlp pour activer l'export OTLP. Spring Boot 3.4 auto-configure l'exporteur HTTP avec le endpoint de management.otlp.tracing.endpoint.
  • Utiliser lowCardinalityKeyValue() pour les dimensions apparaissant dans les métriques (valeurs bornées). Utiliser highCardinalityKeyValue() pour les données uniquement dans les traces (IDs utilisateur, IDs de requête).
  • Activer la propagation de contexte pour le code async avec spring.task.execution.propagate-context=true et envelopper les executors personnalisés avec ContextPropagatingTaskDecorator.
  • Filtrer les endpoints bruyants comme /actuator/health avec des beans ObservationPredicate pour garder les traces focalisées sur les opérations métier.
  • Les patterns de log doivent inclure %X{traceId} pour corréler les lignes de log avec les traces distribuées dans le backend d'observabilité.
  • Les questions d'entretien sur l'observabilité se concentrent sur la cardinalité, les échecs de propagation de contexte, et la distinction entre l'Observation API et l'utilisation directe d'OpenTelemetry.
Défi du jour

Tu saurais repérer le bug en Spring Boot ?

Un vrai bout de code, un bug caché, une tentative par jour. Sans compte pour essayer.

Anthony Fillion-Maillet

Écrit par

Anthony Fillion-Maillet

Fondateur de SharpSkill

Développeur fullstack depuis plus de 10 ans. Il dirige SharpSkill et répond de tout ce qui y est publié.

Mis à jour le 19 septembre 2026

Tags

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

Partager

Articles similaires