Spring Boot Observability 2026: OpenTelemetry, Distributed Tracing und Interview-Fragen

Spring Boot Observability mit OpenTelemetry und Micrometer Tracing beherrschen. OTLP-Export konfigurieren, die Observation API nutzen und Interview-Fragen meistern.

Spring Boot Observability 2026: OpenTelemetry, Distributed Tracing und Interview-Fragen

Spring Boot Observability vereint Logging, Metriken und Distributed Tracing in einem einheitlichen System, das den Fluss von Anfragen durch Microservices sichtbar macht. Mit Spring Boot 3 hat das Framework Micrometer Tracing (als Ersatz für Spring Cloud Sleuth) eingeführt und die Observation API präsentiert, die einen einzigen Instrumentierungspunkt bietet, der sowohl Metriken als auch Traces erzeugt.

Das Observation API Pattern

Spring Boot empfiehlt die Verwendung von Observation.observe() anstatt direkter OpenTelemetry-Aufrufe. Ein einzelner Instrumentierungsaufruf erzeugt Metriken über Micrometer und Traces über die OpenTelemetry-Bridge, was Code-Duplikation reduziert und konsistente Tag-Benennung über alle Signale hinweg gewährleistet.

Wie die Observation API Micrometer und OpenTelemetry verbindet

Die Observation API fungiert als Fassade über Metriken und Tracing. Wenn Code Observation.createNotStarted() aufruft, leitet Spring Boot die Observation an registrierte Handler weiter: MeterObservationHandler für Micrometer-Metriken und TracingObservationHandler für Distributed Traces. Diese Architektur bedeutet: einmal instrumentieren, überall exportieren.

Die Bridge-Abhängigkeit micrometer-tracing-bridge-otel verbindet Micrometer Tracing mit dem SDK von OpenTelemetry. Traces fließen durch den SdkTracerProvider von OpenTelemetry und werden via OTLP an Backends wie Jaeger, Tempo oder jeden OpenTelemetry-kompatiblen Collector exportiert.

