Gymterview
middle

Как настроить distributed tracing в Spring Boot?

Начиная с Spring Boot 3.x, для distributed tracing используется Micrometer Tracing (замена Spring Cloud Sleuth). Micrometer Tracing предоставляет фасад, а конкретная реализация (OTel или Brave/Zipkin) подключается как bridge.

Подключение зависимостей (pom.xml)
<dependencies>
    <!-- Actuator (обязательно) -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-actuator</artifactId>
    </dependency>

    <!-- Micrometer Tracing — фасад -->
    <dependency>
        <groupId>io.micrometer</groupId>
        <artifactId>micrometer-tracing</artifactId>
    </dependency>

    <!-- Bridge к OpenTelemetry -->
    <dependency>
        <groupId>io.micrometer</groupId>
        <artifactId>micrometer-tracing-bridge-otel</artifactId>
    </dependency>

    <!-- OTLP exporter для отправки трейсов -->
    <dependency>
        <groupId>io.opentelemetry</groupId>
        <artifactId>opentelemetry-exporter-otlp</artifactId>
    </dependency>

    <!-- Для Prometheus метрик с exemplars -->
    <dependency>
        <groupId>io.micrometer</groupId>
        <artifactId>micrometer-registry-prometheus</artifactId>
    </dependency>
</dependencies>

Конфигурация application.yml

Пример
spring:
  application:
    name: order-service

management:
  tracing:
    sampling:
      probability: 1.0          # 1.0 = 100% для dev, 0.1 = 10% для prod
    propagation:
      type: w3c                  # W3C Trace Context (по умолчанию)
  otlp:
    tracing:
      endpoint: http://otel-collector:4318/v1/traces
  metrics:
    distribution:
      percentiles-histogram:
        http.server.requests: true
    tags:
      application: ${spring.application.name}

logging:
  pattern:
    console: "%d{yyyy-MM-dd HH:mm:ss} [%thread] [%X{traceId}/%X{spanId}] %-5level %logger{36} - %msg%n"

Автоматическое распространение контекста

Spring Boot автоматически передаёт trace context через:

  • RestTemplate / WebClient — HTTP-заголовки traceparent
  • Spring Kafka — Kafka Record Headers
  • Spring AMQP — RabbitMQ message headers
  • Spring gRPC — gRPC metadata
Пример
@Service
public class OrderService {
    private final RestClient restClient;

    public OrderService(RestClient.Builder builder) {
        this.restClient = builder
            .baseUrl("http://inventory-service:8080")
            .build();
    }

    public InventoryResponse checkStock(String productId) {
        // traceId/spanId автоматически передаются в заголовках
        return restClient.get()
            .uri("/api/inventory/{id}", productId)
            .retrieve()
            .body(InventoryResponse.class);
    }
}

Кастомные спаны с Observation API

Пример
@Service
public class OrderService {
    private final ObservationRegistry observationRegistry;

    public OrderService(ObservationRegistry observationRegistry) {
        this.observationRegistry = observationRegistry;
    }

    public Order processOrder(OrderRequest request) {
        return Observation.createNotStarted("order.processing", observationRegistry)
            .lowCardinalityKeyValue("order.type", request.getType())
            .highCardinalityKeyValue("order.id", request.getId())
            .observe(() -> {
                // Автоматически создаёт:
                // 1. Span с именем "order.processing"
                // 2. Timer-метрику "order.processing"
                Order order = createOrder(request);
                validateOrder(order);
                return order;
            });
    }
}

Аннотация @Observed

Пример
@Configuration
public class ObservationConfig {
    @Bean
    public ObservedAspect observedAspect(ObservationRegistry registry) {
        return new ObservedAspect(registry);
    }
}

@Service
public class PaymentService {
    @Observed(name = "payment.process",
              contextualName = "processing-payment",
              lowCardinalityKeyValues = {"payment.provider", "stripe"})
    public PaymentResult processPayment(PaymentRequest request) {
        // Автоматически создаётся span + метрика
        return callPaymentProvider(request);
    }
}

