Em nosso software, usamos extensivamente o MDC para rastrear itens como IDs de sessão e nomes de usuário para solicitações da Web. Isso funciona bem durante a execução no encadeamento original. No entanto, há muitas coisas que precisam ser processadas em segundo plano. Para isso, usamos as classes java.concurrent.ThreadPoolExecutor
e java.util.Timer
juntamente com alguns serviços de execução assíncrona autolaminados. Todos esses serviços gerenciam seu próprio pool de encadeamentos.
Isto é o que o manual do Logback tem a dizer sobre o uso do MDC em um ambiente como esse:
Uma cópia do contexto de diagnóstico mapeado nem sempre pode ser herdada pelos threads de trabalho do thread inicial. Este é o caso em que java.util.concurrent.Executors é usado para gerenciamento de encadeamentos. Por exemplo, o método newCachedThreadPool cria um ThreadPoolExecutor e, como outro código de pool de encadeamentos, possui uma lógica intrincada de criação de encadeamentos.
Nesses casos, é recomendável que MDC.getCopyOfContextMap () seja chamado no encadeamento original (mestre) antes de enviar uma tarefa ao executor. Quando a tarefa é executada, como primeira ação, ela deve chamar MDC.setContextMapValues () para associar a cópia armazenada dos valores originais do MDC ao novo encadeamento gerenciado pelo Executor.
Isso seria bom, mas é muito fácil esquecer de adicionar essas chamadas e não há uma maneira fácil de reconhecer o problema até que seja tarde demais. O único sinal com o Log4j é que você obtém informações ausentes do MDC nos logs e, com o Logback, obtém informações obsoletas do MDC (uma vez que o encadeamento no pool do piso herda o MDC da primeira tarefa executada nele). Ambos são problemas sérios em um sistema de produção.
Não vejo nossa situação de forma alguma, mas não pude encontrar muito sobre esse problema na web. Aparentemente, isso não é algo contra o qual muitas pessoas se deparam, então deve haver uma maneira de evitá-lo. O que estamos fazendo de errado aqui?
Respostas:
Sim, esse também é um problema comum. Existem algumas soluções alternativas (como configurá-lo manualmente, conforme descrito), mas, idealmente, você deseja uma solução que
Callable
emMyCallable
todos os lugares ou feiúra semelhante).Aqui está uma solução que eu uso que atende a essas três necessidades. O código deve ser auto-explicativo.
(Como uma observação lateral, esse executor pode ser criado e alimentado no Guava's
MoreExecutors.listeningDecorator()
, se você usar o Guava'sListanableFuture
.)fonte
ThreadPoolTaskExecutor
vez de Java simplesThreadPoolExecutor
, poderá usar oMdcTaskDecorator
descrito em moelholm.com/2017/07/24/…Nós encontramos um problema semelhante. Convém estender o ThreadPoolExecutor e substituir os métodos before / afterExecute para fazer as chamadas MDC necessárias antes de iniciar / parar novos threads.
fonte
beforeExecute(Thread, Runnable)
eafterExecute(Runnable, Throwable)
pode ser útil em outros casos, mas eu não sei como isso vai funcionar para a criação MDC. Ambos são executados sob o segmento gerado. Isso significa que você precisa se apossar do mapa atualizado a partir do encadeamento principal antesbeforeExecute
.IMHO a melhor solução é:
ThreadPoolTaskExecutor
TaskDecorator
executor.setTaskDecorator(new LoggingTaskDecorator());
O decorador pode ficar assim:
fonte
É assim que eu faço com conjuntos de threads e executores fixos:
Na parte de rosqueamento:
fonte
Semelhante às soluções postadas anteriormente, os
newTaskFor
métodosRunnable
eCallable
podem ser substituídos para envolver o argumento (consulte a solução aceita) ao criar o arquivoRunnableFuture
.Nota: Por conseguinte, o
executorService
'ssubmit
método deve ser chamado em vez doexecute
método.Para o
ScheduledThreadPoolExecutor
, osdecorateTask
métodos seriam substituídos.fonte
Caso você enfrente esse problema em um ambiente relacionado à estrutura de primavera em que executa tarefas usando a
@Async
anotação, é possível decorar as tarefas usando a abordagem TaskDecorator . Uma amostra de como fazer isso é fornecida aqui: https://moelholm.com/blog/2017/07/24/spring-43-using-a-taskdecorator-to-copy-mdc-data-to-async-threadsEu enfrentei esse problema e o artigo acima me ajudou a resolvê-lo, por isso estou compartilhando aqui.
fonte
Outra variação semelhante às respostas existentes aqui é implementar
ExecutorService
e permitir que um delegado seja passado a ele. Em seguida, usando genéricos, ele ainda pode expor o delegado real, caso deseje obter algumas estatísticas (desde que nenhum outro método de modificação seja usado).Código de referência:
fonte
Consegui resolver isso usando a seguinte abordagem
No thread principal (Application.java, o ponto de entrada do meu aplicativo)
No método de execução da classe que é chamada pelo Executer
fonte