Obserwowalność w Spring Boot 2026: OpenTelemetry, Distributed Tracing i pytania rekrutacyjne

Kompleksowy przewodnik po obserwowalności Spring Boot z OpenTelemetry i Micrometer Tracing. Konfiguracja distributed tracing, Observation API, eksport OTLP oraz przygotowanie do rozmów kwalifikacyjnych.

Obserwowalność Spring Boot z OpenTelemetry distributed tracing

Obserwowalność w Spring Boot łączy logowanie, metryki i distributed tracing w jeden spójny system, który pokazuje przepływ żądań przez mikroserwisy. Od wersji Spring Boot 3, framework przyjął Micrometer Tracing (zastępując Spring Cloud Sleuth) oraz wprowadził Observation API, dostarczając pojedynczy punkt instrumentacji emitujący zarówno metryki, jak i trace'y.

Wzorzec Observation API

Spring Boot zaleca używanie Observation.observe() zamiast bezpośredniego wywoływania OpenTelemetry. Jedno wywołanie instrumentacji produkuje metryki przez Micrometer oraz trace'y przez most OpenTelemetry, redukując duplikację kodu i zapewniając spójne nazewnictwo tagów między sygnałami.

Jak Observation API łączy Micrometer z OpenTelemetry

Observation API działa jako fasada nad metrykami i tracingiem. Gdy kod wywołuje Observation.createNotStarted(), Spring Boot przekazuje obserwację do zarejestrowanych handlerów: MeterObservationHandler dla metryk Micrometer oraz TracingObservationHandler dla distributed tracing. Ta architektura oznacza, że instrumentacja raz wykonana eksportuje dane wszędzie.

Zależność mostkowa micrometer-tracing-bridge-otel łączy Micrometer Tracing z SDK OpenTelemetry. Trace'y przepływają przez SdkTracerProvider OpenTelemetry i są eksportowane przez OTLP do backendów takich jak Jaeger, Tempo lub dowolny kolektor kompatybilny z OpenTelemetry.

ObservabilityConfig.javajava
@Configuration
public class ObservabilityConfig {

    @Bean
    public ObservationRegistryCustomizer<ObservationRegistry> addLowCardinalityTags() {
        return registry -> registry.observationConfig()
            .observationHandler(new ObservationTextPublisher()); // Loguje obserwacje do konsoli
    }
}

Powyższa konfiguracja rejestruje ObservationHandler, który loguje każdą obserwację. W środowisku produkcyjnym automatycznie skonfigurowane handlery wysyłają dane do rejestrów Micrometer i eksporterów OpenTelemetry bez dodatkowego kodu.

Wymagane zależności dla Spring Boot 3.4 Tracing

Spring Boot 3.4 wymaga jawnych zależności dla distributed tracing. spring-boot-starter-actuator dostarcza Observation API, ale tracing wymaga mostu OpenTelemetry i eksportera.

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>

Artefakt micrometer-tracing-bridge-otel mostuje Micrometer Tracing do API OpenTelemetry. opentelemetry-exporter-otlp wysyła spany do kolektorów używając protokołu OTLP przez HTTP lub gRPC. Spring Boot zarządza wyrównaniem wersji przez swoje zarządzanie zależnościami.

Konfiguracja eksportu OTLP do Jaegera lub Tempo

Spring Boot automatycznie konfiguruje OtlpHttpSpanExporter, gdy zależność OTLP jest obecna. Eksporter wysyła trace'y do endpointu określonego w application.yml.

yaml
# application.yml
management:
  tracing:
    sampling:
      probability: 1.0  # 100% samplowania dla dev, zmniejsz w produkcji
  otlp:
    tracing:
      endpoint: http://localhost:4318/v1/traces
  opentelemetry:
    resource-attributes:
      service.name: order-service
      deployment.environment: staging

resource-attributes dołączają metadane do każdego spana. Backendy takie jak Grafana Tempo i Jaeger używają tych atrybutów do grupowania trace'ów według serwisu i środowiska. Ustawienie sampling.probability na 1.0 przechwytuje wszystkie żądania podczas developmentu, ale systemy produkcyjne zazwyczaj samplują między 1% a 10%, aby kontrolować koszty storage'u.

Tworzenie własnych obserwacji z tagami o niskiej i wysokiej kardynalności

Obserwacje wspierają dwa typy tagów: niskiej kardynalności dla metryk (ograniczone wartości jak metody HTTP lub kody statusu) oraz wysokiej kardynalności dla trace'ów (nieograniczone wartości jak ID użytkownika lub ID żądania).

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())              // ograniczone: USD, EUR, GBP
            .highCardinalityKeyValue("payment.id", request.getPaymentId())          // unikalne per żądanie
            .highCardinalityKeyValue("customer.id", request.getCustomerId())        // wysoka kardynalność
            .observe(() -> executePayment(request));
    }

    private PaymentResult executePayment(PaymentRequest request) {
        // Wywołanie bramki płatności
        return new PaymentResult(true, "TXN-" + UUID.randomUUID());
    }
}

