入れ子になった診断コンテキスト(NDC)によるJavaロギング

ネストされた診断コンテキスト(NDC)を使用したJavaロギング

1. 概要

ネストされた診断コンテキスト(NDC)は、さまざまなソースからのインターリーブされたログメッセージを区別するのに役立つメカニズムです。 NDCは、各ログエントリに特有のコンテキスト情報を追加する機能を提供することでこれを行います。

この記事では、さまざまなJavaロギングフレームワークでのNDCの使用とその使用/サポートについて説明します。

2. 診断コンテキスト

WebアプリケーションやREST APIなどの典型的なマルチスレッドアプリケーションでは、各クライアントリクエストは異なるスレッドによって処理されます。 このようなアプリケーションから生成されたログは、すべてのクライアントのリクエストとソースのミックスになります。 これにより、ログをビジネス的に理解したり、デバッグしたりすることが難しくなります。

ネストされた診断コンテキスト(NDC)は、スレッドごとにコンテキスト情報のスタックを管理します。 NDCのデータは、コード内のすべてのログリクエストで使用でき、データがスコープ内にない場所でも、すべてのログメッセージでログを記録するように構成できます。 各ログメッセージのこのコンテキスト情報は、ソースとコンテキストによってログを区別するのに役立ちます。

Mapped Diagnostic Context (MDC)もスレッドごとに情報を管理しますが、マップとして管理します。

3. サンプルアプリケーションのNDCスタック

NDCスタックの使用法を示すために、投資口座に送金するRESTAPIの例を見てみましょう。

入力として必要な情報は、Investmentクラスで表されます。

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
}

投資勘定への振替は、InvestmentServiceを使用して実行されます。 これらのクラスの完全なソースコードは、this github projectにあります。

サンプルアプリケーションでは、データtransactionIdownerは、特定の要求を処理しているスレッドのNDCスタックに配置されます。 このデータは、そのスレッドのすべてのログメッセージで利用可能です。 このようにして、各一意のトランザクションを追跡し、各ログメッセージの関連するコンテキストを識別できます。

4. Log4jのNDC

Log4jは、NDCスタック内のデータを管理するための静的メソッドを提供するNDCと呼ばれるクラスを提供します。 基本的な使い方

  • コンテキストを入力するときは、NDC.push()を使用して現在のスレッドにコンテキストデータを追加します

  • コンテキストを離れるときは、NDC.pop()を使用してコンテキストデータを取り出します

  • スレッドを終了するときは、NDC.remove()を呼び出してスレッドの診断コンテキストを削除し、メモリが解放されていることを確認します(Log4j 1.3以降、不要になりました)

サンプルアプリケーションでは、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 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, HttpStatus.OK);
    }
}

NDCの内容は、log4j.propertiesのアペンダーが使用するConversionPattern%xオプションを使用して、ログメッセージに表示できます。

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

RESTAPIをTomcatにデプロイしましょう。 サンプルリクエスト:

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

ログ出力で診断コンテキスト情報を確認できます。

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. Log4j2のNDC

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 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, HttpStatus.OK);
    }
}

Log4jの場合と同様に、Log4j 2構成ファイルlog4j2.xml%xオプションを使用しましょう。


    
        
            
        
    
    
        
            
            
        
    

ログ出力:

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(JBoss Logging)

SLF4Jのようなロギングファサードは、さまざまなロギングフレームワークとの統合を提供します。 NDCはSLF4Jではサポートされていません(slf4j-extモジュールには含まれています)。 JBoss Loggingは、SLF4Jと同様にロギングブリッジです。 NDCはJBoss Loggingでサポートされています。

デフォルトでは、JBoss LoggingはClassLoaderを検索して、バックエンド/プロバイダーの可用性を次の優先順位で検索します:JBoss LogManager、Log4j 2、Log4j、SLF4J、およびJDKLogging。

ロギングプロバイダーとしてのJBoss LogManagerは、通常WildFlyアプリケーションサーバー内で使用されます。 この場合、JBossロギングブリッジはロギングプロバイダーとして優先順位の次(Log4j 2)を選択します。

必要な依存関係をpom.xmlに追加することから始めましょう。


    org.jboss.logging
    jboss-logging
    3.3.0.Final

依存関係の最新バージョンはhereで確認できます。

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 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, HttpStatus.OK);
    }
}

ログ出力:

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. 結論

診断のコンテキストが、ビジネスの観点からだけでなくデバッグの目的でも、ログを意味のある方法で相関させるのにどのように役立つかを見てきました。 特にマルチスレッドアプリケーションでロギングを強化するための非常に貴重な手法です。

この記事で使用されている例は、the Github projectにあります。