ObservabilityConfig.javajava
@Configuration
public class ObservabilityConfig {

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

Die obige Konfiguration registriert einen ObservationHandler, der jede Observation protokolliert. In Produktionsumgebungen senden die automatisch konfigurierten Handler Daten an Micrometer-Registries und OpenTelemetry-Exporter ohne zusätzlichen Code.

Erforderliche Abhängigkeiten für Spring Boot 3.4 Tracing

Spring Boot 3.4 benötigt explizite Abhängigkeiten für Distributed Tracing. Der spring-boot-starter-actuator stellt die Observation API bereit, aber Tracing erfordert die OpenTelemetry-Bridge und einen Exporter.

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>

Das micrometer-tracing-bridge-otel-Artefakt verbindet Micrometer Tracing mit der API von OpenTelemetry. Der opentelemetry-exporter-otlp sendet Spans an Collector über das OTLP-Protokoll via HTTP oder gRPC. Spring Boot verwaltet die Versionsabstimmung durch sein Dependency Management.

OTLP-Export nach Jaeger oder Tempo konfigurieren

Spring Boot konfiguriert automatisch einen OtlpHttpSpanExporter, wenn die OTLP-Abhängigkeit vorhanden ist. Der Exporter sendet Traces an den in application.yml angegebenen Endpunkt.

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

Die resource-attributes fügen jedem Span Metadaten hinzu. Backends wie Grafana Tempo und Jaeger nutzen diese Attribute, um Traces nach Service und Umgebung zu gruppieren. Das Setzen von sampling.probability auf 1.0 erfasst alle Anfragen während der Entwicklung, aber Produktionssysteme samplen typischerweise zwischen 1% und 10%, um Speicherkosten zu kontrollieren.

Benutzerdefinierte Observations mit Low- und High-Cardinality Tags erstellen

Observations unterstützen zwei Tag-Typen: Low Cardinality für Metriken (begrenzte Werte wie HTTP-Methoden oder Statuscodes) und High Cardinality für Traces (unbegrenzte Werte wie Benutzer-IDs oder Request-IDs).

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

Low-Cardinality-Tags erscheinen sowohl in Metriken als auch in Traces. High-Cardinality-Tags erscheinen nur in Traces, da Metriken mit unbegrenzten Dimensionen den Speicher sprengen. Diese Unterscheidung verhindert Prometheus-Kardinalitätsbomben, während Trace-Details reichhaltig bleiben.

Interview-Frage: Tag-Kardinalität

Interviewer fragen oft, warum bestimmte Tags von Metriken ausgeschlossen werden sollten. Die Antwort bezieht sich auf Kardinalität: Ein Benutzer-ID-Tag auf einer Metrik erzeugt eine Zeitreihe pro Benutzer, potenziell Millionen von Zeitreihen. Monitoring-Backends haben mit hoher Kardinalität Probleme, was zu Speichererschöpfung und langsamen Abfragen führt. Traces handhaben hohe Kardinalität durch Sampling, was sie zum richtigen Ort für anfragespezifische Identifier macht.

Bereit für deine Spring Boot-Interviews?

Übe mit unseren interaktiven Simulatoren, Flashcards und technischen Tests.

Trace-Kontext über asynchrone Grenzen propagieren

Distributed Tracing erfordert Kontext-Propagation. HTTP-Header tragen Trace-IDs zwischen Services, aber asynchrone Operationen wie @Async-Methoden oder CompletableFuture-Ketten können den Kontext verlieren, wenn sie nicht konfiguriert sind.

Spring Boot 3.4 bietet automatische Propagation für Reactive Streams und @Async-Methoden, wenn konfiguriert:

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

Für benutzerdefinierte TaskExecutor-Beans werden diese mit ContextPropagatingTaskDecorator umwickelt:

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

Der Decorator kopiert die aktuelle Observation und den Trace-Kontext in den asynchronen Thread. Ohne ihn haben Spans, die in asynchronen Tasks gestartet werden, keinen Parent, was den Trace-Baum unterbricht.

Verwendung von @Observed und @NewSpan Annotationen

Spring Boot 3.4 unterstützt deklarative Observability durch Annotationen. Die Annotation-Verarbeitung wird durch Hinzufügen des AspectJ-Weavers aktiviert:

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 erstellt sowohl eine Metrik (Timer) als auch einen Span. @NewSpan erstellt nur einen Span, nützlich wenn die Operation bereits anderswo Metriken hat. Beide Annotationen handhaben automatisch Exception-Recording und Span-Status.

Auto-instrumentierte Komponenten in Spring Boot 3.4

Spring Boot 3.4 auto-instrumentiert mehrere Komponenten ohne Code-Änderungen:

KomponenteObservation-NameWas erfasst wird
Spring MVChttp.server.requestsRequest-Pfad, Methode, Status, Exception
WebClienthttp.client.requestsAusgehende HTTP-Aufrufe
RestClienthttp.client.requestsAusgehende HTTP-Aufrufe (Spring 6.1+)
Spring Kafkaspring.kafka.listenerConsumer-Gruppe, Topic, Partition
Spring Data JPAspring.data.repositoryRepository-Methode, Abfragezeit
Scheduled Tasksspring.schedulingTask-Name, Ausführungszeit

Die Spring Boot Actuator Dokumentation listet alle auto-instrumentierten Komponenten auf. Third-Party-Libraries wie Datasource Micrometer fügen JDBC-Query-Tracing hinzu.

Observations filtern zur Rauschreduzierung

Health-Checks, Readiness-Probes und statische Ressourcen erzeugen Rauschen in Traces. Diese werden mit Predicates herausgefiltert:

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

Alternativ können Observations nach Namen in der Konfiguration deaktiviert werden:

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

Sampling reduziert den Prozentsatz der gesammelten Traces über alle Endpunkte hinweg. Filtering entfernt spezifische Endpunkte vollständig. Filtering wird für Endpunkte verwendet, die niemals diagnostischen Wert liefern (Health-Checks, Metrics-Scraping). Sampling wird zur Kostenkontrolle verwendet, während repräsentative Daten erhalten bleiben.

Logs mit Trace-IDs korrelieren

Spring Boot 3.4 fügt automatisch Trace- und Span-IDs zum MDC (Mapped Diagnostic Context) hinzu, wenn Micrometer Tracing aktiv ist. Logback und Log4j2 können diese IDs in die Log-Ausgabe einbeziehen:

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>

Mit diesem Pattern enthält jede Log-Zeile die Trace-ID. Die Suche nach Logs anhand der Trace-ID liefert alle Nachrichten einer einzelnen Anfrage über alle Services hinweg und korreliert Logs mit Spans in Jaeger oder Tempo.

Häufige Interview-Fragen zur Spring Boot Observability

Interviewer bewerten sowohl konzeptuelles Verständnis als auch praktische Erfahrung mit Observability. Diese Fragen erscheinen häufig in Senior-Backend-Positionen.

F: Was ist der Unterschied zwischen Micrometer Tracing und OpenTelemetry?

Micrometer Tracing ist eine herstellerneutrale API für Distributed Tracing, ähnlich wie Micrometer Metriken abstrahiert. OpenTelemetry ist eine spezifische Implementierung und ein Protokoll. Spring Boot verwendet Micrometer Tracing als API und verbindet sich über micrometer-tracing-bridge-otel mit OpenTelemetry für den Export. Diese Schichtung ermöglicht es Anwendungen, Backends ohne Code-Änderungen zu wechseln.

F: Warum empfiehlt Spring die Observation API anstelle direkter OpenTelemetry-Instrumentierung?

Die Observation API bietet einen einzigen Instrumentierungspunkt, der sowohl Metriken als auch Traces erzeugt. Direkte OpenTelemetry-Aufrufe erzeugen nur Traces. Die Verwendung von Observation.observe() generiert eine Timer-Metrik und einen Span aus demselben Code, was Duplikation reduziert und konsistente Benennung gewährleistet.

F: Wie debuggt man einen Trace, der eine Lücke zwischen Spans zeigt?

Lücken weisen auf fehlende Instrumentierung oder unterbrochene Kontext-Propagation hin. Es sollte geprüft werden, ob der Code asynchrone Operationen ohne ContextPropagatingTaskDecorator verwendet. Zu verifizieren ist, dass HTTP-Clients instrumentiert sind (WebClient, RestClient oder ein manuell gewrappter RestTemplate). Bei Message-Queues muss bestätigt werden, dass Trace-Header durch Message-Properties propagiert werden.

F: Was passiert, wenn ein High-Cardinality-Tag zu einer Metrik hinzugefügt wird?

Jeder eindeutige Tag-Wert erzeugt eine neue Zeitreihe. Ein Benutzer-ID-Tag mit Millionen von Benutzern erzeugt Millionen von Zeitreihen, was den Speicher in Prometheus oder anderen TSDB-Backends erschöpft. Die Lösung ist die Verwendung von highCardinalityKeyValue() anstelle von lowCardinalityKeyValue(), was das Tag nur zu Traces hinzufügt, wo hohe Kardinalität erwartet wird.

Eine Distributed-Tracing-Pipeline mit Docker Compose aufbauen

Ein lokaler Observability-Stack hilft bei der Validierung der Instrumentierung vor dem Deployment in Produktion. Dieses Beispiel verwendet den OpenTelemetry Collector und 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]