Tagi niskiej kardynalności pojawiają się zarówno w metrykach, jak i trace'ach. Tagi wysokiej kardynalności pojawiają się tylko w trace'ach, ponieważ metryki z nieograniczonymi wymiarami eksplodują storage. Ta dystynkcja zapobiega bombom kardynalności w Prometheusie, jednocześnie zachowując bogate szczegóły trace'ów.

Pytanie rekrutacyjne: kardynalność tagów

Rekruterzy często pytają, dlaczego niektóre tagi powinny być wykluczone z metryk. Odpowiedź dotyczy kardynalności: tag ID użytkownika na metryce tworzy jedną serię czasową na użytkownika, potencjalnie miliony serii. Backendy monitoringu mają problemy z wysoką kardynalnością, prowadząc do wyczerpania pamięci i wolnych zapytań. Trace'y obsługują wysoką kardynalność przez samplowanie, czyniąc je właściwym miejscem dla identyfikatorów specyficznych dla żądania.

Gotowy na rozmowy o Spring Boot?

Ćwicz z naszymi interaktywnymi symulatorami, flashcards i testami technicznymi.

Propagacja kontekstu trace'a przez granice asynchroniczne

Distributed tracing wymaga propagacji kontekstu. Nagłówki HTTP przenoszą ID trace'ów między serwisami, ale operacje asynchroniczne jak metody @Async lub łańcuchy CompletableFuture mogą utracić kontekst, jeśli nie są skonfigurowane.

Spring Boot 3.4 zapewnia automatyczną propagację dla strumieni reaktywnych i metod @Async przy odpowiedniej konfiguracji:

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

Dla własnych beanów TaskExecutor należy opakować je w 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;
    }
}

Dekorator kopiuje bieżącą Observation i kontekst trace'a do wątku asynchronicznego. Bez niego spany rozpoczęte w zadaniach asynchronicznych nie mają rodzica, przerywając drzewo trace'ów.

Użycie adnotacji @Observed i @NewSpan

Spring Boot 3.4 wspiera deklaratywną obserwowalność przez adnotacje. Przetwarzanie adnotacji wymaga dodania AspectJ weaver:

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) {
        // Zapytanie do bazy danych
        return new StockLevel(productId, 42);
    }

    @NewSpan("inventory.reserve")  // Tworzy span potomny pod bieżącym trace'em
    public ReservationResult reserveStock(String productId, int quantity) {
        // Aktualizacja magazynu
        return new ReservationResult(true, "RES-" + System.currentTimeMillis());
    }
}

@Observed tworzy zarówno metrykę (timer), jak i span. @NewSpan tworzy tylko span, przydatne gdy operacja ma już metryki gdzie indziej. Obie adnotacje automatycznie obsługują rejestrowanie wyjątków i status spana.

Automatycznie instrumentowane komponenty w Spring Boot 3.4

Spring Boot 3.4 automatycznie instrumentuje kilka komponentów bez zmian w kodzie:

KomponentNazwa obserwacjiCo rejestruje
Spring MVChttp.server.requestsŚcieżka żądania, metoda, status, wyjątek
WebClienthttp.client.requestsWychodzące wywołania HTTP
RestClienthttp.client.requestsWychodzące wywołania HTTP (Spring 6.1+)
Spring Kafkaspring.kafka.listenerGrupa konsumentów, topic, partycja
Spring Data JPAspring.data.repositoryMetoda repozytorium, czas zapytania
Scheduled Tasksspring.schedulingNazwa zadania, czas wykonania

Dokumentacja Spring Boot Actuator wymienia wszystkie automatycznie instrumentowane komponenty. Biblioteki zewnętrzne jak Datasource Micrometer dodają tracing zapytań JDBC.

Filtrowanie obserwacji w celu redukcji szumu

Health checki, readiness probes i zasoby statyczne generują szum w trace'ach. Można je odfiltrować używając predykatów:

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

Alternatywnie można wyłączyć obserwacje po nazwie w konfiguracji:

yaml
# application.yml
management:
  observations:
    enable:
      spring.security: false  # Wyłącz obserwacje Spring Security
      http.server.requests.actuator: false  # Własna nazwa predykatu
Kontekst rekrutacyjny: samplowanie vs filtrowanie

Samplowanie redukuje procent trace'ów zbieranych ze wszystkich endpointów. Filtrowanie usuwa określone endpointy całkowicie. Filtrowania należy używać dla endpointów, które nigdy nie dostarczają wartości diagnostycznej (health checki, scraping metryk). Samplowania należy używać do kontrolowania kosztów przy zachowaniu reprezentatywnych danych.

Korelacja logów z ID trace'ów

Spring Boot 3.4 automatycznie dodaje ID trace'a i spana do MDC (Mapped Diagnostic Context), gdy Micrometer Tracing jest aktywny. Logback i Log4j2 mogą zawierać te ID w wyjściu logów:

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>

