В нашем программном обеспечении мы широко используем MDC для отслеживания таких вещей, как идентификаторы сеансов и имена пользователей для веб-запросов. Это отлично работает при запуске в оригинальной теме. Тем не менее, есть много вещей, которые нужно обрабатывать в фоновом режиме. Для этого мы используем java.concurrent.ThreadPoolExecutor
и java.util.Timer
классы вместе с некоторыми самостоятельно прокаткой услуг исполнения асинхронных. Все эти сервисы управляют своим собственным пулом потоков.
Вот что говорит руководство Logback об использовании MDC в такой среде:
Копия сопоставленного диагностического контекста не всегда может быть унаследована рабочими потоками из инициирующего потока. Это тот случай, когда java.util.concurrent.Executors используется для управления потоками. Например, метод newCachedThreadPool создает ThreadPoolExecutor и, как и другой код пула потоков, имеет сложную логику создания потоков.
В таких случаях рекомендуется, чтобы MDC.getCopyOfContextMap () вызывался в исходном (главном) потоке перед отправкой задачи исполнителю. Когда задача выполняется в качестве первого действия, она должна вызвать MDC.setContextMapValues (), чтобы связать сохраненную копию исходных значений MDC с новым управляемым потоком Executor.
Это было бы хорошо, но добавить эти вызовы очень легко, и нет простого способа распознать проблему, пока не станет слишком поздно. Единственным признаком Log4j является то, что вы пропускаете информацию MDC в журналах, а с помощью Logback вы получаете устаревшую информацию MDC (поскольку поток в пуле протектора наследует свой MDC от первой задачи, которая была на нем запущена). Оба являются серьезными проблемами в производственной системе.
Я не считаю нашу ситуацию особенной в любом случае, но я не мог найти много об этой проблеме в Интернете. Очевидно, что это не то, с чем сталкиваются многие люди, поэтому должен быть способ избежать этого. Что мы здесь делаем не так?
Ответы:
Да, это общая проблема, с которой я столкнулся. Есть несколько обходных путей (например, ручная настройка, как описано), но в идеале вам нужно решение, которое
Callable
сMyCallable
везде, или подобным уродством).Вот решение, которое я использую, которое отвечает этим трем потребностям. Код должен быть понятен.
(Как примечание, этот исполнитель может быть создан и передан Guava
MoreExecutors.listeningDecorator()
, если вы используете GuavaListanableFuture
.)источник
ThreadPoolTaskExecutor
вместо обычной JavaThreadPoolExecutor
, вы можете использоватьMdcTaskDecorator
описанное на moelholm.com/2017/07/24/…Мы столкнулись с подобной проблемой. Возможно, вы захотите расширить ThreadPoolExecutor и переопределить методы before / afterExecute, чтобы сделать необходимые вызовы MDC перед запуском / остановкой новых потоков.
источник
beforeExecute(Thread, Runnable)
иafterExecute(Runnable, Throwable)
могут быть полезны в других случаях, но я не уверен, как это будет работать для настройки MDC. Они оба выполнены под порожденной нитью. Это означает, что вы должны иметь возможность получить обновленную карту из основного потока раньшеbeforeExecute
.ИМХО, лучшим решением является:
ThreadPoolTaskExecutor
TaskDecorator
executor.setTaskDecorator(new LoggingTaskDecorator());
Декоратор может выглядеть так:
источник
Вот как я делаю это с фиксированными пулами потоков и исполнителями:
В поточной части:
источник
Подобно ранее опубликованным решениям,
newTaskFor
методыRunnable
иCallable
могут быть перезаписаны для переноса аргумента (см. Принятое решение) при созданииRunnableFuture
.Примечание: Следовательно,
executorService
«Ssubmit
метод должен быть вызван вместоexecute
метода.Для
ScheduledThreadPoolExecutor
,decorateTask
методы будут перезаписаны вместо этого.источник
Если вы столкнулись с этой проблемой в среде, связанной
@Async
с Spring Framework, где вы запускаете задачи с помощью аннотации, вы можете украсить задачи с помощью подхода TaskDecorator . Пример того, как это сделать, представлен здесь: https://moelholm.com/blog/2017/07/24/spring-43-using-a-taskdecorator-to-copy-mdc-data-to-async-threadsЯ столкнулся с этой проблемой, и статья выше помогла мне решить ее, поэтому я делюсь ею здесь.
источник
Другой вариант, аналогичный существующим здесь ответам, заключается в реализации
ExecutorService
и разрешении передачи ему делегата. Затем, используя дженерики, он все еще может выставлять фактического делегата в случае, если кто-то хочет получить некоторую статистику (пока никакие другие методы модификации не используются).Код ссылки:
источник
Я смог решить это, используя следующий подход
В основном потоке (Application.java, точка входа моего приложения)
В методе run класса, который вызывается Executer
источник