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

Что нужно сделать, чтобы появился traceId в логах?

2.3 Middle🔥 121 комментариев
#REST API и микросервисы#SOLID и паттерны проектирования

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

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

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

# Добавление traceId в логи (Distributed Tracing)

traceId — это уникальный идентификатор, который связывает все логи одного запроса, пока тот проходит через разные компоненты приложения и микросервисы. Это критично для debugging в production.

1. Использование Spring Cloud Sleuth (РЕКОМЕНДУЕТСЯ)

Это самый простой способ для Spring приложений.

Шаг 1: Добавляем зависимость

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

Шаг 2: Настраиваем Logback

<!-- src/main/resources/logback-spring.xml -->
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
    <appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
        <encoder>
            <!-- %X{traceId} и %X{spanId} автоматически добавляются Sleuth -->
            <pattern>
                %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - [%X{traceId},%X{spanId}] %msg%n
            </pattern>
        </encoder>
    </appender>

    <root level="INFO">
        <appender-ref ref="CONSOLE"/>
    </root>
</configuration>

Результат логов

2024-03-22 10:15:23.456 [main] INFO com.example.UserService - [abc123def456,xyz789] User created: John
2024-03-22 10:15:24.123 [main] INFO com.example.EmailService - [abc123def456,xyz789] Email sent

2. Spring Cloud Sleuth + Zipkin (для микросервисов)

Для полного distributed tracing с визуализацией.

Шаг 1: Зависимости

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-zipkin</artifactId>
    <version>2.2.10.RELEASE</version>
</dependency>

Шаг 2: Конфигурация (application.yml)

# application.yml
spring:
  application:
    name: user-service
  sleuth:
    sampler:
      probability: 1.0  # Собирать 100% трейсов (для dev)
  zipkin:
    base-url: http://localhost:9411  # URL Zipkin сервера
    enabled: true

Шаг 3: Запуск Zipkin

# Docker
docker run -d -p 9411:9411 openzipkin/zipkin

# Или Docker Compose
version: '3.8'
services:
  zipkin:
    image: openzipkin/zipkin
    ports:
      - "9411:9411"

3. Ручная реализация с MDC (Mapped Diagnostic Context)

Для более контроля или если не используешь Spring Sleuth.

Шаг 1: Создаём фильтр для генерации traceId

import org.slf4j.MDC;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;

import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.UUID;

@Component
public class TraceIdFilter extends OncePerRequestFilter {
    
    private static final String TRACE_ID = "traceId";
    private static final String TRACE_ID_HEADER = "X-Trace-Id";
    
    @Override
    protected void doFilterInternal(HttpServletRequest request, 
                                   HttpServletResponse response,
                                   FilterChain filterChain) 
            throws ServletException, IOException {
        
        // 1. Проверяем, есть ли уже traceId в заголовке (из upstream сервиса)
        String traceId = request.getHeader(TRACE_ID_HEADER);
        if (traceId == null || traceId.isEmpty()) {
            // Генерируем новый traceId
            traceId = UUID.randomUUID().toString();
        }
        
        // 2. Добавляем traceId в MDC (доступен для всех логов)
        MDC.put(TRACE_ID, traceId);
        
        try {
            // 3. Добавляем traceId в response header для downstream сервисов
            response.addHeader(TRACE_ID_HEADER, traceId);
            
            // 4. Продолжаем цепочку фильтров
            filterChain.doFilter(request, response);
        } finally {
            // 5. Очищаем MDC в конце запроса
            MDC.remove(TRACE_ID);
        }
    }
}

Шаг 2: Логирование с traceId

<!-- logback-spring.xml -->
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
    <appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
        <encoder>
            <!-- %X{traceId} извлекает значение из MDC -->
            <pattern>
                %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - [%X{traceId}] %msg%n
            </pattern>
        </encoder>
    </appender>

    <appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <file>logs/application.log</file>
        <rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
            <fileNamePattern>logs/application.%d{yyyy-MM-dd}.%i.log</fileNamePattern>
            <maxFileSize>10MB</maxFileSize>
            <maxHistory>30</maxHistory>
        </rollingPolicy>
        <encoder>
            <pattern>
                %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - [%X{traceId}] %msg%n
            </pattern>
        </encoder>
    </appender>

    <root level="INFO">
        <appender-ref ref="CONSOLE"/>
        <appender-ref ref="FILE"/>
    </root>
</configuration>

Шаг 3: Использование в коде

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.slf4j.MDC;

@Service
public class UserService {
    private static final Logger logger = LoggerFactory.getLogger(UserService.class);
    
    public User createUser(CreateUserRequest request) {
        // traceId автоматически добавляется к каждому логу
        logger.info("Creating user: {}", request.getEmail());
        
        User user = new User(request.getEmail(), request.getName());
        User saved = userRepository.save(user);
        
        logger.info("User created successfully with ID: {}", saved.getId());
        return saved;
    }
    
    public void sendWelcomeEmail(User user) {
        logger.info("Sending welcome email to: {}", user.getEmail());
        
        // traceId проходит через все методы одного запроса
        emailService.send(user.getEmail(), "Welcome!");
        
        logger.info("Email sent successfully");
    }
}

@Service
public class EmailService {
    private static final Logger logger = LoggerFactory.getLogger(EmailService.class);
    
    public void send(String email, String subject) {
        // Тот же traceId будет в этом логе
        logger.info("Sending email to: {} with subject: {}", email, subject);
        
        // Отправка...
        logger.debug("Email sent to SMTP");
    }
}

4. Передача traceId между микросервисами

RestTemplate с traceId