Z tym wzorcem każda linia logu zawiera ID trace'a. Wyszukiwanie logów po ID trace'a zwraca wszystkie wiadomości z pojedynczego żądania ze wszystkich serwisów, korelując logi ze spanami w Jaegerze lub Tempo.

Częste pytania rekrutacyjne dotyczące obserwowalności Spring Boot

Rekruterzy oceniają zarówno rozumienie koncepcyjne, jak i praktyczne doświadczenie z obserwowalnością. Te pytania pojawiają się często na stanowiskach senior backend.

P: Jaka jest różnica między Micrometer Tracing a OpenTelemetry?

Micrometer Tracing to vendor-neutral API dla distributed tracing, podobnie jak Micrometer abstrahuje metryki. OpenTelemetry to konkretna implementacja i protokół. Spring Boot używa Micrometer Tracing jako API i mostuje do OpenTelemetry w celu eksportu przez micrometer-tracing-bridge-otel. Ta warstwa pozwala aplikacjom zmieniać backendy bez zmian w kodzie.

P: Dlaczego Spring zaleca Observation API zamiast bezpośredniej instrumentacji OpenTelemetry?

Observation API zapewnia pojedynczy punkt instrumentacji emitujący zarówno metryki, jak i trace'y. Bezpośrednie wywołania OpenTelemetry produkują tylko trace'y. Użycie Observation.observe() generuje metrykę timer i span z tego samego kodu, redukując duplikację i zapewniając spójne nazewnictwo.

P: Jak debugować trace, który pokazuje lukę między spanami?

Luki wskazują na brakującą instrumentację lub przerwanie propagacji kontekstu. Należy sprawdzić, czy kod używa operacji asynchronicznych bez ContextPropagatingTaskDecorator. Zweryfikować, czy klienty HTTP są instrumentowane (WebClient, RestClient lub ręcznie opakowany RestTemplate). Dla kolejek wiadomości potwierdzić, że nagłówki trace'ów propagują przez właściwości wiadomości.

P: Co się dzieje, gdy dodasz tag o wysokiej kardynalności do metryki?

Każda unikalna wartość tagu tworzy nową serię czasową. Tag ID użytkownika z milionami użytkowników tworzy miliony serii, wyczerpując pamięć w Prometheusie lub innych backendach TSDB. Rozwiązaniem jest użycie highCardinalityKeyValue() zamiast lowCardinalityKeyValue(), które dodaje tag tylko do trace'ów, gdzie wysoka kardynalność jest oczekiwana.

Budowanie pipeline'u distributed tracing z Docker Compose

Lokalny stack obserwowalności pomaga zwalidować instrumentację przed wdrożeniem do produkcji. Ten przykład używa OpenTelemetry Collector i Jaegera:

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]

Kolektor odbiera spany z aplikacji Spring Boot i przekazuje je do Jaegera. Ta architektura pozwala dodawać eksportery (Tempo, Zipkin, backendy chmurowe) bez zmiany kodu aplikacji.

Zacznij ćwiczyć!

Sprawdź swoją wiedzę z naszymi symulatorami rozmów i testami technicznymi.

Kluczowe wnioski dotyczące obserwowalności Spring Boot

  • Observation API unifikuje metryki i tracing: jedno wywołanie Observation.observe() produkuje zarówno metrykę timer, jak i span, eliminując podwójną instrumentację.
  • Dodanie micrometer-tracing-bridge-otel i opentelemetry-exporter-otlp włącza eksport OTLP. Spring Boot 3.4 automatycznie konfiguruje eksporter HTTP z endpointem z management.otlp.tracing.endpoint.
  • lowCardinalityKeyValue() należy używać dla wymiarów pojawiających się w metrykach (ograniczone wartości). highCardinalityKeyValue() należy używać dla danych tylko w trace'ach (ID użytkowników, ID żądań).
  • Propagację kontekstu dla kodu asynchronicznego włącza spring.task.execution.propagate-context=true oraz opakowywanie własnych executorów w ContextPropagatingTaskDecorator.
  • Filtrowanie hałaśliwych endpointów jak /actuator/health przy użyciu beanów ObservationPredicate utrzymuje trace'y skoncentrowane na operacjach biznesowych.
  • Wzorce logów powinny zawierać %X{traceId}, aby korelować linie logów z distributed trace'ami w backendzie obserwowalności.
  • Pytania rekrutacyjne o obserwowalność skupiają się na kardynalności, błędach propagacji kontekstu oraz różnicy między Observation API a bezpośrednim użyciem OpenTelemetry.
Wyzwanie dnia

Znajdziesz błąd w Spring Boot?

Prawdziwy fragment kodu, ukryty błąd, jedna próba dziennie. Bez konta, żeby spróbować.

Anthony Fillion-Maillet

Autor:

Anthony Fillion-Maillet

Założyciel SharpSkill

Programista fullstack od ponad 10 lat. Prowadzi SharpSkill i odpowiada za wszystko, co się tu ukazuje.

Zaktualizowano 19 września 2026

Tagi

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

Udostępnij

Powiązane artykuły