# 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. - Published: 2026-09-19 - Updated: 2026-09-19 - Author: Anthony Fillion-Maillet - Tags: spring-boot, observability, opentelemetry, distributed-tracing, micrometer - Reading time: 12 min --- 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. ```java // ObservabilityConfig.java @Configuration public class ObservabilityConfig { @Bean public ObservationRegistryCustomizer 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 org.springframework.boot spring-boot-starter-actuator io.micrometer micrometer-tracing-bridge-otel io.opentelemetry opentelemetry-exporter-otlp ``` 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](https://grafana.com/oss/tempo/) i [Jaeger](https://www.jaegertracing.io/) 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). ```java // PaymentService.java @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. ## 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`: ```java // AsyncConfig.java @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 org.springframework.boot spring-boot-starter-aop ``` ```yaml # application.yml management: observations: annotations: enabled: true ``` ```java // InventoryService.java @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: | Komponent | Nazwa obserwacji | Co rejestruje | |-----------|-----------------|---------------| | Spring MVC | `http.server.requests` | Ścieżka żądania, metoda, status, wyjątek | | WebClient | `http.client.requests` | Wychodzące wywołania HTTP | | RestClient | `http.client.requests` | Wychodzące wywołania HTTP (Spring 6.1+) | | Spring Kafka | `spring.kafka.listener` | Grupa konsumentów, topic, partycja | | Spring Data JPA | `spring.data.repository` | Metoda repozytorium, czas zapytania | | Scheduled Tasks | `spring.scheduling` | Nazwa zadania, czas wykonania | [Dokumentacja Spring Boot Actuator](https://docs.spring.io/spring-boot/reference/actuator/observability.html) wymienia wszystkie automatycznie instrumentowane komponenty. Biblioteki zewnętrzne jak [Datasource Micrometer](https://github.com/jdbc-observations/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: ```java // ObservationFilterConfig.java @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 %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - traceId=%X{traceId} spanId=%X{spanId} - %msg%n ``` 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](https://opentelemetry.io/docs/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. ## 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. --- Source: SharpSkill (https://sharpskill.dev), tech interview preparation for your real stack. HTML version of this page: https://sharpskill.dev/pl/blog/spring-boot/spring-boot-observability-opentelemetry-distributed-tracing