Log4j 2 API
线程上下文
介绍
Log4j 引入了映射诊断上下文 (MDC) 的概念。它已在许多地方进行过记录和讨论,包括 Log4j MDC:是什么以及为什么 和 Log4j 和映射诊断上下文。此外,Log4j 1.x 支持嵌套诊断上下文 (NDC)。它也在各种地方进行了记录和讨论,例如 Log4j NDC。SLF4J/Logback 随之推出了自己的 MDC 实现,在 映射诊断上下文 中有很好的记录。
Log4j 2 延续了 MDC 和 NDC 的理念,但将它们合并到一个线程上下文中。线程上下文映射相当于 MDC,线程上下文堆栈相当于 NDC。尽管这些通常用于诊断问题以外的目的,但它们在 Log4j 2 中仍然经常被称为 MDC 和 NDC,因为它们已经通过这些缩写为人们所熟知。
鱼类标记
大多数现实世界系统必须同时处理多个客户端。在这样的系统的典型多线程实现中,不同的线程将处理不同的客户端。日志记录特别适合跟踪和调试复杂的分布式应用程序。区分来自同一客户端的日志输出的常用方法是为每个客户端实例化一个新的单独记录器。这会导致记录器激增,并增加日志记录的管理开销。
一种更轻量级的技术是对来自同一客户端交互的每个日志请求进行唯一标记。Neil Harrison 在“诊断消息模式”一书中描述了这种方法,该书收录于 R. Martin、D. Riehle 和 F. Buschmann 编辑的《程序设计模式语言 3》(Addison-Wesley,1997 年)。就像可以标记鱼并跟踪其移动一样,用通用标记或一组数据元素标记日志事件可以跟踪事务或请求的完整流程。我们称之为“鱼类标记”。
Log4j 提供了两种执行鱼类标记的机制:线程上下文映射和线程上下文堆栈。线程上下文映射允许添加任意数量的项,并使用键值对进行标识。线程上下文堆栈允许将一个或多个项压入堆栈,然后通过它们在堆栈中的顺序或数据本身进行标识。由于键值对更加灵活,因此在处理请求期间可能添加数据项或存在多个数据项时,建议使用线程上下文映射。
要使用线程上下文堆栈对每个请求进行唯一标记,用户会将上下文信息压入堆栈。
ThreadContext.push(UUID.randomUUID().toString()); // Add the fishtag; logger.debug("Message 1"); . . . logger.debug("Message 2"); . . ThreadContext.pop();
线程上下文堆栈的替代方案是线程上下文映射。在这种情况下,与正在处理的请求关联的属性将在开始时添加,并在结束时删除,如下所示
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
将项放入堆栈或映射时,需要在适当的时候将其删除。为了帮助实现这一点,CloseableThreadContext
实现了 AutoCloseable 接口。这允许将项压入堆栈或放入映射,并在调用 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"); . . }
或者,要暂时将某些内容放入映射
// 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<String> 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"); } }); } }
实现细节
堆栈和映射按线程进行管理,默认情况下基于 ThreadLocal。可以将映射配置为使用 InheritableThreadLocal(参见 配置 部分)。以这种方式配置时,映射的内容将传递给子线程。但是,正如在 Executors 类中以及在使用线程池的其他情况下所讨论的那样,线程上下文可能并不总是自动传递给工作线程。在这些情况下,池机制应提供一种执行此操作的方法。getContext() 和 cloneStack() 方法可用于分别获取映射和堆栈的副本。
请注意,ThreadContext 类中的所有方法都是静态的。
配置
- 将系统属性
disableThreadContextMap
设置为true
以禁用线程上下文映射。 - 将系统属性
disableThreadContextStack
设置为true
以禁用线程上下文堆栈。 - 将系统属性
disableThreadContext
设置为true
以禁用线程上下文映射和堆栈。 - 将系统属性
log4j2.isThreadContextMapInheritable
设置为true
以使子线程继承线程上下文映射。
写入日志时包含线程上下文
PatternLayout 提供了打印 ThreadContext 映射和堆栈内容的机制。
- 单独使用
%X
以包含映射的完整内容。 - 使用
%X{key}
以包含指定的键。 - 使用
%x
以包含 堆栈 的完整内容。
用于非线程本地上下文数据的自定义上下文数据注入器
使用线程上下文,可以标记日志语句,以便以某种方式相关的日志条目可以通过这些标记进行链接。限制是,这仅适用于在同一应用程序线程(或在配置时为子线程)上完成的日志记录。
某些应用程序具有将工作委托给其他线程的线程模型,在这些模型中,在一个线程中放入线程本地映射的标记属性在其他线程中不可见,并且在其他线程中完成的日志记录将不会显示这些属性。
Log4j 2.7 添加了一种灵活的机制,用于使用来自线程上下文以外的其他来源的上下文数据标记日志语句。有关详细信息,请参见有关 扩展 Log4j 的手册页。