Java Logging with Nested Diagnostic Context (NDC) – 使用嵌套诊断上下文(NDC)的Java日志系统

最后修改: 2016年 12月 17日

中文/混合/英文(键盘快捷键:t)

1. Overview

1.概述

Nested Diagnostic Context (NDC) is a mechanism to help distinguish interleaved log messages from different sources. NDC does this by providing the ability to add distinctive contextual information to each log entry.

嵌套诊断上下文(NDC)是一种机制,有助于区分来自不同来源的交错的日志信息。NDC通过提供为每个日志条目添加独特的上下文信息的能力来做到这一点。

In this article, we will explore the use of NDC and its usage/support in various Java logging frameworks.

在这篇文章中,我们将探讨NDC的使用以及它在各种Java日志框架中的用法/支持。

2. Diagnostic Contexts

2.诊断背景

In a typical multi-threaded application like a web application or REST APIs, each client request is served by a different thread. The logs generated from such an application will be a mix of all client requests and sources. This makes it difficult to make any business sense of the logs or to debug.

在一个典型的多线程应用程序中,如Web应用程序或REST APIs,每个客户的请求是由不同的线程提供的。从这样的应用中产生的日志将是所有客户端请求和来源的混合。这使得我们很难从日志中获得任何商业意义上的信息或进行调试。

Nested Diagnostic Context (NDC) manages a stack of contextual information, on a per thread basis. The data in NDC is available to every log request in the code and can be configured to log with every log message – even at places where the data is not in scope. This contextual information in each log message helps to distinguish the logs by its source and context.

嵌套诊断上下文(NDC)以每个线程为基础,管理一个上下文信息的堆栈。NDC中的数据对代码中的每个日志请求都是可用的,并且可以被配置为与每个日志消息一起记录–甚至在数据不在范围内的地方。每个日志消息中的这种上下文信息有助于通过其来源和上下文来区分日志。

The Mapped Diagnostic Context (MDC) also manages information on a per-thread basis, but as a map.

映射的诊断上下文(MDC)也在每线程的基础上管理信息,但是是作为一个映射。

3. The NDC Stack in a Sample Application

3.示例应用程序中的NDC堆栈

To demonstrate the usage of an NDC stack, let’s take an example of a REST API that sends money to an investment account.

为了证明NDC堆栈的用途,让我们以一个向投资账户发送资金的REST API为例。

The information required as input is represented in an Investment class:

作为输入所需的信息在一个投资类中表示。

public class Investment {
    private String transactionId;
    private String owner;
    private Long amount;

    public Investment (String transactionId, String owner, Long amount) {
        this.transactionId = transactionId;
        this.owner = owner;
        this.amount = amount;
    }
    
    // standard getters and setters
}

The transfer to the investment account is performed using InvestmentService. The full source code for these classes can be found in this github project.

使用InvestmentService执行向投资账户的转移。这些类的完整源代码可以在这个github项目中找到。

In the sample application, the data transactionId and owner are placed in the NDC stack, in the thread that is processing a given request. This data is available in every log message in that thread. This way, each unique transaction can be traced, and the relevant context of each log message can be identified.

在示例应用程序中,数据transactionIdowner被放置在NDC栈中,在处理特定请求的线程中。这个数据在该线程的每条日志信息中都是可用的。这样,每个独特的交易都可以被追踪,每个日志消息的相关背景都可以被识别。

4. NDC in Log4j

4.Log4j中的NDC[/strong]

Log4j provides a class called NDC which provides static methods to manage data in the NDC stack. Basic usage:

Log4j提供了一个名为NDC的类,它提供了静态方法来管理NDC栈中的数据。基本用法。

  • When entering a context, use NDC.push() to add context data in the current thread
  • When leaving the context, use NDC.pop() to take out the context data
  • When exiting the thread, call NDC.remove() to remove diagnostic context for the thread and ensure memory is freed (as of Log4j 1.3, no longer necessary)

In the sample application, let’s use NDC to add/remove contextual data at relevant places in the code:

在示例应用程序中,让我们使用NDC在代码的相关位置添加/删除上下文数据。

import org.apache.log4j.NDC;

@RestController
public class Log4JController {
    @Autowired
    @Qualifier("Log4JInvestmentService")
    private InvestmentService log4jBusinessService;

