W naszym oprogramowaniu szeroko używamy MDC do śledzenia takich rzeczy, jak identyfikatory sesji i nazwy użytkowników dla żądań internetowych. Działa to dobrze podczas uruchamiania w oryginalnym wątku. Jest jednak wiele rzeczy, które muszą zostać przetworzone w tle. W tym celu używamy klas java.concurrent.ThreadPoolExecutor
i java.util.Timer
wraz z niektórymi usługami wykonywania asynchronicznego samoczynnego przejścia . Wszystkie te usługi zarządzają własną pulą wątków.
Oto, co podręcznik Logback ma do powiedzenia na temat używania MDC w takim środowisku:
Kopia odwzorowanego kontekstu diagnostycznego nie zawsze może być dziedziczona przez wątki robocze z wątku inicjującego. Dzieje się tak, gdy do zarządzania wątkami używany jest java.util.concurrent.Executors. Na przykład metoda newCachedThreadPool tworzy ThreadPoolExecutor i podobnie jak inny kod puli wątków ma skomplikowaną logikę tworzenia wątków.
W takich przypadkach zaleca się wywołanie MDC.getCopyOfContextMap () w oryginalnym (głównym) wątku przed przesłaniem zadania do modułu wykonawczego. Po uruchomieniu zadania, jako pierwsze działanie, powinno wywołać MDC.setContextMapValues () w celu skojarzenia przechowywanej kopii oryginalnych wartości MDC z nowym wątkiem zarządzanym przez moduł Executor.
Byłoby dobrze, ale bardzo łatwo jest zapomnieć o dodaniu tych połączeń i nie ma łatwego sposobu na rozpoznanie problemu, dopóki nie będzie za późno. Jedynym znakiem w Log4j jest to, że w dziennikach brakuje informacji MDC, a dzięki Logback otrzymujesz nieaktualne informacje MDC (ponieważ wątek w puli bieżnika dziedziczy swój MDC z pierwszego zadania, które zostało na nim uruchomione). Oba są poważnymi problemami w systemie produkcyjnym.
Nie uważam naszej sytuacji za wyjątkową, ale nie mogłem znaleźć wiele informacji na temat tego problemu w sieci. Najwyraźniej nie jest to coś, z czym wielu ludzi się boryka, więc musi istnieć sposób, aby tego uniknąć. Co tu robimy źle?
Odpowiedzi:
Tak, to jest częsty problem, z którym też się spotykam. Istnieje kilka obejść (takich jak ręczne ustawienie, zgodnie z opisem), ale najlepiej byłoby znaleźć takie rozwiązanie
Callable
zMyCallable
wszędzie lub podobną brzydotę).Oto rozwiązanie, którego używam, które spełnia te trzy potrzeby. Kod powinien być zrozumiały.
(Na marginesie, ten executor może zostać utworzony i dostarczony do guawy
MoreExecutors.listeningDecorator()
, jeśli używasz guawyListanableFuture
).źródło
ThreadPoolTaskExecutor
zamiast zwykłej JavyThreadPoolExecutor
, możesz użyćMdcTaskDecorator
opisanego na moelholm.com/2017/07/24/ ...Napotkaliśmy podobny problem. Możesz chcieć rozszerzyć ThreadPoolExecutor i zastąpić metody before / afterExecute, aby wykonać wywołania MDC, których potrzebujesz przed uruchomieniem / zatrzymaniem nowych wątków.
źródło
beforeExecute(Thread, Runnable)
iafterExecute(Runnable, Throwable)
mogą być pomocne w innych przypadkach, ale nie jestem pewien, jak to zadziała w przypadku ustawiania MDC. Oba są wykonywane w zwołanym wątku. Oznacza to, że musisz być w stanie zdobyć zaktualizowaną mapę z głównego wątku wcześniejbeforeExecute
.IMHO najlepszym rozwiązaniem jest:
ThreadPoolTaskExecutor
TaskDecorator
executor.setTaskDecorator(new LoggingTaskDecorator());
Dekorator może wyglądać następująco:
źródło
Oto jak robię to ze stałymi pulami wątków i programami wykonawczymi:
W części gwintowania:
źródło
Podobnie jak w przypadku wcześniej opublikowanych rozwiązań,
newTaskFor
metody dlaRunnable
iCallable
można nadpisać w celu zawijania argumentu (patrz zaakceptowane rozwiązanie) podczas tworzenia plikuRunnableFuture
.Uwaga: W związku z tym zamiast metody należy wywołać metodę
executorService
's .submit
execute
Albowiem
ScheduledThreadPoolExecutor
, tedecorateTask
metody będą nadpisywane zamiast.źródło
Jeśli napotkasz ten problem w środowisku związanym z Spring Framework, w którym uruchamiasz zadania za pomocą
@Async
adnotacji, możesz ozdobić zadania za pomocą podejścia TaskDecorator . Przykład, jak to zrobić, znajduje się tutaj: https://moelholm.com/blog/2017/07/24/spring-43-using-a-taskdecorator-to-copy-mdc-data-to-async-threadsZmierzyłem się z tym problemem, a powyższy artykuł pomógł mi go rozwiązać, dlatego udostępniam go tutaj.
źródło
Inną odmianą podobną do istniejących tutaj odpowiedzi jest zaimplementowanie
ExecutorService
i zezwolenie na przekazanie delegatu. Następnie, używając typów ogólnych, nadal może ujawniać faktycznego delegata na wypadek, gdyby ktoś chciał uzyskać jakieś statystyki (o ile nie są używane inne metody modyfikacji).Kod referencyjny:
źródło
Udało mi się to rozwiązać, stosując następujące podejście
W głównym wątku (Application.java, punkt wejścia mojej aplikacji)
W metodzie uruchamiania klasy, która jest wywoływana przez Executer
źródło