Логирование с traceId

Micrometer Tracing автоматически добавляет traceId и spanId в MDC (Mapped Diagnostic Context):

Пример
@Service
public class OrderService {
    private static final Logger log = LoggerFactory.getLogger(OrderService.class);

    public Order processOrder(OrderRequest request) {
        // traceId/spanId автоматически добавляются через MDC
        log.info("Processing order for customer: {}", request.getCustomerId());
        // Лог: 2026-04-22 10:30:00 [main] [abc123/def456] INFO  OrderService - Processing order...
        return doProcess(request);
    }
}
Конфигурация logback для JSON-формата
<!-- logback-spring.xml -->
<configuration>
    <appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
        <encoder class="net.logstash.logback.encoder.LogstashEncoder">
            <includeMdcKeyName>traceId</includeMdcKeyName>
            <includeMdcKeyName>spanId</includeMdcKeyName>
        </encoder>
    </appender>
    <root level="INFO">
        <appender-ref ref="CONSOLE" />
    </root>
</configuration>

Результат:

{
  "@timestamp": "2026-04-22T10:30:00.123Z",
  "level": "INFO",
  "logger_name": "com.example.OrderService",
  "message": "Processing order for customer: 42",
  "traceId": "abc123def456789",
  "spanId": "def456789abc",
  "service": "order-service"
}

Важное

  • Micrometer Tracing — фасад, аналогичный SLF4J. Нужен bridge (micrometer-tracing-bridge-otel) для конкретной реализации.
  • Observation API — единый механизм, создающий одновременно метрику и span. Используйте @Observed вместо отдельных @Timed + ручных спанов.
  • sampling.probability = 1.0 в dev/staging, 0.01-0.1 в production. В production используйте tail-based sampling на стороне OTel Collector.
  • Spring Boot 3.x автоматически пропагирует контекст через стандартные HTTP-клиенты, Kafka, RabbitMQ.

Частые ошибки

  • Забыть подключить bridge: без micrometer-tracing-bridge-otel трейсы не генерируются.
  • sampling.probability = 1.0 в production: 100% семплирование на высоконагруженном сервисе создаёт огромный overhead.
  • Потеря контекста в @Async: стандартный TaskExecutor не передаёт trace context. Используйте ContextPropagatingTaskDecorator.
  • Не добавить traceId в логи: без %X{traceId} в log pattern невозможно связать логи с трейсами.
  • Путать Spring Cloud Sleuth и Micrometer Tracing: Sleuth устарел и не поддерживается в Spring Boot 3.x.
Правильная передача контекста в @Async
@Configuration
@EnableAsync
public class AsyncConfig implements AsyncConfigurer {

    private final ObservationRegistry observationRegistry;

    public AsyncConfig(ObservationRegistry observationRegistry) {
        this.observationRegistry = observationRegistry;
    }

    @Override
    public Executor getAsyncExecutor() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        executor.setCorePoolSize(5);
        executor.setMaxPoolSize(10);
        executor.setTaskDecorator(new ContextPropagatingTaskDecorator());
        executor.initialize();
        return executor;
    }
}

Как используется в 2026

  • Micrometer Observation API — стандартный способ инструментирования в Spring-экосистеме.
  • OTLP экспорт — нативная поддержка в Spring Boot через management.otlp.tracing и management.otlp.metrics.
  • Виртуальные потоки (Project Loom) — Micrometer Context Propagation корректно работает с virtual threads.
  • Spring Boot 3.4+ — улучшенная поддержка OTel Logs, автоматическая корреляция логов с трейсами через OTLP.
  • Testcontainers + OTel — интеграционные тесты с реальным OTel Collector для проверки трейсинга.

На собеседовании: обязательно упомяните переход от Spring Cloud Sleuth к Micrometer Tracing в Spring Boot 3.x. Покажите знание Observation API как единого механизма для метрик и трейсов. Ключевая ошибка на собеседовании — не знать про необходимость bridge и ContextPropagatingTaskDecorator для @Async.