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

Что такое MDC?

2.2 Middle🔥 111 комментариев
#Docker, Kubernetes и DevOps

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

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

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

MDC (Mapped Diagnostic Context) в Java

Отличный вопрос про инструмент, который критичен для отладки production систем! MDC помогает отследить логи одного запроса через всю систему.

Что такое MDC?

MDC (Mapped Diagnostic Context) - это механизм логирования, который позволяет привязать контекстную информацию (например, ID запроса или ID пользователя) к одному потоку выполнения (thread), чтобы она автоматически добавлялась во все логи этого потока.

Проблема без MDC

Представь логи в production:

2024-03-23 10:15:30 [main] INFO  UserService - Creating user
2024-03-23 10:15:31 [main] INFO  PaymentService - Processing payment
2024-03-23 10:15:32 [main] INFO  EmailService - Sending confirmation
2024-03-23 10:15:33 [main] ERROR Database - Connection failed

Когда у тебя 1000 одновременных запросов:

2024-03-23 10:15:30 [thread-1] INFO  UserService - Creating user
2024-03-23 10:15:30 [thread-2] INFO  UserService - Creating user
2024-03-23 10:15:31 [thread-1] INFO  PaymentService - Processing payment
2024-03-23 10:15:31 [thread-2] INFO  PaymentService - Processing payment
2024-03-23 10:15:32 [thread-1] INFO  EmailService - Sending confirmation
2024-03-23 10:15:32 [thread-2] ERROR Database - Connection failed

Невозможно отследить логи одного запроса! Какой заказ относится к какому пользователю?

Решение - MDC

MDC добавляет уникальный ID к каждому логу:

2024-03-23 10:15:30 [request-001] INFO  UserService - Creating user
2024-03-23 10:15:30 [request-002] INFO  UserService - Creating user
2024-03-23 10:15:31 [request-001] INFO  PaymentService - Processing payment
2024-03-23 10:15:31 [request-002] INFO  PaymentService - Processing payment
2024-03-23 10:15:32 [request-001] INFO  EmailService - Sending confirmation
2024-03-23 10:15:32 [request-002] ERROR Database - Connection failed

Теперь ясно: все логи с request-001 относятся к одному пользователю!

Как использовать MDC в Spring Boot

1. Добавьте зависимость (обычно уже есть в spring-boot-starter-logging):

<dependency>
    <groupId>org.slf4j</groupId>
    <artifactId>slf4j-api</artifactId>
</dependency>
<dependency>
    <groupId>ch.qos.logback</groupId>
    <artifactId>logback-classic</artifactId>
</dependency>

2. Создайте фильтр для установки MDC в каждом запросе:

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 MDCFilter extends OncePerRequestFilter {
    private static final String REQUEST_ID = "requestId";
    private static final String USER_ID = "userId";

    @Override
    protected void doFilterInternal(
            HttpServletRequest request,
            HttpServletResponse response,
            FilterChain filterChain) throws ServletException, IOException {
        // Генерируем уникальный ID запроса
        String requestId = UUID.randomUUID().toString();
        MDC.put(REQUEST_ID, requestId);

        // Можно также добавить информацию о пользователе (если она доступна)
        String userId = extractUserId(request);
        if (userId != null) {
            MDC.put(USER_ID, userId);
        }

        try {
            filterChain.doFilter(request, response);
        } finally {
            // ВАЖНО: очищай MDC после выполнения запроса
            MDC.clear();
        }
    }

    private String extractUserId(HttpServletRequest request) {
        // Извлекаем userId из header, token, session, и т.д.
        String authHeader = request.getHeader("Authorization");
        if (authHeader != null && authHeader.startsWith("Bearer ")) {
            // Парси JWT и извлекай userId
            return "user-123"; // пример
        }
        return null;
    }
}

3. Настройте logback для вывода MDC:

<!-- src/main/resources/logback-spring.xml -->
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
    <appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
        <encoder>
            <!-- Добавляем requestId и userId в логи -->
            <pattern>
                %d{HH:mm:ss.SSS} [%X{requestId}] [%X{userId}] %-5level %logger{36} - %msg%n
                <!-- %X{requestId} - значение из MDC с ключом "requestId" -->
                <!-- %X{userId} - значение из MDC с ключом "userId" -->
            </pattern>
        </encoder>
    </appender>

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

