Log4j 2 API

Thread Context

原文地址

介绍

Log4j引入了MDC(Mapped Diagnostic Context)的概念,关于它的文档与讨论,请参考:Log4j MDC: What and WhyLog4j and the Mapped Diagnostic Context等。另外,Log4j 1.x也同样提供了对NDC(Nested Diagnostic Context)的支持,它的文档与讨论请参考:Log4j NDC等。

Log4j 2延续了MDC和NDC的想法,但将二者融合成了一个独立的Thread Context。Thread Context Map与MDC等价,而Thread Context Stack则等价于NDC。尽管这些经常用于诊断问题之外的目的,但它们在Log4j 2中仍然经常被称为MDC和NDC,因为它们已经被那些缩略语所熟知。

鱼标记

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

更轻量的技术是对从同一客户端交互发起的每个日志请求进行唯一标记。 Neil Harrison在《Patterns for Logging Diagnostic Messages》的“Pattern Languages of Program Design 3”中描述了这种方法(由R.Martin、D.Riehle和F.Buschmann(Addison-Wesley,1997)编辑)。正如鱼可以被标记并追踪其运动,使用公共标记或数据元素集合来标记日志事件,则允许事务或请求的完整流动被跟踪。我们称之为 鱼标记

Log4j提供了两种鱼标记的机制:Thread Context Map和Thread Context Stack。Thread Context Map允许添加任何数量的项并使用键值对来识别。Thread Context Stack允许在栈上推送一个或多个项,然后通过它们在堆栈中的顺序或由数据本身来识别。由于键值对更灵活,当在请求的处理期间或当存在多于一个或两个项时,可以添加数据项时,推荐使用线程上下文映射。

为了使用Thread Context Stack来唯一地标记每个请求,用户将上下文信息推送到栈中。

ThreadContext.push(UUID.randomUUID().toString()); // 添加鱼标记

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

CloseableThreadContext

Thread Context Stack的替代品是Thread Context Map。在这种情况下,与正在处理的请求相关联的属性在开始时添加,并在结束时删除,如下所示:

// 仅将这个try块添加到Thread Context Stack中
try (final CloseableThreadContext.Instance ctc = CloseableThreadContext.push(UUID.randomUUID().toString())) {

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

或者,临时地将一些东西放入到Map中:

// 仅将这个try块添加到Thread Context Map中
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");
            }
        });
    }
}

实现细节

Stack和Map是每个线程管理的,默认情况下基于ThreadLocal。通过将系统属性isThreadContextMapInheritable设置为"true",可以将Map配置为使用InheritableThreadLocal。当以这种方式配置时,Map的内容将被传递给子线程。然而,正如在Executors)类中讨论的,以及在使用线程池的其他情况下,ThreadContext可能不总是自动传递给工作线程。在这些情况下,汇集机制应提供一种方法。getContext()和cloneStack()方法可以分别用于获取Map和Stack的副本。

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

写日志时包含ThreadContext

PatternLayout提供一种机制来打印ThreadContext Map和Stack的正文。

  • 仅使用%X来包含Map中的完整正文。
  • 使用%X{key}来包含声明的键。
  • 使用%x来包含Stack中的完整正文。

为非thread-local的上下文数据自定义注入器

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

一些应用程序具有将工作委派给其他线程的线程模型,在这种模型中,放入一个线程中的thread-local Map的标签属性,在其他线程中是不可见的,并且在其他线程中完成的日志不会显示这些属性。

Log4j 2.7添加了一种灵活的机制,用于使用来自除ThreadContext之外的其他来源的上下文数据对记录语句进行标记。有关详细信息,请参阅扩展Log4j的手册页。

results matching ""

    No results matching ""