Dans notre logiciel, nous utilisons largement MDC pour suivre des éléments tels que les identifiants de session et les noms d'utilisateur pour les demandes Web. Cela fonctionne bien lors de l'exécution dans le thread d'origine. Cependant, il y a beaucoup de choses qui doivent être traitées en arrière-plan. Pour cela , nous utilisons java.concurrent.ThreadPoolExecutor
et les java.util.Timer
classes ainsi que quelques async roulées services d'exécution. Tous ces services gèrent leur propre pool de threads.
Voici ce que le manuel de Logback a à dire sur l'utilisation de MDC dans un tel environnement:
Une copie du contexte de diagnostic mappé ne peut pas toujours être héritée par les threads de travail du thread initiateur. C'est le cas lorsque java.util.concurrent.Executors est utilisé pour la gestion des threads. Par exemple, la méthode newCachedThreadPool crée un ThreadPoolExecutor et, comme tout autre code de regroupement de threads, elle a une logique de création de thread complexe.
Dans de tels cas, il est recommandé que MDC.getCopyOfContextMap () soit appelé sur le thread d'origine (maître) avant de soumettre une tâche à l'exécuteur. Lorsque la tâche s'exécute, en tant que première action, elle doit appeler MDC.setContextMapValues () pour associer la copie stockée des valeurs MDC d'origine au nouveau thread géré par Executor.
Ce serait bien, mais il est très facile d'oublier l'ajout de ces appels, et il n'y a pas de moyen facile de reconnaître le problème avant qu'il ne soit trop tard. Le seul signe avec Log4j est que vous obtenez des informations MDC manquantes dans les journaux, et avec Logback, vous obtenez des informations MDC périmées (puisque le thread dans le pool de bandes de roulement hérite de son MDC de la première tâche qui a été exécutée dessus). Les deux sont de graves problèmes dans un système de production.
Je ne vois aucunement notre situation particulière, mais je n'ai pas pu trouver grand-chose sur ce problème sur le Web. Apparemment, ce n'est pas quelque chose contre quoi beaucoup de gens se heurtent, il doit donc y avoir un moyen de l'éviter. Que faisons-nous de mal ici?
Réponses:
Oui, c'est également un problème courant que j'ai rencontré. Il existe quelques solutions de contournement (comme le définir manuellement, comme décrit), mais idéalement, vous voulez une solution qui
Callable
avecMyCallable
partout, ou laideur similaire).Voici une solution que j'utilise qui répond à ces trois besoins. Le code doit être explicite.
(En remarque, cet exécuteur peut être créé et envoyé à Guava
MoreExecutors.listeningDecorator()
, si vous utilisez GuavaListanableFuture
.)la source
ThreadPoolTaskExecutor
lieu de Java ordinaireThreadPoolExecutor
, vous pouvez utiliser leMdcTaskDecorator
décrit sur moelholm.com/2017/07/24/…Nous avons rencontré un problème similaire. Vous souhaiterez peut-être étendre ThreadPoolExecutor et remplacer les méthodes before / afterExecute pour effectuer les appels MDC dont vous avez besoin avant de démarrer / arrêter de nouveaux threads.
la source
beforeExecute(Thread, Runnable)
etafterExecute(Runnable, Throwable)
peuvent être utiles dans d'autres cas, mais je ne sais pas comment cela fonctionnera pour la configuration des MDC. Ils sont tous deux exécutés sous le thread généré. Cela signifie que vous devez être en mesure de récupérer la carte mise à jour à partir du thread principal avantbeforeExecute
.À mon humble avis, la meilleure solution est de:
ThreadPoolTaskExecutor
TaskDecorator
executor.setTaskDecorator(new LoggingTaskDecorator());
Le décorateur peut ressembler à ceci:
la source
Voici comment je le fais avec des pools de threads fixes et des exécuteurs:
Dans la partie filetage:
la source
Semblable aux solutions précédemment publiées, les
newTaskFor
méthodes pourRunnable
etCallable
peuvent être écrasées afin d'encapsuler l'argument (voir solution acceptée) lors de la création duRunnableFuture
.Remarque: Par conséquent, la méthode
executorService
ssubmit
doit être appelée à la place de laexecute
méthode.Pour le
ScheduledThreadPoolExecutor
, lesdecorateTask
méthodes seraient écrasées à la place.la source
Si vous rencontrez ce problème dans un environnement lié au framework Spring où vous exécutez des tâches en utilisant l'
@Async
annotation, vous pouvez décorer les tâches à l'aide de l' approche TaskDecorator . Un exemple de procédure est fourni ici: https://moelholm.com/blog/2017/07/24/spring-43-using-a-taskdecorator-to-copy-mdc-data-to-async-threadsJ'ai rencontré ce problème et l'article ci-dessus m'a aidé à le résoudre, c'est pourquoi je le partage ici.
la source
Une autre variante similaire aux réponses existantes ici consiste à implémenter
ExecutorService
et à autoriser un délégué à lui être transmis. Ensuite, en utilisant des génériques, il peut toujours exposer le délégué réel au cas où l'on voudrait obtenir des statistiques (tant qu'aucune autre méthode de modification n'est utilisée).Code de référence:
la source
J'ai pu résoudre ce problème en utilisant l'approche suivante
Dans le thread principal (Application.java, le point d'entrée de mon application)
Dans la méthode run de la classe qui est appelée par Executer
la source