在我们的软件中,我们广泛使用MDC来跟踪会话ID和Web请求的用户名等内容。这在原始线程中运行时工作正常。但是,有很多事情需要在后台处理。为此,我们使用java.concurrent.ThreadPoolExecutor
和java.util.Timer
类以及一些自动异步执行服务。所有这些服务都管理自己的线程池。
这就是Logback's manual在这样的环境中使用MDC所要说的:
映射诊断上下文的副本不能始终由发起线程的工作线程继承。当java.util.concurrent.Executors用于线程管理时就是这种情况。例如,newCachedThreadPool方法创建一个ThreadPoolExecutor,就像其他线程池代码一样,它有复杂的线程创建逻辑。
在这种情况下,建议在将任务提交给执行程序之前,在原始(主)线程上调用MDC.getCopyOfContextMap()。当任务运行时,作为第一个操作,它应该调用MDC.setContextMapValues()以将原始MDC值的存储副本与新的Executor托管线程相关联。
这很好,但是很容易忘记添加这些调用,并且没有简单的方法来识别问题,直到为时已晚。 Log4j的唯一标志是您在日志中丢失了MDC信息,而使用Logback,您会收到过时的MDC信息(因为胎面池中的线程从其上运行的第一个任务继承其MDC)。两者都是生产系统中的严重问题。
我不认为我们的情况有任何特殊之处,但我在网上找不到这个问题。显然,这不是很多人碰到的东西,所以必须有办法避免它。我们在这里做错了什么?
答案 0 :(得分:68)
是的,这是我遇到的一个常见问题。有一些解决方法(如手动设置,如上所述),但理想情况下,您需要一个解决方案
Callable
与MyCallable
进行子类化,或者类似的丑陋)。这是我使用的解决方案,满足这三个需求。代码应该是不言自明的。
(作为旁注,可以创建此执行程序并将其提供给Guava的MoreExecutors.listeningDecorator()
,如果
你使用Guava的ListanableFuture
。)
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);
}
}
}
};
}
}
答案 1 :(得分:25)
我们遇到了类似的问题。您可能希望扩展ThreadPoolExecutor并覆盖before / afterExecute方法,以便在启动/停止新线程之前进行所需的MDC调用。
答案 2 :(得分:10)
恕我直言,最好的解决方案是:
int const i
ThreadPoolTaskExecutor
TaskDecorator
装饰者看起来像这样:
executor.setTaskDecorator(new LoggingTaskDecorator());
答案 3 :(得分:5)
如果您在与Spring框架相关的环境中遇到此问题,在该环境中使用@Async
批注来运行任务,则可以使用TaskDecorator方法来修饰任务。此处提供了有关操作方法的示例:https://moelholm.com/blog/2017/07/24/spring-43-using-a-taskdecorator-to-copy-mdc-data-to-async-threads
我遇到了这个问题,上面的文章帮助我解决了这个问题,所以这就是我在这里分享它的原因。
答案 4 :(得分:2)
与之前发布的解决方案类似,Runnable
和Callable
的{{3}}方法可以被覆盖,以便在创建{{1}时包装参数(请参阅接受的解决方案) }。
注意:因此,必须调用RunnableFuture
的{{1}}方法而不是executorService
方法。
对于submit
,newTaskFor
方法将被覆盖。
答案 5 :(得分:2)
这是我使用固定线程池和执行程序的方法:
ExecutorService executor = Executors.newFixedThreadPool(4);
Map<String, String> mdcContextMap = MDC.getCopyOfContextMap();
在线程部分:
executor.submit(() -> {
MDC.setContextMap(mdcContextMap);
// my stuff
});
答案 6 :(得分:0)
我能够使用以下方法解决这个问题
在主线程(Application.java,我的应用程序的入口点)
static public Map<String, String> mdcContextMap = MDC.getCopyOfContextMap();
在Executer调用的类的run方法中
MDC.setContextMap(Application.mdcContextMap);
答案 7 :(得分:0)
与现有答案类似的另一个变体是实现ExecutorService
并允许将委托传递给它。然后使用泛型,它仍然可以公开实际的委托,以防有人想要获取某些统计信息(只要不使用其他修改方法)。
参考代码:
public class MDCExecutorService<D extends ExecutorService> implements ExecutorService {
private final D delegate;
public MDCExecutorService(D delegate) {
this.delegate = delegate;
}
@Override
public void shutdown() {
delegate.shutdown();
}
@Override
public List<Runnable> shutdownNow() {
return delegate.shutdownNow();
}
@Override
public boolean isShutdown() {
return delegate.isShutdown();
}
@Override
public boolean isTerminated() {
return delegate.isTerminated();
}
@Override
public boolean awaitTermination(long timeout, TimeUnit unit) throws InterruptedException {
return delegate.awaitTermination(timeout, unit);
}
@Override
public <T> Future<T> submit(Callable<T> task) {
return delegate.submit(wrap(task));
}
@Override
public <T> Future<T> submit(Runnable task, T result) {
return delegate.submit(wrap(task), result);
}
@Override
public Future<?> submit(Runnable task) {
return delegate.submit(wrap(task));
}
@Override
public <T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks) throws InterruptedException {
return delegate.invokeAll(wrapCollection(tasks));
}
@Override
public <T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks, long timeout, TimeUnit unit) throws InterruptedException {
return delegate.invokeAll(wrapCollection(tasks), timeout, unit);
}
@Override
public <T> T invokeAny(Collection<? extends Callable<T>> tasks) throws InterruptedException, ExecutionException {
return delegate.invokeAny(wrapCollection(tasks));
}
@Override
public <T> T invokeAny(Collection<? extends Callable<T>> tasks, long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException {
return delegate.invokeAny(wrapCollection(tasks), timeout, unit);
}
@Override
public void execute(Runnable command) {
delegate.execute(wrap(command));
}
public D getDelegate() {
return delegate;
}
/* Copied from https://github.com/project-ncl/pnc/blob/master/common/src/main/java/org/jboss/pnc/common
/concurrent/MDCWrappers.java */
private static Runnable wrap(final Runnable runnable) {
final Map<String, String> context = MDC.getCopyOfContextMap();
return () -> {
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);
}
}
};
}
private static <T> Callable<T> wrap(final Callable<T> callable) {
final Map<String, String> context = MDC.getCopyOfContextMap();
return () -> {
Map previous = MDC.getCopyOfContextMap();
if (context == null) {
MDC.clear();
} else {
MDC.setContextMap(context);
}
try {
return callable.call();
} finally {
if (previous == null) {
MDC.clear();
} else {
MDC.setContextMap(previous);
}
}
};
}
private static <T> Consumer<T> wrap(final Consumer<T> consumer) {
final Map<String, String> context = MDC.getCopyOfContextMap();
return (t) -> {
Map previous = MDC.getCopyOfContextMap();
if (context == null) {
MDC.clear();
} else {
MDC.setContextMap(context);
}
try {
consumer.accept(t);
} finally {
if (previous == null) {
MDC.clear();
} else {
MDC.setContextMap(previous);
}
}
};
}
private static <T> Collection<Callable<T>> wrapCollection(Collection<? extends Callable<T>> tasks) {
Collection<Callable<T>> wrapped = new ArrayList<>();
for (Callable<T> task : tasks) {
wrapped.add(wrap(task));
}
return wrapped;
}
}