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

1概要

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

この記事では、さまざまなJavaロギング・フレームワークにおけるNDCの使用とその使用法/サポートについて探ります。

2診断コンテキスト

WebアプリケーションやREST APIなどの一般的なマルチスレッドアプリケーションでは、各クライアント要求は異なるスレッドによって処理されます。そのようなアプリケーションから生成されたログは、すべてのクライアント要求とソースの組み合わせになります。これにより、ログをビジネス上意味のあるものにしたりデバッグしたりすることが難しくなります。

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

リンク:/mdc-in-log4j-2-logback[Mapped Diagnostic Context(MDC)]も情報をスレッドごとに、ただしマップとして管理します。

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

NDCスタックの使い方を説明するために、投資アカウントにお金を送るREST APIの例を見てみましょう。

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

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 を使用して実行されます。これらのクラスの完全なソースコードはhttps://github.com/eugenp/tutorials/tree/master/logging-modules/log-mdc[このgithubプロジェクト]にあります。

サンプルアプリケーションでは、data transactionId owner は、特定の要求を処理しているスレッド内の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<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);
    }
}

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

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

REST APIを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 Log4j 2の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<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);
    }
}

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

<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>

ログ出力:

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モジュールに含まれています)。 SLF4Jと同様に、JBoss Loggingはロギングブリッジです。 NDCはJBoss Loggingでサポートされています。

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

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

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

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

依存関係の最新バージョンはhttps://search.maven.org/classic/#search%7C1%7Cg%3A%22org.jboss.logging%22%20AND%20a%3A%22jboss-logging%で確認できます。 22[ここ]

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);
    }
}

ログ出力:

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

ビジネス上の観点からも、デバッグの目的からも、診断コンテキストが有意義な方法でログの相関にどのように役立つかを説明しました。特にマルチスレッドアプリケーションでは、ロギングを強化することは非常に貴重な技術です。

この記事で使われている例はhttps://github.com/eugenp/tutorials/tree/master/logging-modules/log-mdc[Githubプロジェクト]にあります。