← Назад к вопросам

Как реализовать трассировку сервиса на Spring Boot?

2.0 Middle🔥 251 комментариев
#Spring Boot и Spring Data

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI22 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Как реализовать трассировку сервиса на Spring Boot

Трассировка (Tracing) — это процесс отслеживания пути запроса через все компоненты системы: от входного контроллера через различные сервисы к базе данных и обратно. Это критично для отладки в микросервисной архитектуре и мониторинга производительности. В Spring Boot используется Spring Cloud Sleuth для простой трассировки и интеграция с Jaeger/Zipkin для распределённой трассировки.

Уровень 1: Базовая трассировка с Spring Cloud Sleuth

Добавь зависимость

<!-- pom.xml -->
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-sleuth</artifactId>
    <version>3.1.0</version>
</dependency>

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

Sleuth автоматически добавляет trace ID и span ID ко всем логам:

@RestController
@RequestMapping("/api/users")
public class UserController {
    
    private static final Logger logger = LoggerFactory.getLogger(UserController.class);
    
    @GetMapping("/{id}")
    public User getUser(@PathVariable Long id) {
        logger.info("Getting user with id: {}", id);
        return userService.findById(id);
    }
}

// Логи будут выглядеть так:
// 2026-03-22 10:15:30.123 INFO [myapp,4eaea46ab0d4ac9b,4eaea46ab0d4ac9b] 123 --- UserController: Getting user with id: 1
//                                   ↑ trace ID     ↑ span ID

Trace ID — уникальный идентификатор для всей операции
Span ID — идентификатор отдельного шага в операции

Уровень 2: Интеграция с Jaeger для визуализации

Jaeger позволяет визуализировать весь путь запроса.

Добавь зависимость

<dependency>
    <groupId>io.micrometer</groupId>
    <artifactId>micrometer-tracing-bridge-brave</artifactId>
</dependency>

<dependency>
    <groupId>io.zipkin.reporter2</groupId>
    <artifactId>zipkin-reporter-brave</artifactId>
</dependency>

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

spring:
  application:
    name: user-service
  sleuth:
    sampler:
      probability: 1.0 # 100% вероятность трассировки (в prod меньше)

management:
  tracing:
    sampling:
      probability: 1.0
  endpoints:
    web:
      exposure:
        include: health,info,metrics,traces

Запуск Jaeger локально

# Docker Compose
version: 3.8
services:
  jaeger:
    image: jaegertracing/all-in-one:latest
    ports:
      - "16686:16686" # UI
      - "14268:14268" # HTTP collector
      - "6831:6831/udp" # Thrift compact

# Запуск
docker compose up -d

# Откройте http://localhost:16686

Конфигурация отправки трассировок

@Configuration
public class TracingConfig {
    
    @Bean
    public Sampler defaultSampler() {
        // Трассируй все запросы (для разработки)
        return Sampler.ALWAYS_SAMPLE;
        // Для продакшена используй:
        // return Sampler.create(0.1f); // 10% запросов
    }
}

Уровень 3: Кастомные Spans для отслеживания бизнес-логики

С помощью Tracer можно создавать свои spans:

@Service
public class UserService {
    
    private final Tracer tracer;
    private static final Logger logger = LoggerFactory.getLogger(UserService.class);
    
    public UserService(Tracer tracer) {
        this.tracer = tracer;
    }
    
    public User findById(Long id) {
        // Создай кастомный span
        Span span = tracer.nextSpan()
            .name("findUserById")
            .tag("userId", id.toString())
            .start();
        
        try (Tracer.SpanInScope ws = tracer.withSpan(span)) {
            logger.info("Finding user with id: {}", id);
            
            // Создай вложенный span для запроса БД
            Span dbSpan = tracer.nextSpan()
                .name("db.query.user")
                .tag("db.operation", "SELECT")
                .start();
            
            try (Tracer.SpanInScope dbScope = tracer.withSpan(dbSpan)) {
                User user = userRepository.findById(id)
                    .orElseThrow(() -> new UserNotFoundException(id));
                dbSpan.tag("db.result_rows", "1");
                return user;
            } finally {
                dbSpan.end();
            }
        } finally {
            span.end();
        }
    }
}

Уровень 4: @NewSpan аннотация (самый удобный способ)

@Service
public class OrderService {
    
    @Autowired
    private OrderRepository repository;
    
    @NewSpan("createOrder")
    public Order createOrder(
            @SpanTag("userId") Long userId,
            @SpanTag("amount") BigDecimal amount) {
        Order order = new Order();
        order.setUserId(userId);
        order.setAmount(amount);
        
        return repository.save(order);
    }
    
