Ответ
MDC (Mapped Diagnostic Context) — это механизм, предоставляемый фреймворками логирования (SLF4J/Logback, Log4j 2), для прикрепления контекстной информации ко всем log-сообщениям, создаваемым в рамках текущего потока выполнения (ThreadLocal).
Основное назначение: Сквозная идентификация запросов или операций в логах, что критически важно для отладки распределенных систем.
Типичные сценарии использования:
- ID запроса (requestId): Для группировки всех логов, относящихся к одному HTTP-запросу.
- ID пользователя (userId): Для отслеживания действий конкретного пользователя.
- ID сессии (sessionId): Для анализа поведения в рамках сессии.
- ID транзакции (transactionId): В batch-процессах или финансовых операциях.
Пример использования в веб-приложении (с использованием фильтра):
import org.slf4j.MDC;
import javax.servlet.*;
import java.io.IOException;
import java.util.UUID;
public class MdcLoggingFilter implements Filter {
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
try {
// Помещаем уникальный ID запроса в MDC
MDC.put("requestId", UUID.randomUUID().toString());
MDC.put("userIp", request.getRemoteAddr());
// Все логи внутри цепочки фильтров и обработки запроса будут содержать эти поля
chain.doFilter(request, response);
} finally {
// Очищаем контекст после обработки запроса (важно!)
MDC.clear();
}
}
}
Конфигурация в logback.xml для вывода значений MDC:
<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>%d{HH:mm:ss} [%thread] [%X{requestId}] %-5level %logger{36} - %msg%n</pattern>
</encoder>
</appender>
Вывод в лог: 14:25:03 [http-nio-8080-exec-1] [abc-123-def] INFO c.example.MyController - Processing request
Важные особенности:
- ThreadLocal: Контекст привязан к потоку. При ручном создании потоков или использовании пулов (например,
ExecutorService) MDC не копируется автоматически. Для передачи контекста нужно использоватьMDC.getCopyOfContextMap()иMDC.setContextMap(). - Очистка: Крайне важно очищать MDC (например, в блоке
finally) после завершения операции, чтобы избежать утечки памяти и некорректных данных в последующих операциях, обработанных тем же потоком из пула.
Ответ 18+ 🔞
А, MDC! Ну это ж, блядь, та самая штука, которая позволяет не ебаться как конь в попытках понять, какой из тысячи одинаковых логов к какому запросу относится. Представь: у тебя сервис, на него наваливается овердохуища запросов одновременно, логи летят в одну кучу, и ты потом, как мудак, полдня сидишь и пытаешьсь собрать пазл — что к чему. Вот MDC эту проблему и решает, нахуй.
Суть проще пареной репы: это как временная татуировка для потока выполнения. Зашёл запрос в поток — нарисовали на нём маркером requestId=abc-123. И всё, что этот поток дальше ни делает, все его логи будут помечены этим ID. Красота, а не жизнь!
Где это, блядь, реально спасает жопу:
- Сквозной ID запроса — главный кейс. Без этого в микросервисах просто пиздец, а не отладка.
- ID юзверя — чтобы отследить, что этот ёбаный пользователь опять что-то сломал.
- ID сессии — ну, для полного аудита, чтобы знать, кто, когда и какую дичь творил.
Вот как это выглядит в коде, на примере простого фильтра:
import org.slf4j.MDC;
import javax.servlet.*;
import java.io.IOException;
import java.util.UUID;
public class MdcLoggingFilter implements Filter {
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
try {
// Вешаем на поток бирку с уникальным номером запроса
MDC.put("requestId", UUID.randomUUID().toString());
MDC.put("userIp", request.getRemoteAddr());
// Теперь все логи внутри цепочки будут с этими полями
chain.doFilter(request, response);
} finally {
// А это, сука, ВАЖНО! Стираем бирку после работы. Как в бане — помылся, убери за собой!
MDC.clear();
}
}
}
А в конфиге логгера (logback.xml) просто добавляем волшебный %X{ключ}:
<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>%d{HH:mm:ss} [%thread] [%X{requestId}] %-5level %logger{36} - %msg%n</pattern>
</encoder>
</appender>
И в логах вместо каши будет чётко:
14:25:03 [http-nio-8080-exec-1] [abc-123-def] INFO c.example.MyController - Processing request
Но есть, блядь, подводные грабли, о которые все расшибают лбы:
- ThreadLocal, ёпта! Контекст живёт только в одном потоке. Если ты внутри запроса запускаешь асинхронную хуйню через
ExecutorServiceили новые потоки — там MDC будет пустой! Контекст не копируется автоматом. Придётся вручную передавать словарик (MDC.getCopyOfContextMap()) и в новом потоке его применять (MDC.setContextMap()). Иначе логи потеряются, и ты останешься с носом. - Очистка — святое дело! Забыл
MDC.clear()вfinally? Поздравляю, у тебя теперь утечка контекста. Поток из пула вернётся с чужимrequestIdна борту, и следующий запрос в логах будет под левым ID. Полный пиздец и неразбериха. Так что не забывай за собой убирать, мартышлюшка!