    @RequestMapping(
      value = "/ndc/log4j", 
      method = RequestMethod.POST)
    public ResponseEntity<Investment> postPayment(
      @RequestBody Investment investment) {
        
        NDC.push("tx.id=" + investment.getTransactionId());
        NDC.push("tx.owner=" + investment.getOwner());

        log4jBusinessService.transfer(investment.getAmount());

        NDC.pop();
        NDC.pop();

        NDC.remove();

        return 
          new ResponseEntity<Investment>(investment, HttpStatus.OK);
    }
}

The contents of NDC can be displayed in log messages by using %x option in the ConversionPattern used by appender in log4j.properties:

通过使用log4j.properties中appender使用的ConversionPattern选项,NDC的内容可以显示在日志消息中。

log4j.appender.consoleAppender.layout.ConversionPattern 
  = %-4r [%t] %5p %c{1} - %m - [%x]%n

Let’s deploy the REST API to tomcat. Sample request:

让我们把REST API部署到Tomcat。样本请求。

POST /logging-service/ndc/log4j
{
  "transactionId": "4",
  "owner": "Marc",
  "amount": 2000
}

We can see the diagnostic context information in the log output:

我们可以在日志输出中看到诊断性的上下文信息。

48569 [http-nio-8080-exec-3]  INFO Log4JInvestmentService 
  - Preparing to transfer 2000$. 
  - [tx.id=4 tx.owner=Marc]
49231 [http-nio-8080-exec-4]  INFO Log4JInvestmentService 
  - Preparing to transfer 1500$. 
  - [tx.id=6 tx.owner=Samantha]
49334 [http-nio-8080-exec-3]  INFO Log4JInvestmentService 
  - Has transfer of 2000$ completed successfully ? true. 
  - [tx.id=4 tx.owner=Marc] 
50023 [http-nio-8080-exec-4]  INFO Log4JInvestmentService 
  - Has transfer of 1500$ completed successfully ? true. 
  - [tx.id=6 tx.owner=Samantha]
...

5. NDC in Log4j 2

5 Log4j 2中的NDC[/strong]

NDC in Log4j 2 is called as Thread Context Stack:

Log4j 2中的NDC被称为线程上下文栈。

import org.apache.logging.log4j.ThreadContext;

@RestController
public class Log4J2Controller {
    @Autowired
    @Qualifier("Log4J2InvestmentService")
    private InvestmentService log4j2BusinessService;

    @RequestMapping(
      value = "/ndc/log4j2", 
      method = RequestMethod.POST)
    public ResponseEntity<Investment> postPayment(
      @RequestBody Investment investment) {
        
        ThreadContext.push("tx.id=" + investment.getTransactionId());
        ThreadContext.push("tx.owner=" + investment.getOwner());

        log4j2BusinessService.transfer(investment.getAmount());

        ThreadContext.pop();
        ThreadContext.pop();

        ThreadContext.clearAll();

        return 
          new ResponseEntity<Investment>(investment, HttpStatus.OK);
    }
}

Just as with Log4j, let’s use the %x option in the Log4j 2 configuration file log4j2.xml:

就像Log4j一样,让我们在Log4j 2配置文件%x中使用log4j2.xml选项。

<Configuration status="INFO">
    <Appenders>
        <Console name="stdout" target="SYSTEM_OUT">
            <PatternLayout
              pattern="%-4r [%t] %5p %c{1} - %m -%x%n" />
        </Console>
    </Appenders>
    <Loggers>
        <Logger name="com.baeldung.log4j2" level="TRACE" />
            <AsyncRoot level="DEBUG">
            <AppenderRef ref="stdout" />
        </AsyncRoot>
    </Loggers>
</Configuration>

Log output:

日志输出。

204724 [http-nio-8080-exec-1]  INFO Log4J2InvestmentService 
  - Preparing to transfer 1500$. 
  - [tx.id=6, tx.owner=Samantha]
205455 [http-nio-8080-exec-2]  INFO Log4J2InvestmentService 
  - Preparing to transfer 2000$. 
  - [tx.id=4, tx.owner=Marc]
