En nuestro software, utilizamos ampliamente MDC para rastrear cosas como ID de sesión y nombres de usuario para solicitudes web. Esto funciona bien mientras se ejecuta en el hilo original. Sin embargo, hay muchas cosas que deben procesarse en segundo plano. Para eso utilizamos las clases java.concurrent.ThreadPoolExecutor
y java.util.Timer
junto con algunos servicios de ejecución asíncrona auto-enrollados. Todos estos servicios administran su propio grupo de subprocesos.
Esto es lo que dice el manual de Logback sobre el uso de MDC en dicho entorno:
Los subprocesos de trabajo no siempre pueden heredar una copia del contexto de diagnóstico asignado del subproceso inicial. Este es el caso cuando se usa java.util.concurrent.Executors para la gestión de subprocesos. Por ejemplo, el método newCachedThreadPool crea un ThreadPoolExecutor y, al igual que otros códigos de agrupación de subprocesos, tiene una intrincada lógica de creación de subprocesos.
En tales casos, se recomienda invocar MDC.getCopyOfContextMap () en el subproceso original (maestro) antes de enviar una tarea al ejecutor. Cuando se ejecuta la tarea, como su primera acción, debe invocar MDC.setContextMapValues () para asociar la copia almacenada de los valores originales de MDC con el nuevo subproceso administrado por el Ejecutor.
Esto estaría bien, pero es muy fácil olvidar agregar esas llamadas, y no hay una manera fácil de reconocer el problema hasta que sea demasiado tarde. El único signo con Log4j es que te falta información de MDC en los registros, y con Logback obtienes información de MDC obsoleta (ya que el subproceso en la banda de rodadura hereda su MDC de la primera tarea que se ejecutó). Ambos son problemas serios en un sistema de producción.
No veo nuestra situación especial de ninguna manera, sin embargo, no pude encontrar mucho sobre este problema en la web. Aparentemente, esto no es algo con lo que muchas personas se topan, por lo que debe haber una forma de evitarlo. ¿Qué estamos haciendo mal aquí?
Respuestas:
Sí, este es un problema común con el que también me he encontrado. Hay algunas soluciones alternativas (como configurarlo manualmente, como se describe), pero idealmente desea una solución que
Callable
conMyCallable
cualquier lugar o fealdad similar)Aquí hay una solución que uso que satisface estas tres necesidades. El código debe explicarse por sí mismo.
(Como nota al margen, este ejecutor se puede crear y alimentar a Guava's
MoreExecutors.listeningDecorator()
, si usa Guava'sListanableFuture
).fuente
ThreadPoolTaskExecutor
lugar de Java simpleThreadPoolExecutor
, puede usar loMdcTaskDecorator
descrito en moelholm.com/2017/07/24/…Nos hemos encontrado con un problema similar. Es posible que desee extender ThreadPoolExecutor y anular los métodos before / afterExecute para realizar las llamadas MDC que necesita antes de comenzar / detener nuevos hilos.
fuente
beforeExecute(Thread, Runnable)
yafterExecute(Runnable, Throwable)
pueden ser útiles en otros casos, pero no estoy seguro de cómo funcionará para establecer MDC. Ambos se ejecutan bajo el hilo generado. Esto significa que debe poder obtener el mapa actualizado desde el hilo principal antesbeforeExecute
.En mi humilde opinión la mejor solución es:
ThreadPoolTaskExecutor
TaskDecorator
executor.setTaskDecorator(new LoggingTaskDecorator());
El decorador puede verse así:
fuente
Así es como lo hago con grupos de subprocesos fijos y ejecutores:
En la parte de roscado:
fuente
Al igual que las soluciones publicadas anteriormente, los
newTaskFor
métodos paraRunnable
yCallable
se pueden sobrescribir para ajustar el argumento (ver solución aceptada) al crear elRunnableFuture
.Nota: Por consiguiente, se debe llamar al método
executorService
's ensubmit
lugar delexecute
método.Para el
ScheduledThreadPoolExecutor
, losdecorateTask
métodos se sobrescribirán en su lugar.fuente
En caso de que enfrente este problema en un entorno relacionado con el marco de Spring donde ejecuta tareas mediante la
@Async
anotación, puede decorar las tareas mediante el enfoque TaskDecorator . Aquí se proporciona una muestra de cómo hacerlo: https://moelholm.com/blog/2017/07/24/spring-43-using-a-taskdecorator-to-copy-mdc-data-to-async-threadsMe enfrenté a este problema y el artículo anterior me ayudó a abordarlo, por eso lo comparto aquí.
fuente
Otra variación similar a las respuestas existentes aquí es implementar
ExecutorService
y permitir que se le pase un delegado. Luego, utilizando genéricos, aún puede exponer al delegado real en caso de que uno quiera obtener algunas estadísticas (siempre y cuando no se utilicen otros métodos de modificación).Código de referencia:
fuente
Pude resolver esto usando el siguiente enfoque
En el hilo principal (Application.java, el punto de entrada de mi aplicación)
En el método de ejecución de la clase que Executer llama
fuente