Log4j 2 API

Thread Context

Introduction

Log4j 引入了“Map 诊断上下文”或 MDC 的概念。它已在许多地方记录和讨论,包括Log4j MDC:什么和为什么Log4j 和 Map 的诊断上下文。此外,Log4j 1.x 还支持嵌套诊断上下文或 NDC。它也已在Log4j NDC等各个地方进行了记录和讨论。 SLF4J/Logback 带有自己的 MDC 实现,在Map 诊断上下文处有很好的记录。

Log4j 2 延续了 MDC 和 NDC 的思想,但将它们合并为一个线程上下文。线程上下文 Map 等效于 MDC,线程上下文堆栈等效于 NDC。尽管它们通常用于诊断问题以外的目的,但由于这些缩写词已经众所周知,因此在 Log4j 2 中仍经常将它们称为 MDC 和 NDC。

Fish Tagging

大多数实际系统必须同时处理多个 Client 端。在这种系统的典型多线程实现中,不同的线程将处理不同的 Client 端。日志记录特别适合跟踪和调试复杂的分布式应用程序。区分一个 Client 端的日志记录输出与另一个 Client 端的日志记录输出的常用方法是为每个 Client 端实例化一个新的单独的日志 Logger。这促进了 Logger 的扩散,并增加了记录的 Management 开销。

一种更轻松的技术是唯一标记从相同 Client 端交互发起的每个日志请求。尼尔·哈里森(Neil Harrison)在 R. Martin,D。Riehle 和 F.Buschmann(Addison-Wesley,1997)编辑的《程序设计 3 的模式语言》一书“记录诊断消息的模式”中描述了这种方法。正如可以标记鱼并对其运动进行跟踪一样,使用通用标签或数据元素集对日志事件进行标记可以允许跟踪 Transaction 或请求的完整流程。我们称之为鱼标签。

Log4j 提供了两种执行鱼标记的机制;线程上下文 Map 和线程上下文堆栈。线程上下文 Map 允许添加任何数量的项,并使用键/值对进行标识。线程上下文堆栈允许将一个或多个项目推入堆栈,然后通过它们在堆栈中的 Sequences 或数据本身进行标识。由于键/值对更加灵活,因此当在请求的处理过程中可能添加数据项或一个或两个以上项时,建议使用“线程上下文 Map”。

为了使用线程上下文堆栈唯一地标记每个请求,用户将上下文信息压入堆栈。

ThreadContext.push(UUID.randomUUID().toString()); // Add the fishtag;

logger.debug("Message 1");
.
.
.
logger.debug("Message 2");
.
.
ThreadContext.pop();

线程上下文堆栈的替代方法是线程上下文 Map。在这种情况下,与正在处理的请求关联的属性在开头添加,并在结尾删除,如下所示:

ThreadContext.put("id", UUID.randomUUID().toString()); // Add the fishtag;
ThreadContext.put("ipAddress", request.getRemoteAddr());
ThreadContext.put("loginId", session.getAttribute("loginId"));
ThreadContext.put("hostName", request.getServerName());
.
logger.debug("Message 1");
.
.
logger.debug("Message 2");
.
.
ThreadContext.clear();

CloseableThreadContext

将项目放置在堆栈或 Map 上时,有必要在适当时再次将其删除。为此,CloseableThreadContext 实现了AutoCloseable interface。这允许将项目推入堆栈或放入 Map 中,并在调用 close()方法时将其删除-或作为 try-with-resources 的一部分自动进行删除。例如,要将某些东西暂时推入堆栈,然后将其删除:

// Add to the ThreadContext stack for this try block only;
try (final CloseableThreadContext.Instance ctc = CloseableThreadContext.push(UUID.randomUUID().toString())) {

    logger.debug("Message 1");
.
.
    logger.debug("Message 2");
.
.
}

或者,暂时在 Map 上放置一些东西:

// Add to the ThreadContext map for this try block only;
try (final CloseableThreadContext.Instance ctc = CloseableThreadContext.put("id", UUID.randomUUID().toString())
                                                                .put("loginId", session.getAttribute("loginId"))) {

    logger.debug("Message 1");
.
.
    logger.debug("Message 2");
.
.
}

如果使用线程池,则可以使用 putAll(final Map<String, String> values)和/或 pushAll(List messages)方法初始化 CloseableThreadContext。

for( final Session session : sessions ) {
    try (final CloseableThreadContext.Instance ctc = CloseableThreadContext.put("loginId", session.getAttribute("loginId"))) {
        logger.debug("Starting background thread for user");
        final Map<String, String> values = ThreadContext.getImmutableContext();
        final List<String> messages = ThreadContext.getImmutableStack().asList();
        executor.submit(new Runnable() {
        public void run() {
            try (final CloseableThreadContext.Instance ctc = CloseableThreadContext.putAll(values).pushAll(messages)) {
                logger.debug("Processing for user started");
                .
                logger.debug("Processing for user completed");
            }
        });
    }
}

Implementation details

堆栈和 Map 是按线程 Management 的,默认情况下基于ThreadLocal。可以将 Map 配置为使用InheritableThreadLocal(请参见Configuration部分)。通过这种方式配置后,Map 的内容将传递给子线程。但是,如在Executors类中讨论的,以及在其他使用线程池的情况下,ThreadContext 可能不会始终自动传递给工作线程。在这些情况下,池化机制应提供这样做的手段。 getContext()和 cloneStack()方法可分别用于获取 Map 和 Stack 的副本。

请注意,ThreadContext类的所有方法都是静态的。

Configuration

  • 将系统属性 disableThreadContextMap 设置为 true 可以禁用线程上下文 Map。

  • 将系统属性 disableThreadContextStack 设置为 true 可以禁用线程上下文堆栈。

  • 将系统属性 disableThreadContext 设置为 true 可以禁用线程上下文 Map 和堆栈。

  • 将系统属性“ log4j2.isThreadContextMapInheritable”设置为“ true”,以使子线程能够继承线程上下文 Map。

编写日志时包括 ThreadContext

PatternLayout提供了用于打印ThreadContextMap 和堆栈内容的机制。

  • 单独使用%X 可以包含 Map 的全部内容。

  • 使用%X{key}包含指定的密钥。

  • 使用%x 包含Stack的全部内容。

用于非线程本地上下文数据的自定义上下文数据注入器

可以使用 ThreadContext 记录日志语句,以便可以通过这些标签链接以某种方式相关的日志条目。限制是,这仅适用于在同一应用程序线程(或配置时的子线程)上完成的日志记录。

某些应用程序具有将工作委托给其他线程的线程模型,在这种模型中,一个线程中放入线程本地 Map 的标记属性在其他线程中不可见,而在其他线程中完成的日志记录将不会显示这些属性。

Log4j 2.7 添加了一种灵活的机制,可以使用来自 ThreadContext 之外的其他来源的上下文数据标记日志记录语句。有关详细信息,请参见extending Log4j上的手册页。