205525 [http-nio-8080-exec-1]  INFO Log4J2InvestmentService 
  - Has transfer of 1500$ completed successfully ? false. 
  - [tx.id=6, tx.owner=Samantha]
206064 [http-nio-8080-exec-2]  INFO Log4J2InvestmentService 
  - Has transfer of 2000$ completed successfully ? true. 
  - [tx.id=4, tx.owner=Marc]
...

6. NDC in Logging Facades (JBoss Logging)

6.Logging Facades(JBoss Logging)中的NDC

Logging facades like SLF4J provide integration with various logging frameworks. NDC is not supported in SLF4J (but included in slf4j-ext module). JBoss Logging is a logging bridge, just like SLF4J. NDC is supported in JBoss Logging.

像SLF4J这样的日志界面提供了与各种日志框架的集成。NDC在SLF4J中不被支持(但包含在slf4j-ext模块中)。JBoss Logging是一个日志桥,就像SLF4J。NDC在JBoss Logging中被支持。

By default, JBoss Logging will search the ClassLoader for the availability of back-ends/providers in the following order of precedence: JBoss LogManager, Log4j 2, Log4j, SLF4J and JDK Logging.

默认情况下,JBoss Logging会在ClassLoader中按以下顺序搜索后端/供应商的可用性。JBoss LogManager、Log4j 2、Log4j、SLF4J和JDK Logging。

JBoss LogManager as the logging provider is typically used inside WildFly application server. In our case, the JBoss logging bridge will pick the next in order of precedence (which is Log4j 2) as the logging provider.

JBoss LogManager作为日志提供者通常在WildFly应用服务器中使用。在我们的案例中,JBoss的日志桥会选择优先级较低的(也就是Log4j 2)作为日志提供者。

Let us begin by adding the required dependency in pom.xml:

让我们首先在pom.xml中添加所需的依赖性。

<dependency>
    <groupId>org.jboss.logging</groupId>
    <artifactId>jboss-logging</artifactId>
    <version>3.3.0.Final</version>
</dependency>

The latest version of the dependency can be checked here.

最新版本的依赖关系可以在这里检查。

Let’s add contextual information to the NDC stack:

让我们在NDC堆栈中添加上下文信息。

import org.jboss.logging.NDC;

@RestController
public class JBossLoggingController {
    @Autowired
    @Qualifier("JBossLoggingInvestmentService")
    private InvestmentService jbossLoggingBusinessService;

    @RequestMapping(
      value = "/ndc/jboss-logging", 
      method = RequestMethod.POST)
    public ResponseEntity<Investment> postPayment(
      @RequestBody Investment investment) {
        
        NDC.push("tx.id=" + investment.getTransactionId());
        NDC.push("tx.owner=" + investment.getOwner());

        jbossLoggingBusinessService.transfer(investment.getAmount());

        NDC.pop();
        NDC.pop();

        NDC.clear();

        return 
          new ResponseEntity<Investment>(investment, HttpStatus.OK);
    }
}

Log output:

日志输出。

17045 [http-nio-8080-exec-1]  INFO JBossLoggingInvestmentService 
  - Preparing to transfer 1,500$. 
  - [tx.id=6, tx.owner=Samantha]
17725 [http-nio-8080-exec-1]  INFO JBossLoggingInvestmentService 
  - Has transfer of 1,500$ completed successfully ? true. 
  - [tx.id=6, tx.owner=Samantha]
18257 [http-nio-8080-exec-2]  INFO JBossLoggingInvestmentService 
  - Preparing to transfer 2,000$. 
  - [tx.id=4, tx.owner=Marc]
18904 [http-nio-8080-exec-2]  INFO JBossLoggingInvestmentService 
  - Has transfer of 2,000$ completed successfully ? true. 
  - [tx.id=4, tx.owner=Marc]
...

7. Conclusion

7.结论

We have seen how diagnostic context helps in correlating logs in a meaningful way – from a business standpoint as well as for debugging purposes. It is an invaluable technique to enrich logging, especially in multi-threaded applications.

我们已经看到了诊断上下文是如何帮助以有意义的方式关联日志的–从业务的角度以及调试的目的。这是一种丰富日志记录的宝贵技术,特别是在多线程应用程序中。

The example used in this article can be found in the Github project.

本文中使用的例子可以在Github项目中找到。