При использовании MDC в Spring Web MVC никаких проблем не возникает. MDC хранит информацию с привязкой к потоку. В Spring Web MVC каждый запрос обрабатывается отдельным персональным потоком. Разумеется, сервисы приложений используют пул потоков. Но для каждого запроса выделяется в полное пользование поток из этого пула до окончания обработки этого запроса. Общая схема изображена на … Читать далее Spring WebFlux и MDC
При использовании MDC1 в Spring Web MVC2 никаких проблем не возникает. MDC хранит информацию с привязкой к потоку. В Spring Web MVC каждый запрос обрабатывается отдельным персональным потоком. Разумеется, сервисы приложений используют пул потоков. Но для каждого запроса выделяется в полное пользование поток из этого пула до окончания обработки этого запроса. Общая схема изображена на рисунке ниже.
Обработка запросов потоками в Spring Web MVC с использованием MDC.
В случае использования Spring WebFlux3 один запрос может обрабатываться несколькими потоками с переключением между ними. По этой причине использовать MDC напрямую в Spring WebFlux нельзя.
Обработка запросов потоками в Spring WebFlux.
К счастью, существует библиотека Micrometer context-propagation, которая позволяет сохранять значения MDC в Context реактивной цепочки и восстанавливать их оттуда.
Ладно, перейдём к делу.
Для начала нужно подключить зависимость:
<dependency> <groupId>io.micrometer</groupId> <artifactId>context-propagation</artifactId> </dependency> |
Версия зависимости прописана в spring-boot-starter-parent, здесь её указывать не нужно.
Затем необходимо зарегистрировать ThreadLocalAccessor, который будет заниматься копированием значений из MDC в контекст реактивной цепочки и обратно. После чего выполнить Hooks.enableAutomaticContextPropagation();
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 |
package ru.urvanov.javaexamples.springwebfluxmdc; import java.util.List; import org.slf4j.MDC; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Configuration; import io.micrometer.context.ContextRegistry; import reactor.core.publisher.Hooks; /** * Основная настройка библиотека context-propagation. * * @author Fedor Urvanov (https://urvanov.ru) */ @Configuration public class MdcContextPropagationConfiguration { /** * <p> * В этом варианте между MDC и Context реактивных потоков передаются только * поля, перечисленные в настройке. * </p> * * <p> * При желании можно передавать все поля * из MDC, для этого необходимо использовать следующий подход: * </p> * <pre> * {@code * ContextRegistry.getInstance() * .registerThreadLocalAccessor( * "MDC", * MDC::getCopyOfContextMap, * MDC::setContextMap, * MDC::clear); * } * </pre> * @param propagationFields Список полей MDC, которые нужно передать * в Context реактивных потоков. */ public MdcContextPropagationConfiguration( @Value("${spring-webflux-mdc.propagation.fields:correlationId}") List<String> propagationFields) { propagationFields.forEach(claim -> ContextRegistry.getInstance() .registerThreadLocalAccessor(claim, () -> MDC.get(claim), value -> MDC.put(claim, value), () -> MDC.remove(claim))); Hooks.enableAutomaticContextPropagation(); } } |
Код выше использует настройку spring-webflux-mdc.propagation.fields. При желании вы можете перечислить в файле “application.properties” поля, которые будут перекладываться из MDC в Context реактивной цепочки и обратно:
spring-webflux-mdc.propagation.fields=correlationId,traceId,txId |
По умолчанию используется только поле correlationId, так как именно оно указано в аннотации @Value.
Как указано в JavaDoc к конструктору MdcContextPropagationConfiguration, если не хочется заморачиваться с описанием полей, которые передаются из MDC и в контекст реактивной цепочки и обратно, то код в теле конструктора необходимо заменить на примерно такой:
propagationFields.forEach(ContextRegistry.getInstance() .registerThreadLocalAccessor( "MDC", MDC::getCopyOfContextMap, MDC::setContextMap, MDC::clear)); Hooks.enableAutomaticContextPropagation(); |
MDC необходимо заполнять значениями в начале обработки запроса. Значение correlationId обычно берётся из HTTP-заголовка X-Correlation-Id.
Пример фильтра, который получает correlationId из запроса и сохраняет его в MDC с последующей очисткой по окончании обработки:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 |
package ru.urvanov.javaexamples.springwebfluxmdc; import java.util.UUID; import org.slf4j.MDC; import org.springframework.core.Ordered; import org.springframework.core.annotation.Order; import org.springframework.stereotype.Component; import org.springframework.web.server.ServerWebExchange; import org.springframework.web.server.WebFilter; import org.springframework.web.server.WebFilterChain; import reactor.core.publisher.Mono; /** * Фильтр, перекладывающий X-Correlation-Id из заголовка запроса в MDC и в * заголовок ответа сервера. * * @author Fedor Urvanov (https://urvanov.ru) */ @Component @Order(Ordered.HIGHEST_PRECEDENCE) public class CorrelationIdWebFilter implements WebFilter { private static final String HEADER_NAME = "X-Correlation-Id"; private static final String CORRELATION_ID_KEY = "correlationId"; @Override public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) { final String correlationId; final String correlationIdFromHeader = exchange.getRequest() .getHeaders().getFirst(HEADER_NAME); if ((correlationIdFromHeader != null && (!correlationIdFromHeader.trim().isEmpty()))) { correlationId = correlationIdFromHeader; } else { correlationId = UUID.randomUUID().toString(); } return chain.filter(exchange).doOnSubscribe(unused -> { exchange.getResponse().getHeaders().add(HEADER_NAME, correlationId); MDC.put(CORRELATION_ID_KEY, correlationId); }).doFinally(signalType -> { MDC.remove(CORRELATION_ID_KEY); }); } } |
На этом настройка завершена. Любое сообщение лога будет содержать в MDC значение correlationId. Библиотеки логирования увидят их так же, как увидели бы их без реактивных потоков. Если нужно вывести это значение в консоли, то в файле “application.properties” необходимо изменить шаблон сообщения лога в консоли:
logging.pattern.console=[%-5level] [%X{correlationId}] - %m%n |
Исходные коды примера на GitHub.
Вывод: использование Spring WebFlux при всех своих преимуществах приводит к дополнительным сложностям, которые приходится решать и понимать их причину.
| # | Наименование новости | Тональность | Информативность | Дата публикации |
|---|---|---|---|---|
| 1 | WMX провел крупнейшее обновление «ПроWAF»: версия 5.0 упрощает развертывание и усиливает защиту веб-приложений и API | 5 | 7 | 09-07-2026 |
| 2 | WMX провел крупнейшее обновление «ПроWAF»: версия 5.0 упрощает развертывание и усиливает защиту веб-приложений и API | 5 | 7 | 09-07-2026 |
| 3 | Timeout в HttpClient не работает | 0 | 11.88 | 16-06-2026 |
| 4 | Секреты и преимущества мессенджера Макс | 0 | 8.06 | 16-06-2026 |
| 5 | Водители смогут обжаловать штрафы за нарушение пропускного режима в Москве по видеосвязи | 0 | 0 | 01-06-2020 |
| 6 | Власти Москвы заявили, что данные горожан в системе пропусков защищены законодательством | 0 | 0 | 25-05-2020 |
| 7 | В МВД рассказали, как защитить данные в облачных сервисах | 0 | 0 | 26-02-2025 |
| 8 | Более 17 тыс. москвичей участвовали в краудсорсинг-проекте по МЦД | 0 | 0 | 31-01-2020 |
| 9 | Медведев не стал рассказывать, является ли неформальным переговорщиком с Западом | 0 | 0 | 24-03-2023 |