import org.slf4j.MDC;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;

@Service
public class OrderService {
    private static final Logger logger = LoggerFactory.getLogger(OrderService.class);
    private static final String TRACE_ID_HEADER = "X-Trace-Id";
    private final RestTemplate restTemplate;
    
    public OrderService(RestTemplate restTemplate) {
        this.restTemplate = restTemplate;
    }
    
    public void createOrder(Order order) {
        logger.info("Creating order: {}", order.getId());
        
        // Передаём traceId в downstream сервис
        String traceId = MDC.get("traceId");
        
        HttpHeaders headers = new HttpHeaders();
        headers.set(TRACE_ID_HEADER, traceId);
        
        HttpEntity<Order> request = new HttpEntity<>(order, headers);
        
        // Вызов другого сервиса
        restTemplate.postForObject(
            "http://payment-service/api/v1/payments",
            request,
            PaymentResponse.class
        );
        
        logger.info("Order created successfully");
    }
}

WebClient с traceId (для реактивных приложений)

import org.slf4j.MDC;
import org.springframework.stereotype.Service;
import org.springframework.web.reactive.function.client.WebClient;
import reactor.util.context.Context;

@Service
public class ReactiveOrderService {
    private final WebClient webClient;
    
    public ReactiveOrderService(WebClient.Builder builder) {
        this.webClient = builder.build();
    }
    
    public Mono<PaymentResponse> createOrder(Order order) {
        String traceId = MDC.get("traceId");
        
        return webClient.post()
            .uri("http://payment-service/api/v1/payments")
            .header("X-Trace-Id", traceId)
            .bodyValue(order)
            .retrieve()
            .bodyToMono(PaymentResponse.class)
            // Пропагируем traceId в реактивную цепочку
            .contextWrite(Context.of("traceId", traceId));
    }
}

5. Интеграция с логирующей системой (ELK Stack)

Конфигурация Logstash

{
  "version": "1",
  "fields": {
    "traceId": "abc123def456",
    "spanId": "xyz789",
    "service": "user-service",
    "level": "INFO",
    "timestamp": "2024-03-22T10:15:23.456Z"
  },
  "@timestamp": "2024-03-22T10:15:23.456Z",
  "message": "User created successfully with ID: 123"
}

Поиск в Kibana

# Все логи одного запроса
traceId: "abc123def456"

# Логи конкретного сервиса
service: "user-service" AND traceId: "abc123def456"

# Ошибки в трейсе
level: "ERROR" AND traceId: "abc123def456"

6. Асинхронные операции (Async Tasks)

ThreadPoolTaskExecutor с traceId

import org.slf4j.MDC;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;

@Service
public class AsyncService {
    
    @Async
    public void sendEmailAsync(String email) {
        // ❌ Проблема: MDC не пропагируется в новый поток
        String traceId = MDC.get("traceId");
        if (traceId == null) {
            traceId = "no-trace-id";
        }
        
        // ✅ Решение: явно установи traceId в новом потоке
        MDC.put("traceId", traceId);
        
        try {
            // Отправка письма
            emailService.send(email);
        } finally {
            MDC.remove("traceId");
        }
    }
}

// Или используй TaskDecorator
@Configuration
public class AsyncConfig implements AsyncConfigurer {
    
    @Bean(name = "taskExecutor")
    public Executor taskExecutor() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        executor.setCorePoolSize(5);
        executor.setMaxPoolSize(10);
        executor.setQueueCapacity(100);
        
        // Пропагируем MDC в новые потоки
        executor.setTaskDecorator(runnable -> {
            Map<String, String> context = MDC.getCopyOfContextMap();
            return () -> {
                if (context != null) {
                    MDC.setContextMap(context);
                }
                try {
                    runnable.run();
                } finally {
                    MDC.clear();
                }
            };
        });
        
        executor.initialize();
        return executor;
    }
}

7. Сравнение подходов

ПодходСложностьПроизводительностьКогда использовать
Spring Cloud SleuthНизкаяХорошаяВсе Spring приложения
Sleuth + ZipkinСредняяХорошаяМикросервисная архитектура
Ручная реализацияСредняяОтличнаяМаксимальный контроль
MDC + LogbackНизкаяОтличнаяМонолиты и простые приложения

8. Best Practices

// ✅ Включай traceId в ВСЕ логи
logger.info("Operation result: {}", result);
// Логируется как: [abc123] Operation result: success

// ✅ Пропагируй traceId между сервисами
headers.set("X-Trace-Id", MDC.get("traceId"));

// ✅ Очищай MDC в finally блоке
try {
    // логика
} finally {
    MDC.remove("traceId");
}

// ✅ Для асинхронных операций используй TaskDecorator
// или явно пропагируй MDC

// ❌ Не логируй traceId явно (он добавится автоматически)
logger.info("Trace: {} - Message", traceId);  // Лишнее

// ❌ Не забывай о чистке MDC
MDC.put("traceId", value);
// ... код ...
// MDC.remove("traceId");  // Должно быть в finally

Выводы

  1. Spring Cloud Sleuth — самый простой способ для Spring приложений
  2. MDC + logback pattern — стандартный инструмент для работы с traceId
  3. Передавай traceId между микросервисами через HTTP заголовки
  4. Для асинхронных операций явно пропагируй traceId в новые потоки
  5. Используй Zipkin для визуализации трейсов в микросервисах
  6. Интегрируй с ELK/Kibana для централизованного логирования
  7. Тестируй что traceId правильно появляется во всех логах одного запроса
Что нужно сделать, чтобы появился traceId в логах? | PrepBro