On this page
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上的手册页。