slf4jで独自ログ項目を追加(MDC)

はじめに

  • 業務要件、障害発生時の処理追跡や証跡等、ログ出力に特定項目を含めたい場合があります。
    共通のログ出力ユーティリティやライブラリを作って、その中でログ出力内容を変更しても良いのですが、MDC(Mapped Diagnostic Context)という仕組みで簡単に実装できます。
  • なお、ここで前提としているslf4jについて知りたい方は、こちらをご覧ください。
  • ログ全般の設計・実装ポリシーに興味がある方には、こちらが参考になれば幸いです。
    NDW

    一般的なログ種別・ログ出力項目の紹介、例外時や稼働監視システムを考慮したログ設計の注意点等を紹介します。また、実装時のロ…

はじめてのMDC

一般的なログ出力ライブラリを使って、任意のメッセージを指定してログを出力すると、指定したメッセージの他に、日時、ログレベル、ロガー(クラス名)等のログ項目も併せて出力されます。MDCを使用すると、このような既定のログ項目の他に、独自のログ項目を出力できるようになります。

また、このようなログ項目をどのような書式で出力するかのログ出力書式は、ログ出力ライブラリの設定ファイルで定義されています。例えば、日時であれば”%d”、ログレベルであれば”%p”等のように定義されており、設定ファイルを変更することで、プログラムを変更せずに柔軟にログ出力仕様を変更できます。MDCで追加したログの項目は、”%X{ログ項目名}”のように、他の項目と同様にログ出力書式で指定できるようになります。

Webアプリのログファイルに、独自のログ項目としてユーザIDを追加する例を説明します。
(Wildfly13で動作するサーブレットやJSF等のJavaプログラムで、SLF4Jを使う前提。)

  • SLF4Jで独自ログ項目を出力する場合、MDCというスタティッククラスを使用します。
    ログ項目”userid”として”a00001″を出力する場合、次のようにMDCクラスを使用します。
  • 上記で出力したログ項目は、設定ファイルで次のように”%{userid}”で指定できます。
  • ログ出力は次のようになります。
    上記の”%X{userid}”に対応する位置に”a00001″が出力されます。

MDCの詳細

  • 複数クライアントからの同時アクセス時のログ出力のパターンとしてMDCが考案されました。これは、”Patterns for Logging Diagnostic Messages in Pattern Languages of Program Design 3″という書籍が原型のようです。
  • 汎用的なロギングAPIであるslf4jでもMDCを使用することができます。ただし、実際にMDCを使ってログを出力するためには、slf4jにバインドされているログ出力ライブラリが対応している必要があります。
  • ポピュラーなログ出力ライブラリである、log4j2logback等はMDCを実装しています。
    Wildfly11/EAP7ではjboss-loggingが使われており、こちらもMDCを実装しています。
  • MDCの基本的な仕組みは、MDCのログ内容をスレッド毎の変数領域に格納しています。そのため、あるリクエスト(スレッド)で出力したログは、そのスレッドのログ出力時のみ有効です。別のリクエスト(スレッド)でのログ出力では参照できません。
  • MDCのログを出力する基本的にはリクエストに対して独自のログ項目を出力する想定であり、サーブレットフィルタ, JSF フェーズリスナ, インターセプター等の比較的に早いタイミングでのログ埋め込みをお薦めします。(MDCログを出力するタイミングが遅すぎると該当項目が空になってしまうので。)
  • スレッドは性能向上のためにスレッドを終了せずに再利用(スレッドプール)する場合があります。このケースでは、前回のリクエストの処理で設定したログ内容が残っている可能性があります。そのため、リクエストの処理終了後に初期化した方が安全です。
  • 参考ですが、MDCと同様にNDC(Nested Diagnostic Context)と呼ばれる方法もあります。MDCはスレッドに対してkey=valueのみの指定ですが、NDCではスレッドで複数の処理を行う際の深さをスタックで表現できるようです。私が業務で使ったことがないので、ここでは割愛します。

業務での活用例

私が業務で使ったことがある例を紹介します。

用途の例(項目名)説明・用途
ログイン中のユーザIDユーザ認証が完了し、セッション情報に保持されているユーザIDを出力します。特定ユーザの操作やログを追跡する際にユーザIDが各ログ行に含まれていると抽出しやすい。
セッションIDセキュリティ要件が厳しいアプリで出力する場合がある。
カンバゼーションIDカンバゼーション単位(JavaEEのセッションより小さいスコープ)で操作を追跡する際に便利である。カンバゼーションに不慣れな開発者向けに開発用で出力する場合がある。
X-Forwared-For(XFF)ヘッダロードバランサやリバースプロキシの背後にアプリサーバを配置する場合、リクエストの送信元IPアドレスが当該ヘッダに設定される場合があるので、この値を出力する。
コンテキスト名結合試験等で、あるWebアプリを名前(”/example-web1″, “/example-web2″等のコンテキスト名)を変えて複数デプロイしたい場合がある。これらのWebアプリが同一のログファイルに出力されてしまうと、どのWebアプリからのログか判断できなくなるため、コンテキスト名を出力して判別できるようにする。

サンプル

Webアプリに対する全てのリクエストに対してMDCログを出力できるようにすサーブレットフィルタのサンプルです。
Webアプリのプロジェクトはこちらで公開しています。

サーブレットフィルター

セッションID、HTTPリクエストのHOSTヘッダ値をMDCでログに出力します。
完全なソースはこちらを参照のこと。

Wildfly13設定(standalone.xml)

上記のプログラムで指定したログ項目名(MDC_ITEM_HOST: “host”, MDC_ITEM_SESSION_ID: “session_id”)を、ログ出力書式に指定します。

実行結果