Der Collector empfängt Spans von der Spring-Boot-Anwendung und leitet sie an Jaeger weiter. Diese Architektur ermöglicht das Hinzufügen von Exportern (Tempo, Zipkin, Cloud-Backends) ohne Änderung des Anwendungscodes.

Fang an zu üben!

Teste dein Wissen mit unseren Interview-Simulatoren und technischen Tests.

Wichtige Erkenntnisse zur Spring Boot Observability

  • Die Observation API vereint Metriken und Tracing: Ein Observation.observe()-Aufruf erzeugt sowohl eine Timer-Metrik als auch einen Span und eliminiert duale Instrumentierung.
  • micrometer-tracing-bridge-otel und opentelemetry-exporter-otlp hinzufügen, um OTLP-Export zu aktivieren. Spring Boot 3.4 konfiguriert automatisch den HTTP-Exporter mit dem Endpunkt aus management.otlp.tracing.endpoint.
  • lowCardinalityKeyValue() für Dimensionen verwenden, die in Metriken erscheinen (begrenzte Werte). highCardinalityKeyValue() für reine Trace-Daten verwenden (Benutzer-IDs, Request-IDs).
  • Kontext-Propagation für asynchronen Code mit spring.task.execution.propagate-context=true aktivieren und benutzerdefinierte Executors mit ContextPropagatingTaskDecorator umwickeln.
  • Laute Endpunkte wie /actuator/health mit ObservationPredicate-Beans filtern, um Traces auf Geschäftsoperationen fokussiert zu halten.
  • Log-Patterns sollten %X{traceId} enthalten, um Log-Zeilen mit Distributed Traces im Observability-Backend zu korrelieren.
  • Interview-Fragen zur Observability konzentrieren sich auf Kardinalität, Kontext-Propagation-Fehler und die Unterscheidung zwischen der Observation API und direkter OpenTelemetry-Nutzung.
Tägliche Challenge

Findest du den Bug in Spring Boot?

Ein echter Codeausschnitt, ein versteckter Bug, ein Versuch pro Tag. Zum Ausprobieren ohne Konto.

Anthony Fillion-Maillet

Geschrieben von

Anthony Fillion-Maillet

Gründer von SharpSkill

Seit über 10 Jahren Fullstack-Entwickler. Er leitet SharpSkill und verantwortet alles, was hier erscheint.

Aktualisiert am 19. September 2026

Teilen

Verwandte Artikel