    @NewSpan("processPayment")
    public void processPayment(
            @SpanTag("orderId") Long orderId,
            @SpanTag("gateway") String paymentGateway) {
        // Логика обработки платежа
    }
}

// Использование
@RestController
public class OrderController {
    
    @Autowired
    private OrderService orderService;
    
    @PostMapping("/orders")
    public Order createOrder(@RequestBody CreateOrderRequest req) {
        Order order = orderService.createOrder(req.getUserId(), req.getAmount());
        orderService.processPayment(order.getId(), "stripe");
        return order;
    }
}

Уровень 5: Контекст в асинхронных операциях

При использовании async нужно явно передавать trace context:

@Service
public class AsyncOrderService {
    
    @Autowired
    private Tracer tracer;
    
    @Async
    public CompletableFuture<Order> processOrderAsync(Long orderId) {
        // Сохраняем текущий trace context
        Span currentSpan = tracer.currentSpan();
        
        return CompletableFuture.supplyAsync(() -> {
            try (Tracer.SpanInScope ws = tracer.withSpan(currentSpan)) {
                // Выполняем async операцию в контексте трассировки
                return processOrder(orderId);
            }
        });
    }
    
    private Order processOrder(Long orderId) {
        // Длительная операция
        return null;
    }
}

Или используй spring-cloud-sleuth-otel для автоматического управления контекстом в async операциях:

<dependency>
    <groupId>io.micrometer</groupId>
    <artifactId>micrometer-tracing-bridge-otel</artifactId>
</dependency>

Уровень 6: Распределённая трассировка между микросервисами

Когда запрос переходит между сервисами, нужно передать trace ID:

// Сервис 1: UserService
@RestController
@RequestMapping("/api/users")
public class UserController {
    
    @Autowired
    private OrderServiceClient orderClient;
    
    @GetMapping("/{id}/orders")
    public UserWithOrders getUserWithOrders(@PathVariable Long id) {
        User user = userRepository.findById(id).get();
        
        // HTTP клиент автоматически передаёт trace headers
        List<Order> orders = orderClient.getUserOrders(id);
        
        return new UserWithOrders(user, orders);
    }
}

// Клиент для вызова других сервисов
@Component
public class OrderServiceClient {
    
    @Autowired
    private RestTemplate restTemplate; // Spring автоматически инструментирует
    
    public List<Order> getUserOrders(Long userId) {
        // Trace ID автоматически передаётся в заголовке
        // X-B3-TraceId, X-B3-SpanId, X-B3-ParentSpanId
        return restTemplate.getForObject(
            "http://order-service/api/orders?userId=" + userId,
            new ParameterizedTypeReference<List<Order>>() {}
        );
    }
}

// Сервис 2: OrderService получает trace context
@RestController
@RequestMapping("/api/orders")
public class OrderController {
    
    @GetMapping
    public List<Order> getUserOrders(
            @RequestParam Long userId,
            @RequestHeader("X-B3-TraceId") String traceId) {
        // Trace ID автоматически используется в логах
        logger.info("Getting orders for user: {}", userId);
        return orderRepository.findByUserId(userId);
    }
}

Уровень 7: Метрики производительности в трассировке

@Service
public class PerformanceMonitoringService {
    
    @Autowired
    private Tracer tracer;
    
    @Autowired
    private MeterRegistry meterRegistry;
    
    public void trackDuration(String operationName, Runnable operation) {
        Span span = tracer.nextSpan()
            .name(operationName)
            .start();
        
        long startTime = System.nanoTime();
        
        try (Tracer.SpanInScope ws = tracer.withSpan(span)) {
            operation.run();
        } finally {
            long duration = System.nanoTime() - startTime;
            span.tag("duration_ns", String.valueOf(duration));
            
            // Регистрируем метрику
            meterRegistry.timer(operationName).record(duration, TimeUnit.NANOSECONDS);
            span.end();
        }
    }
}

Чеклист реализации трассировки

Добавь Spring Cloud Sleuth — автоматическое логирование trace/span ID
Интегрируй Jaeger — визуализация путей запросов
Создавай кастомные spans — отслеживай критические операции
Используй @NewSpan — декларативный способ
Передавай контекст между сервисами — для распределённой трассировки
Избегай 100% выборки в продакшене — используй sampling
Монитори производительность — связывай traces с метриками

Правильная трассировка — это окно в сердце твоего приложения, позволяющее увидеть ВСЕ, что происходит и КОГДА это происходит.

Как реализовать трассировку сервиса на Spring Boot? | PrepBro