Пример использования в сервисе

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.slf4j.MDC;
import org.springframework.stereotype.Service;

@Service
public class OrderService {
    private static final Logger logger = LoggerFactory.getLogger(OrderService.class);

    public void createOrder(Order order) {
        // requestId уже установлен в MDC фильтром
        logger.info("Creating order: {}", order.getId());
        // Лог автоматически включит requestId!

        validateOrder(order);
        saveOrder(order);
        notifyCustomer(order);
    }

    private void validateOrder(Order order) {
        logger.debug("Validating order items");
        // requestId АВТОМАТИЧЕСКИ добавится в этот лог тоже
    }

    private void saveOrder(Order order) {
        logger.info("Saving order to database");
        // И в этот
    }

    private void notifyCustomer(Order order) {
        logger.info("Sending notification email");
        // И в этот
    }
}

Результат логирования

2024-03-23 10:15:30.123 [550e8400-e29b-41d4-a716-446655440000] [user-456] INFO OrderService - Creating order: ORD-789
2024-03-23 10:15:30.124 [550e8400-e29b-41d4-a716-446655440000] [user-456] DEBUG OrderService - Validating order items
2024-03-23 10:15:30.125 [550e8400-e29b-41d4-a716-446655440000] [user-456] INFO OrderService - Saving order to database
2024-03-23 10:15:30.126 [550e8400-e29b-41d4-a716-446655440000] [user-456] INFO OrderService - Sending notification email

2024-03-23 10:15:30.127 [a1b2c3d4-e5f6-4g7h-i8j9-k0l1m2n3o4p5] [user-789] INFO OrderService - Creating order: ORD-790
2024-03-23 10:15:30.128 [a1b2c3d4-e5f6-4g7h-i8j9-k0l1m2n3o4p5] [user-789] DEBUG OrderService - Validating order items

Теперь чётко видно какие логи относятся к какому запросу и какому пользователю!

Важные моменты

1. Очистка MDC - КРИТИЧНО в thread pool:

// ПРАВИЛЬНО - очищаем в finally
try {
    MDC.put("requestId", id);
    processRequest();
} finally {
    MDC.clear(); // ОБЯЗАТЕЛЬНО!
}

// НЕПРАВИЛЬНО - утечка MDC в thread pool
MDC.put("requestId", id);
processRequest();
// Если поток переиспользуется, old requestId останется!

2. MDC в асинхронных операциях:

// Проблема: асинхронный код выполняется в другом потоке
@Async
public void sendEmailAsync(String email) {
    // requestId ПОТЕРЯЕТСЯ - мы в другом потоке!
    logger.info("Sending email to {}", email);
}

// Решение: копируй MDC
public void sendEmailAsync(String email) {
    Map<String, String> mdc = MDC.getCopyOfContextMap();
    
    asyncExecutor.execute(() -> {
        MDC.setContextMap(mdc); // восстанавливаем контекст
        try {
            logger.info("Sending email to {}", email);
        } finally {
            MDC.clear();
        }
    });
}

3. Несколько ключей в MDC:

MDC.put("requestId", "req-123");
MDC.put("userId", "user-456");
MDC.put("correlationId", "corr-789");
MDC.put("traceId", "trace-000");

// Все будут добавлены в логи согласно pattern в logback
// %X{requestId} %X{userId} %X{correlationId} %X{traceId}

Рекомендуемые ключи MDC

КлючЗначениеПример
requestIdУникальный ID запроса550e8400-e29b-41d4
userIdID пользователяuser-456
correlationIdID для связи микросервисовcorr-789
traceIdТрейс для OpenTelemetrytrace-000
sessionIdID сессииsession-123

Вывод

MDC - это essential инструмент для:

  • Отслеживания запросов через всю систему
  • Отладки production проблем - мгновенно найти все логи одного запроса
  • Мониторинга и анализа - корреляция логов
  • Безопасности - отслеживание действий пользователей

Best Practice: используй MDC в каждом Spring Boot приложении с фильтром, который добавляет requestId в начале и очищает в конце.

Что такое MDC? | PrepBro