在我们的软件中,我们广泛使用来跟踪web请求的会话ID和用户名等内容。当在原始线程中运行时,这可以很好地工作。
然而,有很多事情需要在后台处理。为此,我们使用java.concurrent.ThreadPoolExecutor
和java.util.Timer
类以及一些自滚动异步执行服务。所有这些服务都管理自己的线程池。
这是Logback's manual关于在这样的环境中使用MDC所说的话:
工作线程不能始终从发起线程继承映射的诊断上下文的副本。当使用java.util.concurrent.Executors进行线程管理时就是这种情况。例如,newCachedThreadPool方法创建一个ThreadPoolExecutor,与其他线程池代码一样,它具有复杂的线程创建逻辑。
在这种情况下,建议在将任务提交给executor之前在原始(主)线程上调用MDC.getCopyOfContextMap()。当任务运行时,作为它的第一个动作,它应该调用MDC.setContextMapValues()来将存储的原始MDC值副本与新的Executor托管线程相关联。
这很好,但是很容易忘记添加这些调用,并且没有简单的方法来识别问题,直到为时已晚。使用Log4j的唯一标志是日志中缺少MDC信息,而使用Logback则获得陈旧的MDC信息(因为tread池中的线程继承了在其上运行的第一个任务的MDC )。这两个都是生产系统中的严重问题。
我认为我们的情况没有任何特殊之处,但我在网上找不到太多关于这个问题的信息。显然,这不是很多人都会遇到的事情,所以一定有办法避免它。我们在这里做错了什么?
发布于 2013-10-12 10:02:20
是的,这也是我遇到的一个常见问题。有一些变通方法(如手动设置,如上所述),但理想情况下,您需要的解决方案是
Callable
子类化时到处使用MyCallable
,或者类似的丑陋)。这里有一个我使用的解决方案,它可以满足这三个需求。代码应该是自解释的。
(顺便说一句,如果您使用的是Guava的ListanableFuture
,那么可以创建这个执行器并将其提供给Guava的MoreExecutors.listeningDecorator()
。)
import org.slf4j.MDC;
import java.util.Map;
import java.util.concurrent.*;
/**
* A SLF4J MDC-compatible {@link ThreadPoolExecutor}.
* <p/>
* In general, MDC is used to store diagnostic information (e.g. a user's session id) in per-thread variables, to facilitate
* logging. However, although MDC data is passed to thread children, this doesn't work when threads are reused in a
* thread pool. This is a drop-in replacement for {@link ThreadPoolExecutor} sets MDC data before each task appropriately.
* <p/>
* Created by jlevy.
* Date: 6/14/13
*/
public class MdcThreadPoolExecutor extends ThreadPoolExecutor {
final private boolean useFixedContext;
final private Map<String, Object> fixedContext;
/**
* Pool where task threads take MDC from the submitting thread.
*/
public static MdcThreadPoolExecutor newWithInheritedMdc(int corePoolSize, int maximumPoolSize, long keepAliveTime,
TimeUnit unit, BlockingQueue<Runnable> workQueue) {
return new MdcThreadPoolExecutor(null, corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue);
}
/**
* Pool where task threads take fixed MDC from the thread that creates the pool.
*/
@SuppressWarnings("unchecked")
public static MdcThreadPoolExecutor newWithCurrentMdc(int corePoolSize, int maximumPoolSize, long keepAliveTime,
TimeUnit unit, BlockingQueue<Runnable> workQueue) {
return new MdcThreadPoolExecutor(MDC.getCopyOfContextMap(), corePoolSize, maximumPoolSize, keepAliveTime, unit,
workQueue);
}
/**
* Pool where task threads always have a specified, fixed MDC.
*/
public static MdcThreadPoolExecutor newWithFixedMdc(Map<String, Object> fixedContext, int corePoolSize,
int maximumPoolSize, long keepAliveTime, TimeUnit unit,
BlockingQueue<Runnable> workQueue) {
return new MdcThreadPoolExecutor(fixedContext, corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue);
}
private MdcThreadPoolExecutor(Map<String, Object> fixedContext, int corePoolSize, int maximumPoolSize,
long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue) {
super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue);
this.fixedContext = fixedContext;
useFixedContext = (fixedContext != null);
}
@SuppressWarnings("unchecked")
private Map<String, Object> getContextForTask() {
return useFixedContext ? fixedContext : MDC.getCopyOfContextMap();
}
/**
* All executions will have MDC injected. {@code ThreadPoolExecutor}'s submission methods ({@code submit()} etc.)
* all delegate to this.
*/
@Override
public void execute(Runnable command) {
super.execute(wrap(command, getContextForTask()));
}
public static Runnable wrap(final Runnable runnable, final Map<String, Object> context) {
return new Runnable() {
@Override
public void run() {
Map previous = MDC.getCopyOfContextMap();
if (context == null) {
MDC.clear();
} else {
MDC.setContextMap(context);
}
try {
runnable.run();
} finally {
if (previous == null) {
MDC.clear();
} else {
MDC.setContextMap(previous);
}
}
}
};
}
}
发布于 2011-06-21 03:08:16
我们遇到了类似的问题。在启动/停止新线程之前,您可能想要扩展ThreadPoolExecutor并覆盖之前/afterExecute方法,以便进行所需的MDC调用。
发布于 2018-04-12 23:27:03
我想最好的解决方案是:
use ThreadPoolTaskExecutor
TaskDecorator
executor.setTaskDecorator(new LoggingTaskDecorator());
:
TaskDecorator
executor.setTaskDecorator(new LoggingTaskDecorator());
装饰器可能如下所示:
private final class LoggingTaskDecorator implements TaskDecorator {
@Override
public Runnable decorate(Runnable task) {
// web thread
Map<String, String> webThreadContext = MDC.getCopyOfContextMap();
return () -> {
// work thread
try {
// TODO: is this thread safe?
MDC.setContextMap(webThreadContext);
task.run();
} finally {
MDC.clear();
}
};
}
}
https://stackoverflow.com/questions/6073019
复制相似问题