In unserer Software verwenden wir MDC ausgiebig, um beispielsweise Sitzungs-IDs und Benutzernamen für Webanfragen zu verfolgen. Dies funktioniert gut, wenn Sie im ursprünglichen Thread ausgeführt werden. Es gibt jedoch viele Dinge, die im Hintergrund verarbeitet werden müssen. Dafür verwenden wir die Klassen java.concurrent.ThreadPoolExecutor
und java.util.Timer
zusammen mit einigen selbst gerollten asynchronen Ausführungsdiensten. Alle diese Dienste verwalten ihren eigenen Thread-Pool.
Dies ist, was das Handbuch von Logback über die Verwendung von MDC in einer solchen Umgebung zu sagen hat:
Eine Kopie des zugeordneten Diagnosekontexts kann nicht immer von Arbeitsthreads vom initiierenden Thread geerbt werden. Dies ist der Fall, wenn java.util.concurrent.Executors für die Thread-Verwaltung verwendet wird. Beispielsweise erstellt die newCachedThreadPool-Methode einen ThreadPoolExecutor und verfügt wie anderer Thread-Pooling-Code über eine komplexe Thread-Erstellungslogik.
In solchen Fällen wird empfohlen, MDC.getCopyOfContextMap () für den ursprünglichen (Master-) Thread aufzurufen, bevor eine Aufgabe an den Executor gesendet wird. Wenn die Aufgabe als erste Aktion ausgeführt wird, sollte sie MDC.setContextMapValues () aufrufen, um die gespeicherte Kopie der ursprünglichen MDC-Werte dem neuen von Executor verwalteten Thread zuzuordnen.
Dies wäre in Ordnung, aber es ist sehr leicht zu vergessen, diese Anrufe hinzuzufügen, und es gibt keine einfache Möglichkeit, das Problem zu erkennen, bis es zu spät ist. Das einzige Anzeichen bei Log4j ist, dass Sie fehlende MDC-Informationen in den Protokollen erhalten, und bei Logback erhalten Sie veraltete MDC-Informationen (da der Thread im Laufflächenpool seinen MDC von der ersten Aufgabe erbt, die darauf ausgeführt wurde). Beides sind schwerwiegende Probleme in einem Produktionssystem.
Ich sehe unsere Situation in keiner Weise besonders, aber ich konnte im Web nicht viel über dieses Problem finden. Anscheinend ist dies nicht etwas, gegen das viele Menschen stoßen, also muss es einen Weg geben, dies zu vermeiden. Was machen wir hier falsch?
Antworten:
Ja, dies ist ein häufiges Problem, auf das ich ebenfalls gestoßen bin. Es gibt einige Problemumgehungen (wie das manuelle Einstellen wie beschrieben), aber im Idealfall möchten Sie eine Lösung, die
Callable
mitMyCallable
überall oder ähnliche Hässlichkeit).Hier ist eine Lösung, die ich verwende und die diese drei Anforderungen erfüllt. Der Code sollte selbsterklärend sein.
(Als Randnotiz kann dieser Executor erstellt und Guavas zugeführt werden
MoreExecutors.listeningDecorator()
, wenn Sie Guavas verwendenListanableFuture
.)quelle
ThreadPoolTaskExecutor
anstelle von einfachem Java verwendenThreadPoolExecutor
, können Sie dasMdcTaskDecorator
unter moelholm.com/2017/07/24/…Wir sind auf ein ähnliches Problem gestoßen. Möglicherweise möchten Sie ThreadPoolExecutor erweitern und before / afterExecute-Methoden überschreiben, um die erforderlichen MDC-Aufrufe auszuführen, bevor Sie neue Threads starten / stoppen.
quelle
beforeExecute(Thread, Runnable)
undafterExecute(Runnable, Throwable)
können in anderen Fällen hilfreich sein, aber ich bin nicht sicher, wie dies zum Festlegen von MDCs funktionieren wird. Sie werden beide unter dem gespawnten Thread ausgeführt. Dies bedeutet, dass Sie zuvor in der Lage sein müssen, die aktualisierte Karte aus dem Hauptthread abzurufenbeforeExecute
.IMHO ist die beste Lösung:
ThreadPoolTaskExecutor
TaskDecorator
executor.setTaskDecorator(new LoggingTaskDecorator());
Der Dekorateur kann so aussehen:
quelle
So mache ich es mit festen Thread-Pools und Executoren:
Im Gewindeteil:
quelle
Ähnlich wie bei den zuvor veröffentlichten Lösungen können die
newTaskFor
Methoden fürRunnable
undCallable
überschrieben werden, um das Argument (siehe akzeptierte Lösung) beim Erstellen der zu umbrechenRunnableFuture
.Hinweis: Folglich muss die Methode von
executorService
'submit
anstelle derexecute
Methode aufgerufen werden.Für die würden
ScheduledThreadPoolExecutor
diedecorateTask
Methoden stattdessen überschrieben.quelle
Wenn Sie auf dieses Problem in einer Spring Framework-bezogenen Umgebung stoßen, in der Sie Aufgaben mithilfe von
@Async
Anmerkungen ausführen, können Sie die Aufgaben mithilfe des TaskDecorator- Ansatzes dekorieren . Ein Beispiel dafür finden Sie hier: https://moelholm.com/blog/2017/07/24/spring-43-using-a-taskdecorator-to-copy-mdc-data-to-async-threadsIch war mit diesem Problem konfrontiert und der obige Artikel hat mir geholfen, es anzugehen. Deshalb teile ich es hier.
quelle
Eine andere Variante, die den hier vorhandenen Antworten ähnelt, besteht darin
ExecutorService
, einen Delegaten zu implementieren und an ihn weiterzuleiten. Bei Verwendung von Generika kann der tatsächliche Delegat dennoch angezeigt werden, falls Statistiken abgerufen werden sollen (solange keine anderen Änderungsmethoden verwendet werden).Referenzcode:
quelle
Ich konnte dies mit folgendem Ansatz lösen
Im Hauptthread (Application.java, der Einstiegspunkt meiner Anwendung)
In der Ausführungsmethode der Klasse, die von Executer aufgerufen wird
quelle