Как реализовать трассировку сервиса на Spring Boot?
Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Как реализовать трассировку сервиса на 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 с метриками
Правильная трассировка — это окно в сердце твоего приложения, позволяющее увидеть ВСЕ, что происходит и КОГДА это происходит.