java - 如何将 MDC 与线程池一起使用?

标签 java logging slf4j logback mdc

在我们的软件中,我们广泛使用 MDC跟踪 Web 请求的 session ID 和用户名等内容。这在原始线程中运行时工作正常。

但是,有很多事情需要在后台处理。为此,我们使用 java.concurrent.ThreadPoolExecutorjava.util.Timer 类以及一些自滚动的 async 执行服务。所有这些服务都管理自己的线程池。

这就是 Logback's manual不得不说在这样的环境下使用MDC:

A copy of the mapped diagnostic context can not always be inherited by worker threads from the initiating thread. This is the case when java.util.concurrent.Executors is used for thread management. For instance, newCachedThreadPool method creates a ThreadPoolExecutor and like other thread pooling code, it has intricate thread creation logic.

In such cases, it is recommended that MDC.getCopyOfContextMap() is invoked on the original (master) thread before submitting a task to the executor. When the task runs, as its first action, it should invoke MDC.setContextMapValues() to associate the stored copy of the original MDC values with the new Executor managed thread.

这很好,但是很容易忘记添加这些调用,并且没有简单的方法可以识别问题,直到为时已晚。 Log4j 的唯一迹象是您在日志中缺少 MDC 信息,而使用 Logback 您会获得陈旧的 MDC 信息(因为线程池中的线程从在其上运行的第一个任务继承其 MDC)。两者都是生产系统中的严重问题。

我认为我们的情况并没有什么特别之处,但我在网上找不到太多关于这个问题的信息。显然,这不是很多人遇到的事情,所以必须有办法避免它。我们在这里做错了什么?

最佳答案

是的,这也是我遇到的一个常见问题。有一些解决方法(如手动设置,如上所述),但理想情况下,您需要一个解决方案,

  • 一致地设置 MDC;
  • 避免MDC不正确但您不知道的默认错误;和
  • 尽量减少对线程池使用方式的更改(例如,将 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);
                    }
                }
            }
        };
    }
}

关于java - 如何将 MDC 与线程池一起使用?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/6073019/

相关文章:

maven - slf4j-log4j12 未由 maven 打包,范围为 "runtime"

java - ApacheKafka Appender 回退

java - IntelliJ IDEA Gradle 与 Maven "Run all tests"通过右键单击 `sources-root` 与 `test-sources-root` 文件夹?

mysql - 获取过去 2 周的每日活跃用户数

java - 服务器返回 Http 响应代码 : 707

c# - 查找 VSTO 日志

java - 在 jre 7 update 40 上启动 web start 时带有日志记录的 NPE

scala - LOGBACK:没有为ch.qos.logback.classic.gaffer.ConfigurationDelegate @提供上下文

java - MongoDB Java - 在嵌套 json 中获取 id

java - Kafka 流和窗口以在时间窗口内保持计数