第4章 アペンダー

西部について教えたいことが多すぎるのでどこから始めたらいいのかわからないよ。1つを選べば残りの100を捨てることになってしまう。最初の1つを決めるにはどうしたいいんだろう?

—JOHN STEINBECK, East of Eden

アペンダーについて

logback はロギングイベントを出力する仕事を、アペンダーと呼ばれるコンポーネントに任せています。ch.qos.logback.core.Appenderインターフェイスを実装したものがアペンダーとして利用できます。このインターフェイスに宣言された重要なメソッドは次のとおりです。

package ch.qos.logback.core;
  
import ch.qos.logback.core.spi.ContextAware;
import ch.qos.logback.core.spi.FilterAttachable;
import ch.qos.logback.core.spi.LifeCycle;
  

public interface Appender<E> extends LifeCycle, ContextAware, FilterAttachable {

  public String getName();
  public void setName(String name);
  void doAppend(E event);
  
}

Appenderインターフェイスのほとんどのメソッドはゲッター、あるいはセッターです。doAppend()メソッドだけは特別で、唯一の引数として型Eのオブジェクトを取ります。Eの実際の型は、logbackモジュールによって異なります。logback-classic モジュールの場合、EILoggingEventになるでしょうし、logback-access モジュールではAccessEventになるでしょう。doAppend()メソッドは、おそらくlogbackフレームワークの中で最も重要なメソッドです。ロギングイベントを、適切な書式で、適切な出力デバイスに出力する役目があります。

アペンダーには名前を付けられます。設定ファイル中で参照しやすいようにするためです。AppenderインターフェイスはFilterAttachableインターフェイスを継承しています。つまり、アペンダーのインスタンスには一つ以上のフィルターを割り当てられるのです。フィルターについては、後の章で詳しく説明します。

アペンダーには、ロギングイベントを出力することについて最終的な責任があります。しかし、アペンダー自体が書式化をするのではなく、LayoutあるいはEncoderオブジェクトに処理を委譲します。レイアウトやエンコーダーは、ただ一つのアペンダーにだけ関連付けられ、そのアペンダーだけが参照することになります。アペンダーによっては、組み込みの、あるいは固定の書式が指定されています。そういうアペンダーには、レイアウトもエンコーダーも不要です。たとえば、 SocketAppenderは、ロギングイベントを接続したリモートホストに転送する前に単純にシリアライズするだけです。

AppenderBase

ch.qos.logback.core.AppenderBaseは、Appenderインターフェイスを実装した抽象クラスです。このクラスは、どんなアペンダーにも必要な共通の機能として、名前や活性化状態やレイアウト、フィルターに対するゲッターおよびセッターメソッドが実装されています。logback の配布物に含まれるアペンダーはすべてこのクラスの派生クラスです。AppenderBaseは抽象クラスですが、AppendインターフェイスのdoAppend()メソッドを実装しています。AppenderBaseクラスの話は、実際にソースコードの抜粋を見ながら進めるのが一番わかりやすいと思います。

public synchronized void doAppend(E eventObject) {

  // prevent re-entry.
  if (guard) {
    return;
  }

  try {
    guard = true;

    if (!this.started) {
      if (statusRepeatCount++ < ALLOWED_REPEATS) {
        addStatus(new WarnStatus(
            "Attempted to append to non started appender [" + name + "].",this));
      }
      return;
    }

    if (getFilterChainDecision(eventObject) == FilterReply.DENY) {
      return;
    }
    
    // ok, we now invoke the derived class's implementation of append
    this.append(eventObject);

  } finally {
    guard = false;
  }
}

doAppend()メソッドはsynchronizedメソッドになっています。つまり、別のスレッドから同じアペンダーに安全にロギングできるのです。スレッドTdoAppend()メソッドを実行している間は、Tだけが排他的にアペンダーにアクセスできるので、他のスレッドからの呼び出しはキューイングされます。

synchronized が不適切な場合もあります。そういうときのために、logback の配布物にはAppenderBaseとよく似たch.qos.logback.core.UnsynchronizedAppenderBaseも含まれています。ややこしくなるので、UnsynchronizedAppenderBaseについては本章の後半で説明します。

doAppend()メソッドでは、まず最初にguard変数にtrueが設定されているかどうかをチェックします。trueだったら、ただちに終了します。そうでなければ、まずguard変数にtrueを設定してから次の処理に進みます。guard変数によって、doAppend()メソッドを再帰的に呼び出してしまうことを防いでいるのです。あるコンポーネントが、ログを取得するためにappend()というメソッドを呼び出したとしましょう。そうすると、今呼び出したアペンダーと同じアペンダーを直接呼び出すことになってしまうので、無限ループした結果スタックオーバーフローが発生してしまいます。

次の式ではstartedフィールドにtrueが設定されているかどうかをチェックします。trueでなければ、警告メッセージを出力してdoAppend()メソッドは終了します。これは、クローズされたアペンダーには何も書き込めなくなるということです。AppenderオブジェクトはLifeCycleインターフェイスを実装しているので、つまりstart()メソッド、stop()メソッド、isStarted()メソッドを実装しています。Joran設定フレームワークは、アペンダーの全てのプロパティを設定してから、アペンダーを開始して活性化状態をアクティブにするためstart()メソッドを呼び出します。アペンダーにもよりますが、指定されていないプロパティがあったり、プロパティに指定した値の影響で、開始できないことがあります。たとえば、FIleAppenderの場合ファイルを作成するかどうかは指定された切り捨てモードに依存しています。ですので、Fileオプションが指定されていたとしても、Appendオプションに正しい値が指定されていなければ、正常に動作しません。開始するまでの処理順序が明確なので、アペンダーは自身のプロパティがちゃんと設定されていることを前提として動作することができます。

アペンダーが開始できなかったとき、あるいは、停止してしまったときは、logback の内部状態管理システムによって警告メッセージが出力されます。何度か(ロギングを?)試みたあと、同じ内容の警告メッセージで溢れかえってしまうのを避けるため、doAppend()メソッドは警告メッセージを出力しないようになります。

その次のif文では割り当てられたフィルターの応答をチェックします。フィルターチェインの応答によって、ロギングイベントを拒否するか受け入れるかが決まります。フィルターチェインが何も決めなかったら、デフォルトでロギングイベントは受け入れられるようになっています。

その後は、派生クラスで実装されてたappend()メソッドを呼び出します。このメソッドは、実際に適切なデバイスへロギングイベントを出力します。

最後にguard変数が開放され、キューイングされたdoAppend()メソッドの呼び出しが処理できるようになります。

ここの説明では "プロパティ" という言葉の代わりに "オプション" という言葉を使いました。これは、JavaBeanの規約に従ったゲッターおよびセッターメソッドが用意された属性のことです。

logback-coreモジュール

logback-coreモジュールは、他のモジュールを構築する基盤となるモジュールです。一般的に、logbac-core モジュールのコンポーネントは、ある程度の(少なくとも最小限の)カスタマイズが必要です。以降の節では、カスタマイズしないでもすぐに利用できるアペンダーを紹介していきます。

OutputStreamAppender

OutputStreamAppenderjava.io.OutputStreamにロギングイベントを出力します。このクラスは他のアペンダーを構築するための基本的なサービスを提供するものです。普通なら、利用者がOutputStreamAppenderを直接インスタンス化することはありません。java.io.OutputStreamを文字列で表現することはできないので、設定ファイルからOutputStreamオブジェクトを指定することはできないのです。簡単に言うと、設定ファイルではOutputStreamAppenderを設定することはできません。しかし、OutputStreamAppenderに設定できるプロパティが無いという意味ではありません。次のようなプロパティがあります。

プロパティ名 説明
encoder Encoder OutputStreamAppenderへの出力を書式化する。エンコーダについては別の章で説明しています。

OutputStreamAppenderConsoleAppenderFileAppenderの基底クラスです。RollingFileAppenderの基底クラスはFileAppenderなので、これも含めておきます。次の図はOutputStreamAppenderとそのサブクラスの関係を図示したものです。

OutputStreamAppenderとサブクラスを示すUML図

ConsoleAppender

ConsoleAppenderは名前のとおり、ロギングイベントをコンソールに出力します。正確に言うと、System.outあるいはSystem.errに出力します。デフォルトではSystem.outが使われます。ConsoleAppenderは、利用者が指定したエンコーダーを使ってロギングイベントを書式化します。エンコーダーについては別の章で説明します。System.outSystem.errの型はjava.io.PrintStreamです。つまり、いずれもバッファIO操作のためのOutputStreamWriterにラップされているということです。

プロパティ名 説明
encoder Encoder OutputStreamAppenderと同じです。
target String 文字列で、System.outSystem.errを指定します。デフォルトはSystem.outにです。
withJansi boolean withJansiプロパティの値にはデフォルトでfalseが設定されています。withJansiプロパティにtrueを指定すると、Jansiライブラリが有効化され、Windowsマシン上でANSIカラーコードがサポートされるようになります。Windows のホスト上でこのプロパティにtrueを指定する場合、クラスパス上にjansiライブラリのjarファイルを置かなければなりません。なお、UnixベースのOSであるLinuxやMac OS Xのターミナルは、デフォルトでANSIカラーコードをサポートしています。

Eclipse IDEから利用する場合は、ANSI in Eclipse Consoleプラグインをインストールしてみましょう。

ConsoleAppenderの設定サンプルを見てください。

例:ConsoleAppenderの設定(logback-examples/src/main/java/chapters/appenders/conf/logback-Console.xml

Groovyで表示
<configuration>

  <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
    <!-- encoders are assigned the type
         ch.qos.logback.classic.encoder.PatternLayoutEncoder by default -->
    <encoder>
      <pattern>%-4relative [%thread] %-5level %logger{35} - %msg %n</pattern>
    </encoder>
  </appender>

  <root level="DEBUG">
    <appender-ref ref="STDOUT" />
  </root>
</configuration>

logback-examplesディレクトリに移動して、クラスパスを設定すれば、次のコマンドで上の設定ファイルを使って実行できます。

java chapters.appenders.ConfigurationTester src/main/java/chapters/appenders/conf/logback-Console.xml

FileAppender

FileAppenderOutputStreamAppenderの派生クラスで、ファイルにロギングイベントを出力します。fileオプションで対象のファイルを指定します。既にファイルが存在するとき、ファイル内容を切り捨てるか、追加するかどうかは、appendプロパティの値に応じて決まります。

プロパティ名 説明
append boolean trueの場合、既存のファイルの末尾にロギングイベントを追加します。appendプロパティがfalseの場合、既存のファイルの内容は捨てられてしまいます。デフォルトはtrueです。
encoder Encoder OutputStreamAppenderと同じです。
file String 書き込み先のファイル名です。ファイルが存在しない場合、新しく作成します。Windowsプラットフォームの利用者は、バックスラッシュをエスケープするの忘れがちなので注意してください。たとえば、c:\temp\test.logという文字列は意図したように解釈されません。'\t'はエスケープシーケンスのタブ文字{\u0009}として解釈されるでしょう。c:/temp/test.logと書くか、c:\\temp\\test.logのように書けばよいでしょう。デフォルト値はありません。

指定したファイルの親ディレクトリが存在しない場合、 FileAppenderは途中の全てのディレクトリを作成します。

prudent boolean prudentモードでは、指定されたファイルに安全に書き込むようになります。同じファイルを対象にした他のFileAppenderがいるとしても、それが別のJVMで動いているとしても、ましてや別のホストで動いているとしても、です。デフォルト値はfalseです。

prudent モードは、多少の制限はあるものの、RollingFileAppenderと組み合わせて利用することもできます。

prudent モードがtrueなら、appendプロパティも自動的にtrueになります。

prudentモードは排他的なファイルロックを使用します。ファイルロックを使うと、ロギングイベントの出力にかかるコストがおよそ3倍程度になることが分かっています。標準的なPCで、ローカルのハードディスク上のファイルへ一つロギングイベントを出力をするとき、prudentモードがオフの場合はおよそ10マイクロ秒かかります。prudentモードをオンにすると、30マイクロ秒になります。言い換えると、prudentモードがオフの場合は毎秒100,000回のロギングイベントが処理できるのに対して、prudentモードがオンの場合は毎秒33,000回しか処理できなくなるということです。

prudentモードでは、複数のJVMから同じファイルに行うIO操作を効果的にシリアライズします。したがって、JVMの数が多くなればなるほど、IO操作ごとの待ち時間が長くなります。IO操作の合計時間が、毎秒20回オーダーのロギング要求が処理できる程度であれば、性能への影響は無視してもよいでしょう。アプリケーションが毎秒100回以上のIO操作をするようであれば、きっと性能影響があるのでprudentモードを使うのはやめてください。

ネットワークファイルロックネットワークファイルシステム上のログファイルを対象にすると非常にコストが高くなります。ネットワークファイルシステム越しのファイルロックは、プロセスがすでに所有しているロックをリリースする前に再取得する、という振る舞いに強く依存していることも同じくらい重要です。したがって1つのプロセスがログファイルを占有していると、他のプロセスからはデッドロックしているように見えるので、ロックを待ち続けることになってしまいます。

prudentモードは、ネットワークの速度と同じくらいの影響をOSの実装からも受けます。私たちの提供しているとても小さいアプリケーションのFileLockSimulatorを使えば、あなたの環境でprudentモードがどのように振る舞うかシミュレートできます。

即時フラッシュ デフォルトでは、それぞれのロギングイベントは、最終的な出力ストリームに即時にフラッシュされます。これは、あなたのアプリケーションがアペンダーをちゃんとクローズしていない場合、ロギングイベントが失われないようにするためのより安全な方法です。しかし、ロギング要求のスループットが大幅に増加してしまう場合には、EncorderimmediateFlushプロパティにfalseを指定することもできます。エンコーダー、特にLayoutWrappingEncoderについては別の章で説明します。

FileAppenderの設定例を見てください。

例:FileAppenderの設定(logback-examples/src/main/java/chapters/appenders/conf/logback-fileAppender.xml

Groovyとして表示
<configuration>

  <appender name="FILE" class="ch.qos.logback.core.FileAppender">
    <file>testFile.log</file>
    <append>true</append>
    <!-- encoders are assigned the type
         ch.qos.logback.classic.encoder.PatternLayoutEncoder by default -->
    <encoder>
      <pattern>%-4relative [%thread] %-5level %logger{35} - %msg%n</pattern>
    </encoder>
  </appender>
	
  <root level="DEBUG">
    <appender-ref ref="FILE" />
  </root>
</configuration>

logback-examplesディレクトリに移動すれば、次のコマンドで上の設定ファイルを使って実行できます。

java chapters.appenders.ConfigurationTester src/main/java/chapters/appenders/conf/logback-fileAppender.xml

ファイル名を一意にする(タイムスタンプを使う)

アプリケーションの開発フェーズの間や、アプリケーションを実行している時間が短い(たとえば、バッチアプリケーション)場合は、アプリケーションを実行するたびに新しいログファイルを作るほうがよいでしょう。timestamp要素を使えば簡単に実現できます。以下に例を示します。

例:タイムスタンプを使ってファイル名を一意にするFileAppenderの設定(logback-examples/src/main/java/chapters/appenders/conf/logback-timestamp.xml

Groovyとして表示
<configuration>

  <!-- Insert the current time formatted as "yyyyMMdd'T'HHmmss" under
       the key "bySecond" into the logger context. This value will be
       available to all subsequent configuration elements. -->
  <timestamp key="bySecond" datePattern="yyyyMMdd'T'HHmmss"/>

  <appender name="FILE" class="ch.qos.logback.core.FileAppender">
    <!-- use the previously created timestamp to create a uniquely
         named log file -->
    <file>log-${bySecond}.txt</file>
    <encoder>
      <pattern>%logger{35} - %msg%n</pattern>
    </encoder>
  </appender>

  <root level="DEBUG">
    <appender-ref ref="FILE" />
  </root>
</configuration>

timestamp要素には、key属性およびdatePattern属性という2つの必須属性と、timeReference属性という任意属性があります。key属性には、変数と同じく、他の設定要素からタイムスタンプを参照するときの名前を指定します。datePattern属性には、設定ファイルを解釈した時点の日時を文字列に変換するための日付パターン文字列を指定します。日付パターン文字列に指定できるのは、SimpleDateFormatで利用できるものと同じです。timeReference属性には、タイムスタンプの基準時間を指定します。デフォルトでは、現在の日時、つまり、設定ファイルを解析、解釈した時点の日時になります。ですが、コンテキストを生成した時間を基準時間としたほうが便利な場合もあるでしょう。そういう場合は、timeReference属性"contextBirth"を指定すればよいです。

次のコマンドを実行して、timestamp要素がどうなるのか試してみましょう。

java chapters.appenders.ConfigurationTester src/main/java/chapters/appenders/conf/logback-timestamp.xml

ロガーコンテキストを生成した日時を基準時間として使う場合、timeReference属性に"contextBirth"を指定します。

例:タイムスタンプの基準時間にロガーコンテキストを生成した日時を使用する(logback-examples/src/main/java/chapters/appenders/conf/blogback-timestamp-contextBirth.xml

Groovyとして表示
<configuration>
  <timestamp key="bySecond" datePattern="yyyyMMdd'T'HHmmss" 
             timeReference="contextBirth"/>
  ...
</configuration>

RollingFileAppender

RollingFileAppenderFileAppenderを拡張して、ログファイルを切り替えられるようにしたものです。たとえば、RollingFileAppenderでは、log.txtという名前のファイルにログを出力するようにした上で、一定の条件が満たされたら、出力先を別のファイルに変えることができます。

RollingFileAppenderと連携する2つの重要なサブコンポーネントがあります。一つはRollingPolicyです。これはファイルを切り替えるために必要な処理を行います。詳しくは後述します。もう一つはTriggeringPolicyです。これはいつ切り替えるのか決定するものです。こちらも詳しくは後述します。つまり、RollingPolicywhatを、TriggeringPolicywhenを表しているのです。

どんな使い方をするにしても、RollingFileAppenderにはRollingPolicyTriggeringPolicyの両方を設定しなければなりません。RollingPolicyTriggeringPolicyインターフェイスを実装しているので、少なくともRollingPolicyだけは明示的に設定しなければなりません。

RollingFileAppenderで利用出来るプロパティは次のとおりです。

プロパティ名 説明
file String FileAppenderと同じです。
append boolean FileAppenderと同じです。
encoder Encoder OutputStreamAppenderと同じです。
rollingPolicy RollingPolicy このオプションに指定するのは、RollingFileAppenderがファイルを切り替える際に処理を委譲するコンポーネントです。詳細は後述します。
triggeringPolicy TriggeringPolicy このオプションに指定するのは、RollingFileAppenderにファイルを切り替えるタイミングを通知するコンポーネントです。詳細は後述します。
prudent boolean prudentモードでは、FixedWindowRollingPolicyはサポートされていません。

TimeBasedRollingPolicyを使えばRollingFileAppenderでもprudentモードを利用できますが、二つの制限があります。

  1. purudentモードでは、ファイル圧縮オプションをサポートしていませんし、利用することもできません。(他のJVMがログファイルを圧縮している間、書き込むことはできません)
  2. FileAppenderfileプロパティを指定することはできません。空のままにしておかなければなりません。ほとんどのOSでは、他のプロセスが開いているファイルの名前を変えることはできません。
fileプロパティについてはFileAppenderの説明を参照してください。

ローリングポリシーの概要

RollingPolicyは、ファイルの切り替えに伴う移動や名前の変更を行います。

RollingPolicyインターフェイスは次のようなものです。

package ch.qos.logback.core.rolling;  

import ch.qos.logback.core.FileAppender;
import ch.qos.logback.core.spi.LifeCycle;

public interface RollingPolicy extends LifeCycle {

  public void rollover() throws RolloverFailure;
  public String getActiveFileName();
  public CompressionMode getCompressionMode();
  public void setParent(FileAppender appender);
}

rolloverメソッドによって、現在のログファイルのアーカイブ処理を行います。getActiveFileName()メソッドは、今まさにログを出力しているはずのログファイル名を算出するために呼び出されます。getCompressionModeメソッドの名前が示すとおり、RollingPolicyにはファイル圧縮モードを決める役割もあります。RollingPolicyから親となるアペンダーへの参照は、setParent()メソッドによって設定します。

TimeBasedRollingPolicy

TimeBasedRollingPolicyはおそらく一番人気のあるポリシーです。これは、ファイル切り替えポリシーを日付や月などの日時に基いて定義するものです。 TimeBasedRollingPolicyにはファイルを切り替えるタイミングを通知する役割もあります。実際に、 TimeBasedTriggeringPolicyRollingPolicyTriggeringPolicy両方のインターフェイスを実装しています。

TimeBasedRollingPolicyには、必須プロパティのfileNamePatternと、いくつかの任意のプロパティが設定できます。

プロパティ名 説明
fileNamePattern String fileNamePatternプロパティは必須プロパティで、切り替えるときのログファイル名を指定します。指定する値には、ファイル名と%d変換指示子が含まれます。%d変換指示子には、java.text.SimpleDateFormatクラスで定義された日付時刻のパターン文字列を指定します。日付時刻パターンが省略された場合、パターン文字列としてyyyy-MM-ddが使われます。ファイルの切り替え周期は、fileNamePatternに指定された値から推測されます。

TimeBasedRollingPolicyの親アペンダーであるRollingFileAppenderfileプロパティは、指定することもできるし省略することもできます。fileプロパティにファイル名を指定した場合、ログを出力するファイルとアーカイブファイルの場所を別々にすることができます。ログは常にfileプロパティで指定されたファイルに出力されます。 そうすると、有効なログファイルを常に同じ名前にしておくことができます。fileプロパティを省略した場合、有効なログファイル名は、fileNamePatternプロパティに指定した値に基いて定期的に新しい名前になります。この振る舞いを明確に説明する例を以降に示します。

%d{}変換指示子の内側に書かれる日付と時刻のパターン文字列は、java.text.SimpleDateFormatの規約にしたがったものです。fileNamePatternプロパティの中であれば、日付と時刻のパターン文字列の中でも外でも、スラッシュ'/'およびバックスラッシュ'\'はディレクトリの区切り文字として扱われます。

%dトークンはいくつでも指定することができますが、ファイル切り替え周期を推測するために使われるのは最初のものだけです。他のすべてのトークンには、'aux'パラメータを指定して補助であるという印を付けなければなりません

maxHistory int maxHistoryプロパティは、任意プロパティです。削除せずに保持しておくアーカイブファイルの最大数を指定します。たとえば毎月切り替えるつもりでmaxHistoryに6を指定した場合、過去6ヶ月以内のアーカイブファイルは保持され、過去6ヶ月より古いファイルは削除されるでしょう。古くなったファイルだけが削除されるので、logbackが作成したディレクトリは削除されないので注意してください。
cleanHistoryOnStart boolean

trueを指定した場合、アペンダーの開始時に古いアーカイブを削除します。デフォルトではfalseが設定されています。

通常は、ファイルを切り替えるタイミングで古いアーカイブも削除されます。しかし、アプリケーションによっては切り替えのタイミングが来るまで実行し続けないことがあります。そういう短命なアプリケーションでは、古いアーカイブを削除するタイミングが来ない可能性があるのです。cleanHistoryOnStartプロパティにtrueを指定しておけば、アペンダーの開始時に古いアーカイブを削除することができます。

fileNamePatternの値とその効果について具体例を示します。

fileNamePattern 切り替えタイミング 具体例
/wombat/foo.%d 毎日深夜に切り替え。%d変換指示子に日付時刻パターンが省略されているので、デフォルトのyyyy-MM-ddが指定されたことになります。このパターン文字列の場合は、毎日切り替えることになります。

fileプロパティを指定しない場合:2006年11月23日中は/wombat/foo.2006-11-23に出力されます。23日の24時(24日の0時)以降、24日中は/wombat/foo.2006-11-24に出力されます。

fileプロパティに/wombat/foo.txtを指定した場合:2006年11月23日中は/wombat/foo.txtに出力されます。23日24時(24日0時)にfoo.txt/wombat/foo.2006-11-23に変更されます。24日中は、新しく作成された/wombat/foo.txtに出力されます。

/wombat/%d{yyyy/MM}/foo.txt 月初めに切り替え。

fileプロパティを指定しない場合:2006年10月中は、/wombat/2006/10/foo.txtに出力されます。10月31日の24時(つまり11月1日の0時)以降、11月中は/wombat/2006/11/foo.txtに出力されます。

fileプロパティに/wombat/foo.txtを指定した場合:出力先は常に/wombat/foo.txtです。2006年10月中は、/wombat/foo.txtに出力されます。10月31日の24時(つまり11月1日の0時)に/wombat/foo.txt/wombat/2006/10/foo.txtに変更されます。11月中は、新しく作成された/wombat/foo.txtに出力されます。11月30日の24時(つまり12月1日の0時)に/wombat/foo.txt/wombat/2006/11/foo.txtに変更されます。

/wombat/foo.%d{yyyy-ww}.log 週初めに切り替え。週の最初の日は、ロケールに依存するので注意してください。 前の例と同じですが、切り替えが発生するのは週の初日です。
/wombat/foo%d{yyyy-MM-dd_HH}.log 毎時0分に切り替え。 前の例と同じですが、切り替えが発生するのは毎時0分です。
/wombat/foo%d{yyyy-MM-dd_HH-mm}.log 毎分0秒に切り替え。 前の例と同じですが、切り替えが発生するのは毎分0秒です。
/foo/%d{yyyy-MM,aux}/%d.log 毎日深夜に切り替え。年と月からなる名前のフォルダにアーカイブを作成する。 この例では、最初の%dトークンは補助(auxiliary)であるという印が付いています。したがって、日付時刻パターンの省略された二つ目の%dトークンが最初のものとして使われます。したがって、切り替えタイミングは日次(%dのデフォルトパターン)になり、アーカイブするフォルダ名はそのときの年と月になります。例えば、2006年11月中にアーカイブされたファイルはすべて/foo/2006-11というフォルダに置かれます。例えば/foo/2006-11/2006-11-14.logのようになります。

スラッシュまたはバックスラッシュ文字はフォルダ(ディレクトリ)の区切り文字として扱われます。存在しないフォルダは必要に応じて作成されるので、別々のフォルダにログファイルを作るのは簡単です。

TimeBasedRollingPolicyは、FixedWindowRollingPolicyのように自動ファイル圧縮をサポートしています。fileNamePatternオプションの値が.gzまたは.zipで終わっている場合、このフィーチャが有効になります。

fileNamePattern 切り替えタイミング 具体例
/wombat/foo.%d.gz 毎日深夜に切り替え。アーカイブファイルは自動的にgz圧縮する。

fileプロパティを指定しない場合:2009年11月23日中は/wombat/foo.2009-11-23に出力します。23日24時(24日0時)になったら、それまでログを出力していたファイルはgz圧縮されて/wombat/foo.2009-11-23.gzになります。11月24日中は/wombat/folder/foo.2009-11-24に出力されます。

fileプロパティに/wombat/foo.txtを指定した場合:2009年11月23中は/wombat/foo.txtに出力されます。23日24時(24日0時)にgz圧縮されて/wombat/foo.2009-11-23.gzになります。11月24日中は新しく作られた/wombat/foo.txtに出力されます。24日24時(25日0時)に/wombat/foo.txtはgz圧縮されて/wombat/foo.2009-11-24.gzになります。

fileNamePatternには2つの目的があります。1つは、パターン文字列を処理して、logbackに切り替えタイミングを指示することです。もう1つはアーカイブファイル名を決めることです。パターン文字列の形が違っても切り替えタイミングが同じになることがあるので気をつけてください。パターン文字列がyyyy@MMでもyyyy-MMでも、切り替えタイミングは毎月ですが、アーカイブファイル名は異なります。

fileプロパティを設定すると、アクティブなログファイルの場所とアーカイブファイルの場所を別々にすることができます。ログはfileプロパティで指定したファイルに出力されます。つまり、アクティブなログファイルの名前は常に同じになるのです。ただし、fileプロパティを省略した場合、アクティブなログファイルの名前はfileNamePatternに基づいたものになります。fileオプションを設定しなければ、ログファイルの名前変更エラーを割けられます。これは外部からログファイルを参照している間に切り替えようとすると発生するエラーです。存在するときに発生する。

maxHistoryプロパティは、削除せずに保持しておくアーカイブファイルの最大数を指定します。たとえば、切り替えタイミングが毎月で、maxHistoryに6を指定していたら、6ヶ月以内のアーカイブは保持されますが、6ヶ月を超える古いアーカイブは削除されます。古いアーカイブは削除されてしまうので注意しましょう。logbackによって作られたフォルダも必要に応じて削除されてしまいます。

技術的な都合により、ファイルの切り替えは時間ベースではなくロギングイベントの到着ベースで行われます。例えば、2002年3月8日時点でfileNamePatternYYYY-MM-DD(毎日切り替え)が指定されていることにしましょう。すると、8日24時(9日0時)を過ぎてから最初のロギングイベントが到着した際にファイルの切り替えが行われます。しばらくロギングイベントが発生しなかった場合、たとえば0時を過ぎてから23分47秒後にロギングイベントが発生した場合、3月9日0時0分ではなく、0時23分47秒にファイルの切り替えが行われることになります。したがって、ファイル切り替えにはロギングイベントの頻度に応じた遅延が伴います。どれくらいの遅延があろうとも、切り替えアルゴリズムは常に正しく動作します。したがってあらゆるロギングイベントはそれが発生した時刻に基いて適切なファイルに出力されます。

RollingFileAppenderTimeBasedRollingPolicyの設定例を見てください。

例:RollingFileAppenderTimeBasedRollingPolicyの設定(logback-examples/src/main/java/chapters/appenders/conf/logback-RollingTimeBased.xml

Groovyとして表示
<configuration>
  <appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
    <file>logFile.log</file>
    <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
      <!-- daily rollover -->
      <fileNamePattern>logFile.%d{yyyy-MM-dd}.log</fileNamePattern>

      <!-- keep 30 days' worth of history -->
      <maxHistory>30</maxHistory>
    </rollingPolicy>

    <encoder>
      <pattern>%-4relative [%thread] %-5level %logger{35} - %msg%n</pattern>
    </encoder>
  </appender> 

  <root level="DEBUG">
    <appender-ref ref="FILE" />
  </root>
</configuration>

次の例はprudentモードの例です。

例:prudentモードにしたRollingFileAppenderTimeBasedRollingPolicyの設定(logback-examples/src/main/java/chapters/appenders/conf/logback-PrudentTimeBasedRolling.xml

Groovyとして表示
<configuration>
  <appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
    <!-- Support multiple-JVM writing to the same log file -->
    <prudent>true</prudent>
    <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
      <fileNamePattern>logFile.%d{yyyy-MM-dd}.log</fileNamePattern>
      <maxHistory>30</maxHistory> 
    </rollingPolicy>

    <encoder>
      <pattern>%-4relative [%thread] %-5level %logger{35} - %msg%n</pattern>
    </encoder>
  </appender> 

  <root level="DEBUG">
    <appender-ref ref="FILE" />
  </root>
</configuration>

FixedWindowRollingPolicy

FixedWindowRollingPolicyを使うと、固定幅アルゴリズムに従ってファイルの名前を変更することができます。

fileNamePatternオプションには、アーカイブファイル名のパターンを指定します。このオプションは必須、パターン文字列中のどこかに整数トークンの%iを含めなければなりません。

FixedWindowRollingPolicyのプロパティは次のとおりです。

プロパティ名 説明
minIndex int

このオプションには固定幅の添字の下限値を指定します。

maxIndex int

このオプションには固定幅の添字の上限値を指定します。

fileNamePattern String

このオプションにはFixedWindowRollingPolicyがログファイルをアーカイブするファイル名のパターン文字列を指定します。パターン文字列には%iを含めなければなりません。これは、現在の幅に追加する位置の添字になります。

例えば、パターン文字列がMyLogFile%i.logminIndexが1、maxIndexが3だとしたら、アーカイブファイル名はMyLogFile1.logMyLogFile2.logMyLogFile3.logのいずれかになります。

自動ファイル圧縮を有効にする場合もこのプロパティで指定することになります。例えば、fileNamePatternMyLogFile%i.log.zipが指定されている場合、アーカイブされたファイルはzip形式で圧縮されることになります。gz形式も有効です。

固定幅切り替えポリシーでは、指定した窓の大きさによってはたくさんのファイル名を変更しなければなりません。ですので、あまりに大きな値を指定することは極力避けるようにしてください。利用者が指定した窓が大きすぎる場合、現在の実装は窓の大きさを自動的に20にしてします。

固定幅切り替えポリシーの具体的な例を見ていきましょう。それぞれ、minIndex1maxIndex3fileNamePatternfoo%i.logfileプロパティがfoo.logとなっていることを想定しています。;

切り替え回数 現在の出力対象 アーカイブファイル 説明
0 foo.log - まだ切り替えは発生していません。logbackは最初に指定されたファイルにログを出力しています。
1 foo.log foo1.log 切り替えが発生しました。foo.logfoo1.logという名前に変更されました。新しくfoo.logが作成され、現在の出力対象になりました。
2 foo.log foo1.log、foo2.log 切り替えが発生しました。foo1.logfoo2.logという名前に変更され、foo.logfoo1.logという名前に変更されました。新しくfoo.logが作成され、現在の出力対象になりました。
3 foo.log foo1.log、foo2.log、foo3.log 切り替えが発生しました。foo2.logfoo3.logという名前に変更され、foo1.logfoo2.logという名前に変更され、foo.logfoo1.logという名前に変更されました。新しくfoo.logが作成され、現在の出力対象になりました。
4 foo.log foo1.log、foo2.log、foo3.log 以降の切り替えでは、最初にアーカイブfoo3.logを削除します。その他のファイルは、前のステップと同じように、添字を増やした名前に変更されます。以降は、常に1つのログファイル、3つのアーカイブファイルが存在することになります。

RollingFileAppenderFixedWindowRollingPolicyの設定例を次に示します。fileNamePatternオプションに同じ情報が含まれているとはいえ、 Fileオプションは必須なので注意してください。

例:RollingFileAppenderFixedWindowRollingPolicyの設定(logback-examples/src/main/java/chapters/appenders/conf/logback-RollingFixedWindow.xml

Groovyとして表示
<configuration>
  <appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
    <file>test.log</file>

    <rollingPolicy class="ch.qos.logback.core.rolling.FixedWindowRollingPolicy">
      <fileNamePattern>tests.%i.log.zip</fileNamePattern>
      <minIndex>1</minIndex>
      <maxIndex>3</maxIndex>
    </rollingPolicy>

    <triggeringPolicy class="ch.qos.logback.core.rolling.SizeBasedTriggeringPolicy">
      <maxFileSize>5MB</maxFileSize>
    </triggeringPolicy>
    <encoder>
      <pattern>%-4relative [%thread] %-5level %logger{35} - %msg%n</pattern>
    </encoder>
  </appender>
	
  <root level="DEBUG">
    <appender-ref ref="FILE" />
  </root>
</configuration>

日時サイズに基づいたログファイルのアーカイブ

基本的には日付でファイルをアーカイブしたいけど、後続処理ツールに指定できるログファイルにサイズ制限があるので、ログファイルのサイズも制限したいことがあるでしょう。そんなときは、logbackの配布物に含まれるTimeBasedRollingPolicyのサブコンポーネントであるSizeAndTimeBasedFNATPを使うとよいでしょう。FNATP は File Naming And Triggering Policy の略です。

日時とサイズに基づいてログファイルをアーカイブする設定ファイルの例を示します。

例:SizeAndTimeBasedFNATPの設定(logback-examples/src/main/java/chapters/appenders/conf/logback-sizeAndTime.xml

Groovyとして表示
<configuration>
  <appender name="ROLLING" class="ch.qos.logback.core.rolling.RollingFileAppender">
    <file>mylog.txt</file>
    <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
      <!-- rollover daily -->
      <fileNamePattern>mylog-%d{yyyy-MM-dd}.%i.txt</fileNamePattern>
      <timeBasedFileNamingAndTriggeringPolicy
            class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
        <!-- or whenever the file size reaches 100MB -->
        <maxFileSize>100MB</maxFileSize>
      </timeBasedFileNamingAndTriggeringPolicy>
    </rollingPolicy>
    <encoder>
      <pattern>%msg%n</pattern>
    </encoder>
  </appender>


  <root level="DEBUG">
    <appender-ref ref="ROLLING" />
  </root>

</configuration>

"%d"トークンに加えて、"%i"トークンがあることに気づきましたか。次の切り替えタイミングがくるまでの間に、現在のログファイルがmaxFileSizeに指定したサイズを越えたら、添字を加算してアーカイブします。添字は0から始まります。

日時とサイズに基づいたログファイルのアーカイブをしていても、古いアーカイブを削除することができます。残しておくアーカイブの数をmaxHistoryプロパティで指定しなければなりません。アプリケーションが停止してその後再起動した場合でも、正しい添字のファイルにログを出力します。

トリガーポリシーについて

RollingFileAppenderにファイルを切り替えるタイミングを通知するのがTriggeringPolicyです。

TriggeringPolicyインターフェイスには1つだけメソッドが宣言されています。

package ch.qos.logback.core.rolling;

import java.io.File;
import ch.qos.logback.core.spi.LifeCycle;

public interface TriggeringPolicy<E> extends LifeCycle {

  public boolean isTriggeringEvent(final File activeFile, final <E> event);
}

isTriggeringEvent()メソッドには、二つの引数があります。1つは現在有効なファイル、もう1つは処理中のロギングイベントです。これらのパラメータに基づいて、ファイルの切り替えをするべきかどうかを判断します。

最もよく使われているトリガーポリシーはTimeBasedRollingPolicyです。これはローリングポリシーの別名です。他のローリングポリシーの説明と合わせて説明したとおりです。

SizeBasedTriggeringPolicy

SizeBasedTriggeringPolicyは現在有効なファイルのサイズを判断します。指定したサイズより大きくなった場合、RollingFileAppenderに現在のファイルを切り替えるよう通知します。

SizeBasedTriggeringPolicymaxFileSizeパラメータだけを受け付けます。デフォルトは10MBです。

maxFileSizeオプションはバイト、キロバイト、メガバイト、ギガバイト単位で指定できます。それぞれKBMBGBという接尾辞を使うこともできます。たとえば、50000005000KB5MB2GBはすべて正しい値です。最初の三つは同じ値になります。

ファイルサイズが5MBを越えたら切り替える場合の RollingFileAppenderSizeBasedTriggeringPolicyの設定例を見てみましょう。

例:RollingFileAppenderSizeBasedTriggeringPolicyの設定(logback-examples/src/main/java/chapters/appenders/conf/logback-RollingSizeBased.xml

Groovyとして表示
<configuration>
  <appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
    <file>test.log</file>
    <rollingPolicy class="ch.qos.logback.core.rolling.FixedWindowRollingPolicy">
      <fileNamePattern>test.%i.log.zip</fileNamePattern>
      <minIndex>1</minIndex>
      <maxIndex>3</maxIndex>
    </rollingPolicy>

    <triggeringPolicy class="ch.qos.logback.core.rolling.SizeBasedTriggeringPolicy">
      <maxFileSize>5MB</maxFileSize>
    </triggeringPolicy>
    <encoder>
      <pattern>%-4relative [%thread] %-5level %logger{35} - %msg%n</pattern>
    </encoder>
  </appender>
	
  <root level="DEBUG">
    <appender-ref ref="FILE" />
  </root>
</configuration>

logback-classicモジュール

ロギングイベントはlogback-coreモジュールの中ではジェネリック型ですが、logback-classicではILoggingEventのインスタンスになります。logback-classicでは、ILoggingEventのインスタンスの具体的なパイプライン処理を行います。

SocketAppenderとSSLSocketAppender

ここまでに紹介してきたアペンダーは、ローカルリソースだけにログを出力するものでした。対照的に、 SocketAppenderはログをシリアライズしたILoggingEventのインスタンスとしてリモートホストに送信するものとして設計されています。SocketAppenderでは、ロギングイベントを平文で送信します。SSLSocketAppenderでは、暗号化された安全なチャネルを介してロギングイベントを送信します。

シリアライズされたロギングイベントの実際の型は、ILoggingEventインターフェイスを実装したLoggingEventVOです。ロギングイベントがどれだけ心配なのはわかりますが、リモートロギングが盗聴されることはありません。★受信したロギングイベントをデシリアライズしたら、ローカルで生成されたものとまったく同じように扱われます。それぞれのマシンで実行されているSocketAppenderのインスタンスの出力書式がどれも同じになっていれば、中央ログサーバに直接ロギング出力を送りつけることができます。SocketAppenderに割り当てられたレイアウトをリモートサーバに送ることはありません。シリアライズされたイベントを送信するからです。SocketAppenderTransmission Control Protocol(TCP)レイヤーで動作します。TCPレイヤーは、信頼性、順序性、フロー制御を備えた端末間のオクテットストリームによる通信を提供するものです。つまり、リモートサーバがネットワークから到達可能な場合、ロギングイベントは確実に到着します。リモートサーバーがダウンしているか到達不能である場合、ロギングイベントは単純に破棄されてしまいます。リモートサーバが復旧した場合、ロギングイベントの送信は透過的に再開されます。この透過的な再接続は、定期的にサーバへの接続を試みるコネクタースレッドによって行われます。

ロギングイベントはネイティブのTCP実装によって自動的にバッファへ保存されます。つまり、リモートサーバへの通信速度がクライアントでロギングイベントを生成する速度より遅かったとしても、クライアントアプリケーションが通信速度に影響を受けることはない、ということです。通信速度がロギングイベントを生成する速度よりも遅ければ、クライアントは通信速度に合わせた速度でしか処理を進めることができません。極端な具体例ですが、リモートサーバへのネットワーク接続がダウンしている場合、最終的にクライアントはブロックしてしまいます。また、ネットワーク接続は生きててもリモートサーバがダウンしている場合、ロギングイベントはサーバが利用できないために破棄されてしまいますが、クライアントがブロックされることはありません。

あらゆるロガーにSocketAppenderが割り当てられていなかったとしても、コネクタースレッドが生きているかぎりGCに回収されることはありません。コネクタースレッドが死ぬのは、リモートサーバへの接続がダウンしたときだけです。このGCに伴う問題を回避するには、SocketAppenderを明示的にクローズしなければなりません。長時間稼働するアプリケーションでは、非常に多くのSocketAppenderのインスタンスを生成、破棄することになるので、このGCに伴う問題には注意してください。ほとんどのアプリケーションでは無視してもたいして問題になりません。SocketAppenderをホストしているJVMが、SocketAppenderをクローズする前に終了してしまうとしたら、それが明示的に行われるとしても、あるいは、GCの後であろうとも、通信用のパイプの中に残っていた未送信のデータは失われてしまうでしょう。これはWindowsベースのシステムに共通する問題です。データの消失を避けるには、普通ならアプリケーションを終了する前にSocketAppenderclose()メソッドを明示的に呼び出すか、LoggerContextstop()メソッドを介して呼び出すようにするだけで十分です。

リモートサーバはremotehostプロパティとportプロパティで指定します。 SocketAppenderのプロパティを表にまとめました。SSLSocketAppenderにはこれよりも多くのプロパティが追加されています。詳しくはSSLを使用するの章を参照してください。

プロパティ名 説明
includeCallerData boolean

includeCallerDataオプションには真偽値を指定します。trueの場合、リモートホストでログの送信者情報が利用できるようになります。デフォルトでは発信者情報をサーバに送信しません。

port int

リモートサーバのポート番号です。

reconnectionDelay Duration reconnectionDelayオプションには、待ち時間を表す文字列を指定します。たとえば、"10 seconds"と指定した場合、サーバへの接続が失敗するたびに10秒間待ってから、再接続を試みます。このオプションのデフォルト値は30秒​​です。0を指定すると再接続機能が無効になります。サーバへの接続が成功した場合、コネクタースレッドは存在しないはずなので注意してください。
queueSize int

queueSizeプロパティには、受信側に配信するために蓄えておくロギングイベントの数を、非負の整数で指定します。キューサイズが0の場合、イベントの配信は同期になります。キューサイズが0より大きい場合、キューに空きがあれば新しいロギングイベントはキューに入れられるようになります。キューの長さを0以外にすると、一時的なネットワークの遅延によって発生する配信の遅れを排除することができるので、性能が向上します。

eventDelayLimitプロパティのことも参照してください。

eventDelayLimit Duration eventDelayLimitオプションには、"10 seconds"のような待ち時間を表す文字列を指定します。これは、ローカルキューが満杯になった際、蓄積されたロギングイベントを削除する前に待機する時間を表しています。リモートホストがロギングイベントを受信するのが継続的に遅い場合に発生する可能性があります。このオプションのデフォルト値は100ミリ秒です。
remoteHost String リモートサーバのホスト名です。
ssl SSLConfiguration SSLSocketAppenderだけがサポートするプロパティです。アペンダーから利用されるSSL設定を指定します。詳しくはSSLを使用するを参照してください。

サーバ用のオプション

標準的なlogback-classicの配布物には、SocketAppenderあるいはSSLSocketAppenderからロギングイベントを受け取るための2つのサーバ用オプションが含まれています。

SimpleSocketServerの使い方

SimpleSocketServerアプリケーションはコマンドライン引数を2つ取ります。portconfigFileです。 portには待ち受けるポート番号を、configFileにはXML形式の設定ファイルを指定します。

logback-examplesディレクトリに移動してから、次のコマンドを実行するとSimpleSocketServerを開始できます。

java ch.qos.logback.classic.net.SimpleSocketServer 6000 \ src/main/java/chapters/appenders/socket/server1.xml

待ち受けるポート番号として6000、設定ファイルとしてserver1.xmlを指定しています。この設定ファイルでは、ルートロガーにConsoleAppenderRollingFileAppenderを割り当てています。SimpleSocketServerを開始すれば、複数のロギングクライアントからSocketAppenderを使ってロギングイベントを送信できるようになります。このマニュアルでは2つのクライアントを用意しています。chapters.appenders.SocketClient1chapters.appenders.SocketClient2です。どちらもコンソールで利用者が何かキー入力するのを待つようになっています。利用者がキー入力したテキストはログレベルがDEBUGのロギングイベントに包まれてリモートサーバに送信されます。それぞれのクライアントのSocketAppenderの設定は異なります。SocketClient1がプログラムでアペンダーを設定しているのに対して、SocketClient2には設定ファイルが必要です。

SimpleSocketServerをローカルホスト上で実行しているなら、次のようなコマンドで接続することができます。

java chapters.appenders.socket.SocketClient1 localhost 6000

クライアントのコンソールに入力したテキストは、前の手順で開始したSimpleSocketServerのコンソールに1行ずつ出力されます。SimpleSocketServerを一旦停止してから再び開始しても、クライアント側では何もなかったかのように透過的に新しいサーバインスタンスに再接続します。しかし、切断中に発生したロギングイベントは単純に破棄されてしまい、取り返すことができません。

SocketClient1と違って、SocketClient2ではアプリケーション自体でlogbackを設定していません。XML形式の設定ファイルが必要です。設定ファイルclient1.xmlの内容は次のとおりです。SocketAppenderを定義して、ルートロガーに割り当てています。

例:SocketAppenderの設定(logback-examples/src/main/java/chapters/appenders/socket/client1.xml

Groovyとして表示
<configuration>
	  
  <appender name="SOCKET" class="ch.qos.logback.classic.net.SocketAppender">
    <remoteHost>${host}</remoteHost>
    <port>${port}</port>
    <reconnectionDelay>10000</reconnectionDelay>
    <includeCallerData>${includeCallerData}</includeCallerData>
  </appender>

  <root level="DEBUG">
    <appender-ref ref="SOCKET" />
  </root>  

</configuration>

上記の設定スクリプトでは、remoteHost, portincludeCallerDataの値が変数で指定されているのがわかりますか。これらの変数の値は、システムプロパティとして指定することができます。

java -Dhost=localhost -Dport=6000 -DincludeCallerData=false \ chapters.appenders.socket.SocketClient2 src/main/java/chapters/appenders/socket/client1.xml

このコマンドを実行すると、前のSocketClient1と同じ設定になります。

ロギングイベントのシリアライズは非侵入型であることをもう一度アピールさせていただきます。デシリアライズされたロギングイベントは、普通のロギングイベントと全く同じ情報を持っています。したがって、自分が生成したロギングイベントであるかのように扱うことができるのです。ただし、デフォルトではシリアライズされたロギングイベントには送信者情報が含まれていません。この点を説明する例を示します。まずSimpleSocketServerを準備しましょう。

java ch.qos.logback.classic.net.SimpleSocketServer 6000 \ src/main/java/chapters/appenders/socket/server2.xml

設定ファイルserver2.xmlではConsoleAppenderを定義しています。レイアウトには、他の情報と合わせて送信元のファイル名と行番号を指定しています。前のように設定ファイルclient1.xmlを引数としてSocketClient2を起動します。サーバ側のコンソールに出力されたログには、送信元のファイル名と行番号の代わりに2つのクエスチョンマークが出力されているはずです。

2006-11-06 17:37:30,968 DEBUG [Thread-0] [?:?] chapters.appenders.socket.SocketClient2 - Hi

この出力は簡単に変更できます。SocketAppenderが送信者情報を含めるように設定するには、includeCallerDataオプションにtrueを指定するだけです。次のように実行すればよいです。

java -Dhost=localhost -Dport=6000 -DincludeCallerData=true \
  chapters.appenders.socket.SocketClient2 src/main/java/chapters/appenders/socket/client1.xml

デシリアライズされたロギングイベントは、ローカルで生成されたロギングイベントと同じように扱うことができます。つまり、追加で何か処理するためにさらに別のリモートサーバに送信することができるのです。サーバを2つ用意して、最初のサーバがクライアントから受け取ったロギングイベントをトンネルのようにそのまま二つ目のサーバに転送するところを確認するのは、読者の演習課題にしておきます。

SimpleSSLSocketServerの使い方

SimpleSSLSocketServerでは、SimpleSocketServerと同様にコマンドライン引数でportconfigFileを指定します。それに加えて、ロギングサーバのX.509証明書ファイルの場所とパスワードをシステムパラメータで指定しなければなりません。

logback-examplesディレクトリに移動してから、次のコマンドを実行するとSimpleSSLSocketServerを開始できます。

java -Djavax.net.ssl.keyStore=src/main/java/chapters/appenders/socket/ssl/keystore.jks \ -Djavax.net.ssl.keyStorePassword=changeit \ ch.qos.logback.classic.net.SimpleSSLSocketServer 6000 \ src/main/java/chapters/appenders/socket/ssl/server.xml

この例では、テストや検証で使う用のX.509証明書ファイルを指定してSimpleSSLSocketServerを実行しています。本番環境でSimpleSSLSocketServerを使う前に、ロギングサーバを識別するための正式なX.509証明書を手に入れなければなりません 。詳しくはSSLを使用するを参照してください。

サーバの使用する設定ファイルのルート要素にdebug="true"を指定しているので、サーバの開始ログを見ればSSLを設定していることがわかるでしょう。これはセキュリティポリシーが正しく設定されていることを確認するのに便利です。

実行中のSimpleSSLSocketServerには、SSLSocketAppenderを使って接続することができます。アペンダーの設定例は次のとおりです。

例:SSLSocketAppenderの設定(logback-examples/src/main/java/chapters/appenders/socket/ssl/client.xml

Groovyとして表示
<configuration debug="true">
	  
  <appender name="SOCKET" class="ch.qos.logback.classic.net.SSLSocketAppender">
    <remoteHost>${host}</remoteHost>
    <port>${port}</port>
    <reconnectionDelay>10000</reconnectionDelay>
    <ssl>
      <trustStore>
        <location>${truststore}</location>
        <password>${password}</password>
      </trustStore>
    </ssl>
  </appender>

  <root level="DEBUG">
    <appender-ref ref="SOCKET" />
  </root>  

</configuration>

前の例と同じく、remoteHostportの値は変数になっています。また、sslプロパティとネストしているtrustStoreプロパティが増えているので注意してください。locationとpasswordも変数になっています。例として用意したサーバが自己署名証明書を使っているので、これらの設定は必須です。SSLSocketAppenderのSSL設定について、詳しくはSSLを使用するを参照してください。

コマンドラインから、設定ファイルで使用している変数をシステムプロパティとして指定すれば、クライアントアプリケーションを実行することができます。

java -Dhost=localhost -Dport=6000 \ -Dtruststore=file:src/main/java/chapters/appenders/socket/ssl/truststore.jks \ -Dpassword=changeit \ chapters.appenders.socket.SocketClient2 src/main/java/chapters/appenders/socket/ssl/client.xml

前の例と同じく、クライアントアプリケーションのコンソールにメッセージを入力することができます。そうすると、メッセージは(安全な通信路上で)ロギングサーバに送信されます。そして、サーバ側のコンソールにログが出力されます。

コマンドラインで指定したtruststoreプロパティには、信頼できるキーストアのURLを指定することに注意しましょう。SSLを使用するでも説明していますが、URLでクラスパス上のリソースを指定することもできます。

前の例でサーバの起動時にいろいろと出力されているのと同じように、クライアントの設定ファイルのルート要素にdebug="true"と指定しているので、クライアントの起動時にもSSL設定に関する情報が出力されています。ローカルポリシーの適合性の監査に役立つでしょう。

ServerSocketAppenderとSSLServerSocketAppender

前に説明したSocketAppenderコンポーネントとSSL対応版のSSLSocketAppenderは、ネットワークの向こう側のサーバにロギングイベントを配信するためのものです。アプリケーションがリモートロギングサーバに接続するために設計されています。場合によりますが、アプリケーションから特定のリモートロギングサーバへの接続を確立するのが不便だったり不可能だったりすることがあります。そういう場合のためにlogbackではServerSocketAppenderが用意されています。

ServerSocketAppenderは、特定のリモートロギングサーバとの接続を確立する代わりに、リモートクライアントからのTCPソケット接続を待ち受けます。アペンダーに送信されたロギングイベントは、接続しているクライアントに配布されます。接続しているクライアントがいなければ、ロギングイベントはすぐに破棄されます

logback は、普通のServerSocketAppenderだけでなく、SSL対応版のSSLServerSocketAppenderも用意しています。安全な、暗号化された通信路で接続したクライアントにロギングイベントを配布します。さらに、SSL対応版のアペンダーは、完全な相互認証をサポートしています。つまり、認証されたクライアントだけがロギングイベントを受信するためにアペンダーに接続できることが保証されるのです。

通信路上でのロギングイベントの符号化方法は使用しているSocketAppenderに関わらず同一です。ロギングイベントはILoggingEventのインスタンスがシリアライズされたものです。接続を確立する方向だけが逆転しています。SocketAppenderが特定のロギングサーバに対して接続を確立しようとする活性ピアとして振る舞うのに対して、ServerSocketAppenderは受動的にクライアントからの接続を待ち受けます。

ServerSocketAppenderの派生タイプは、logbackの他のレシーバーコンポーネントとは排他的に利用されることを想定しています。コンポーネントタイプに関する詳細についてはレシーバーを参照してください。

ServerSocketAppenderの設定可能なプロパティを表にまとめました。

プロパティ名 説明
address String アペンダーが待ち受けるためのローカルネットワークインターフェイスに割り当てられたIPアドレス。このプロパティが指定されていない場合、アペンダーはすべてのネットワークインターフェイスで待ち受けます。
includeCallerData boolean

trueの場合、リモートホスト側で送信者情報が利用できるようになります。デフォルトでは、送信者情報はクライアントに送信されません。

port int

アペンダーが待ち受けるポート番号。

ssl SSLConfiguration SSLServerSocketAppenderでのみ使用出来るプロパティ。SSLを使用するで説明したように、アペンダーの使用するSSLの設定を指定します。

ServerSocketAppenderの使用例を次に示します。

例:ServerSocketAppenderの基本的な設定(logback-examples/src/main/java/chapters/appenders/socket/server4.xml

<configuration debug="true">
  <appender name="SERVER" 
    class="ch.qos.logback.classic.net.server.ServerSocketAppender">
    <port>${port}</port>
    <includeCallerData>${includeCallerData}</includeCallerData>
  </appender>

  <root level="debug">
    <appender-ref ref="SERVER" />
  </root>  

</configuration>

前の例との違いは、class属性に指定しているのがSocketAppenderではないこと、remoteHostプロパティが無いことだけなのがわかりましたか。このアペンダーはリモートロギングサーバに接続するのではなく、リモートホストからの接続を受動的に待ち受けます。

SSLServerSocketAppenderの設定例は次のとおりです。

例:SSLServerSocketAppenderの基本的な設定(logback-examples/src/main/java/chapters/appenders/socket/server3.xml

<configuration debug="true">
  <appender name="SERVER" 
    class="ch.qos.logback.classic.net.server.SSLServerSocketAppender">
    <port>${port}</port>
    <includeCallerData>${includeCallerData}</includeCallerData>
    <ssl>
      <keyStore>
        <location>${keystore}</location>
        <password>${password}</password>
      </keyStore>
    </ssl>
  </appender>

  <root level="debug">
    <appender-ref ref="SERVER" />
  </root>  

</configuration>

前の例との主な違いは、class属性SSLServerSocketAppenderを指定していることと、ネストしているssl要素があることです。この例ではアペンダー用のX.509証明書が置かれたキーストアが指定されています。SSLの設定について詳細はSSLを使用するを参照してください。

SMTPAppender

SMTPAppenderは、一つ以上の固定サイズのバッファにロギングイベントを蓄積し、利用者が指定したイベントが発生したら適切なバッファを選んで内容をメールで送信します。SMTPによるメール送信は非同期で実行されます。デフォルトでは、ERRORレベルのロギングイベントがトリガとなってメールが送信されます。また、1つのバッファがすべてのロギングイベントから使用されます。

SMTPAppenderの設定可能なプロパティを表にまとめました。

プロパティ名 説明
smtpHost String SMTPサーバのホスト名。これは必須パラメータです。
smtpPort int SMTPサーバーの待ち受けポート番号。デフォルトでは25です。
to String recipient のメールアドレスのパターンを指定します。指定したパターンは、送信されるメールそれぞれに対して、トリガとなったロギングイベントと共に評価されます。複数のrecipientを指定するときは、宛先アドレスをカンマ区切りにします。また、to要素を複数並べることもできます。
from String SMTPAppenderの送信するメールメッセージの originator を一般的なメールアドレス形式で指定します。送信者名を含めたい場合は "Adam Smith &lt;smith@moral.org&gt;" のようにします(XML形式の設定ファイルではブラケットを文字実態参照にします)。そうすると、メールでは "Adam Smith <smith@moral.org>" のようになります。
subject String

メールの件名。PatternLayoutで利用できる全ての値を指定できます。レイアウトについては次の章で説明します。

送信されるメールメッセージの件名には、メール送信をトリガしたロギングイベントを適用したパターンが指定されます。

subjectオプションに指定されたパターンが "Log: %logger - %msg" で、"com.foo.Bar"ロガーが "Hello World" というメッセージのロギングイベントを発生して、このロギングイベントがトリガとなった場合、送信されるメールの件名は "Log: com.foo.Bar - Hello World" になるでしょう。

デフォルトでは、"%logger{20} - %m" が設定されています。

discriminator Discriminator

SMTPAppenderは、弁別器の返す値に基づいてバッファを選択して、発生したロギングイベントを振り分けます。デフォルトの弁別器は常に同じ値を返すので、全てのロギングイベントが常に同じバッファに振り分けられます。

デフォルト以外の弁別器を指定すれば、特定のユーザー、ユーザーセッション、クライアントのIPアドレスに関連するロギングイベントを含むメールだけを送信することもできます。

evaluator IEvaluator

このオプションを指定するには、新しくEventEvaluator要素を宣言します。SMTPAppenderEvaluatorとして使いたいクラスの完全クラス名を、class属性に指定します。

このオプションを指定しなかった場合、SMTPAppenderOnErrorEvaluatorのインスタンスを使用します。これはERROR以上のロギングイベントが発生したらメール送信をトリガします。

logback の配布物には他の評価器もいくつか含まれています。OnMarkerEvaluator(詳しくは後述します)と、より強力な評価器のJaninoEventEvaluatorです。後者については別の章で紹介します。最新バージョンの配布物にはさらに強力な評価器であるGEventEvaluatorが含まれます。

cyclicBufferTracker CyclicBufferTracker

名前が示す通り、CyclicBufferTrackerクラスは循環バッファを追跡します。追跡は、前に説明した弁別器の返すキーに基づいて行われます。

cyclicBufferTracker指定しなかった場合、自動的にCyclicBufferTrackerImplのインスタンスを生成して使用します。デフォルトで256個のロギングイベントを循環バッファに保持します。bufferSizeオプションを指定してサイズを変更することもできます(下記参照)。

username String 平文のパスワード認証をするときに使用するユーザー名。デフォルトではnullです。
password String 平文のパスワード認証をするときに使用するパスワード。デフォルトではnullです。
STARTTLS boolean trueを指定すると、サーバがサポートしているならSSL接続に切り替えるため、STARTTLSコマンドを発行します。接続が確立した時点では暗号化されていないので注意してください。デフォルトではfalseに設定されています。
SSL boolean trueを指定するとサーバにSSL接続をします。デフォルトではfalseです。
charsetEncoding String 送信されるメッセージは指定された文字セットでエンコードされます。デフォルトの文字セットは"UTF-8"ですが、ほとんどの場合十分でしょう。
localhost String SMTPクライアントのホスト名が正しく設定されていない場合(例えば、ホスト名が完全修飾名ではない場合)、SMTPサーバの中にはそういうクライアントから送信された HELO/EHLO コマンドを拒否することがあります。この問題を解決するには、クライアントのホスト名として、localhostに完全修飾名を指定することができます。com.sun.mail.smtpパッケージのドキュメントで説明されている "mail.smtp.localhost" パラメータも参考にしてください。
asynchronousSending boolean メール送信を非同期で行うかどうかを指定します。デフォルトはtrueです。しかし、非同期送信が不適切な場合もあります。たとえば、あなたのアプリケーションで致命的なエラーが発生した際、SMTPAppenderでアラートメールを送信してから終了するとしても、その仕事を任されたスレッドにはメールを送信する時間が残されていないからです。そういう場合は同期的にメール送信をするためfalseを指定しましょう。
includeCallerData boolean デフォルトはfalseです。asynchronousSendingが有効でログに送信者情報を含めたいときはtrueを指定しなければなりません。
sessionViaJNDI boolean javax.mail.Sessionを使用してメールを送信します。デフォルトはfalseなので、SMTPAppenderは利用者の指定した設定に従ってjavax.mail.Sessionインスタンスを構築します。trueを指定するとJNDIからjavax.mail.Sessionインスタンスを取得します。jndiLocationプロパティも参照してください。

注意JNDIからSessionを取得すれば、設定する場所は減るし、同じ情報をあちこちで指定しなくてもよくなります。そうすると、アプリケーションはよりDRYになります。TomcatでJNDIリソースを設定する方法についてはJNDIリソースハウツーを参照してください。JNDIからSessionオブジェクトを取得する場合は、ドキュメントに記載されているように、mail.jaractivation.jarをWebアプリケーションのWEB-INF/libフォルダから取り除いてください。

jndiLocation String JNDIのjavax.mail.Sessionの位置を指定します。デフォルトは"java:comp/env/mail/Session"です。

SMTPAppenderは循環バッファで最新の256個のロギングイベントだけを保持します。バッファが一杯になったら古いロギングイベントを捨てます。したがって、SMTPAppenderからメールで送信するロギングイベントの最大値は256個になります。つまり、アプリケーションコンテキストに妥当なメモリ量を割り当てられるよう、メモリ要件に合わせて制限できるということです。

SMTPAppenderはJavaMail APIにも依存しています。テストされているJavaMail APIのバージョンは1.4です。JavaMail APIにはJavaBeans Activation フレームワークが必要です。JavaMail APIJavaBeans Activation フレームワークはそれぞれのウェブサイトからダウンロードすることができます。以降の例を試す前に、クラスパス上にこれらのjarファイルが配置されていることを確かめてください。

サンプルアプリケーションでは、chapters.appenders.mail.EMailがいくつかのログメッセージを生成したあとで、エラーメッセージを1つ生成しています。このアプリケーションの引数は二つあります。一つ目のパラメータは生成するロギングイベントの数となる整数値です。二つ目のパラメータはlogbackの設定ファイルです。EMailアプリケーションが最後に生成するロギングイベントのログレベルはERRORなので、メール送信をトリガします。

Emailアプリケーションから使う設定ファイルは次のとおりです。

例:SMTPAppenderの設定例(logback-examples/src/main/java/chapters/appenders/mail/mail1.xml

Groovyとして表示
<configuration>	  
  <appender name="EMAIL" class="ch.qos.logback.classic.net.SMTPAppender">
    <smtpHost>ADDRESS-OF-YOUR-SMTP-HOST</smtpHost>
    <to>EMAIL-DESTINATION</to>
    <to>ANOTHER_EMAIL_DESTINATION</to> <!-- additional destinations are possible -->
    <from>SENDER-EMAIL</from>
    <subject>TESTING: %logger{20} - %m</subject>
    <layout class="ch.qos.logback.classic.PatternLayout">
      <pattern>%date %-5level %logger{35} - %message%n</pattern>
    </layout>	    
  </appender>

  <root level="DEBUG">
    <appender-ref ref="EMAIL" />
  </root>  
</configuration>

上記の設定ファイルでchapters.appenders.mail.Emailアプリケーションを動かしてみる前に、smtpHostやtofromへ、あなたの環境に適した値を指定しなければなりません。設定ファイルに正しい値を指定したら次のコマンドを実行しましょう。

java chapters.appenders.mail.EMail 100 src/main/java/chapters/appenders/mail/mail1.xml

指定したrecipientにはPatternLayoutで指定したとおりに書式化された100個のロギングイベントが含まれたメールが届くはずです。Mozilla Thunderbird で受信したメールを開いたところを示します。

結果の電子メール

設定ファイルmail2.xmlでは、smtpHosttofromが変数で指定されています。mail2.xmlの大事な部分を次に示します。

<appender name="EMAIL" class="ch.qos.logback.classic.net.SMTPAppender">
  <smtpHost>${smtpHost}</smtpHost>
  <to>${to}</to>
  <from>${from}</from>
  <layout class="ch.qos.logback.classic.html.HTMLLayout"/>
</appender>

コマンドラインで必要なパラメータを指定します。

java -Dfrom=source@xyz.com -Dto=recipient@xyz.com -DsmtpHost=some_smtp_host \
  chapters.appenders.mail.EMail 10000 src/main/java/chapters/appenders/mail/mail2.xml

値はあなたの環境で利用できるものに置き換えてください。

最後の例では、PatternLayoutHTMLLayoutになっていました。これはログの内容をHTMLのテーブルとして書式化します。列の並びと順番は変更することができます。CSSも変更できます。HTMLLayoutについて詳しくはドキュメントを参照してください。

循環バッファのサイズが256なので、recipientが受け取るメールでは256個のロギングイベントが綺麗にHTMLのテーブルに整形されるようになっています。chapters.appenders.mail.Emailアプリケーションを実行して10000件のロギングイベントを生成しても、送信されるメールには最新の256件しか含まれないことになるので気をつけてください。

第2回のメール

Mozilla ThunderbirdやEudoraやMS Outlookといったメールクライアントは、HTML形式のメールでもCSSをわりと上手く処理します。ですが、勝手にHTMLを平文テキストにダウングレードすることがあります。例えば、Thunderbird でHTMLメールを表示するには、"表示→メッセージ本文→オリジナルのHTML" でオプションを指定しなければなりません。Yahoo!メールはHTMLメールをサポートしており、CSSの対応具合はピカ一です。一方、Gmailは普通のHTMLテーブルはそのまま表示してくれますが、内部CSSによる整形はしてくれません。GmailはインラインCSSをサポートしていますが、インラインCSSを使うとHTMLソースコードが膨れ上がってしまうので、HTMLLayoutではインラインCSSを使用しません。

カスタムバッファサイズ

デフォルトでは、SMTPAppenderから送信されるメッセージには最大で256件のロギングメッセージが含まれています。次の例に示すように、別のバッファサイズを指定することができます。

例: バッファサイズを変更したSMTPAppenderの設定(logback-examples/src/main/java/chapters/appender/mail/customBufferSize.xml

<configuration>   
  <appender name="EMAIL" class="ch.qos.logback.classic.net.SMTPAppender">
    <smtpHost>${smtpHost}</smtpHost>
    <to>${to}</to>
    <from>${from}</from>
    <subject>%logger{20} - %m</subject>
    <layout class="ch.qos.logback.classic.html.HTMLLayout"/>

    <cyclicBufferTracker class="ch.qos.logback.core.spi.CyclicBufferTracker">
      <!-- send just one log entry per email -->
      <bufferSize>1</bufferSize>
    </cyclicBufferTracker>
  </appender>

  <root level="DEBUG">
    <appender-ref ref="EMAIL" />
  </root>  
</configuration>    

トリガイベント

Evaluatorプロパティが指定されなかったら、SMTPAppenderはデフォルトでOnErrorEvaluatorを使用して、ERRORレベルのロギングイベントの発生をトリガとしてメールを送信する。エラーの発生に応じて、メール送信をトリガするのはそれなりに合理的ですが、EventEvaluatorインターフェイスの別の実装を指定すればデフォルトの振る舞いを上書きすることができます。

SMTPAppenderevaluate()メソッドを呼び出すことで、受け取ったロギングイベントを評価器に渡します。そのロギングイベントがメール送信をトリガするものなのか、単に循環バッファに入れておくだけでいいのかを判定するためです。評価器が評価した結果が真なら、メールを送信します。SMTPAppenderの保持する評価器オブジェクトは1つだけです。このオブジェクトは、自身の内部状態を管理することができます。わかりにくいので、CounterBasedEvaluatorクラスのコードで説明します。これはロギングイベントが1024件発生するたびにメール送信をトリガします。

例: 1024件ごとに評価値trueを返すEventEvaluatorインターフェイスの実装(logback-examples/src/main/java/chapters/appenders/mail/CounterBasedEvaluator.java

package chapters.appenders.mail;

import ch.qos.logback.core.boolex.EvaluationException;
import ch.qos.logback.core.boolex.EventEvaluator;
import ch.qos.logback.core.spi.ContextAwareBase;

public class CounterBasedEvaluator extends ContextAwareBase implements EventEvaluator {

  static int LIMIT = 1024;
  int counter = 0;
  String name;

  public boolean evaluate(Object event) throws NullPointerException,
      EvaluationException {
    counter++;

    if (counter == LIMIT) {
      counter = 0;

      return true;
    } else {
      return false;
    }
  }

  public String getName() {
    return name;
  }

  public void setName(String name) {
    this.name = name;
  }
}

このクラスはContextAwareBaseを継承して、EventEvaluatorインターフェイスを実装しているのがわかるでしょうか。こうすれば、利用者はEventEvaluatorとしての中心機能に集中することができるし、基底クラスを使って共通する機能を提供できるようになります。

SMTPAppenderevaluatorオプションを指定するということは、カスタム評価器を使うということです。次の設定ファイルではルートロガーにSMTPAppenderを割り当てています。SMTPAppenderでは、CounterBasedEvaluatorを評価器として指定しています。

例: SMTPAppenderとカスタム評価器とバッファサイズの設定例(logback-examples/src/main/java/chapters/appenders/mail/mail3.xml

Groovyとして表示
<configuration>
  <appender name="EMAIL" class="ch.qos.logback.classic.net.SMTPAppender">
    <evaluator class="chapters.appenders.mail.CounterBasedEvaluator" />
    <smtpHost>${smtpHost}</smtpHost>
    <to>${to}</to>
    <from>${from}</from>
    <subject>%logger{20} - %m</subject>

    <layout class="ch.qos.logback.classic.html.HTMLLayout"/>
  </appender>

  <root level="DEBUG">
    <appender-ref ref="EMAIL" />
  </root>  
</configuration>

マーカーに基づくトリガ

全てのERRORレベルのロギングイベントをトリガとしてメールを送信するデフォルトのポリシーは合理的ではありますが、メールを送信する機会が多すぎると対象になっているユーザのメールボックスを埋め尽くしてしまいます。logback の配布物にはOnMarkerEvaluatorというトリガポリシーも含まれています。これはマーカーに基づいてトリガするものです。原則として、利用者が指定したマーカーのロギングイベントが発生したときだけメール送信がトリガされます。次の例を見ればどういう動き方をするのかはっきりするでしょう。

Marked_EMailのアプリケーションには、ERRORレベルを含むいくつものロギング式があります。そのうちで、一つのロギング式にだけマーカーが指定されています。該当するコードは次のとおりです。

Marker notifyAdmin = MarkerFactory.getMarker("NOTIFY_ADMIN");
logger.error(notifyAdmin,
  "This is a serious an error requiring the admin's attention",
   new Exception("Just testing"));

次の設定ファイルは、マーカーとしてNOTIFY_ADMINあるいはTRANSACTION_FAILUREを指定されたロギングイベントが発生したときだけメール送信をトリガするものです。

例: OnMarkerEvaluatorを指定したSMTPAppenderの設定(logback-examples/src/main/java/chapters/appenders/mail/mailWithMarker.xml

Groovyとして表示
<configuration>
  <appender name="EMAIL" class="ch.qos.logback.classic.net.SMTPAppender">
    <evaluator class="ch.qos.logback.classic.boolex.OnMarkerEvaluator">
      <marker>NOTIFY_ADMIN</marker>
      <!-- you specify add as many markers as you want -->
      <marker>TRANSACTION_FAILURE</marker>
    </evaluator>
    <smtpHost>${smtpHost}</smtpHost>
    <to>${to}</to>
    <from>${from}</from>
    <layout class="ch.qos.logback.classic.html.HTMLLayout"/>
  </appender>

  <root>
    <level value ="debug"/>
    <appender-ref ref="EMAIL" />
  </root>  
</configuration>

次のコマンドを実行してみましょう。

java -Dfrom=source@xyz.com -Dto=recipient@xyz.com -DsmtpHost=some_smtp_host \
  chapters.appenders.mail.Marked_EMail src/main/java/chapters/appenders/mail/mailWithMarker.xml

JaninoEventEvaluatorを使ったマーカーに基づくトリガ

マーカーだけを対象にしたOnMarkerEvaluatorの代わりに、より汎用的なJaninoEventEvaluatorを使うことができますし、それ以上に強力なGEventEvaluatorを使うことも出来ます。たとえば、次の設定ファイルはOnMarkerEvaluatorの代わりにJaninoEventEvaluatorを指定した以外は前の設定ファイルとまったく同じ内容になります。

例: JaninoEventEvaluatorを指定したSMTPAppenderの設定(logback-examples/src/main/java/chapters/appenders/mail/mailWithMarker_Janino.xml)

Groovyとして表示
<configuration>
  <appender name="EMAIL" class="ch.qos.logback.classic.net.SMTPAppender">
    <evaluator class="ch.qos.logback.classic.boolex.JaninoEventEvaluator">
      <expression>
        (marker != null) &&
        (marker.contains("NOTIFY_ADMIN") || marker.contains("TRANSACTION_FAILURE"))
      </expression>
    </evaluator>    
    ... same as above
  </appender>
</configuration>

GEventEvaluatorを使ったマーカーに基づくトリガ

GEventEvaluatorを使っている以外は前の例と同じ内容です。

例:GEventEvaluatorを指定したSMTPAppenderの設定(logback-examples/src/main/java/chapters/appenders/mail/mailWithMarker_GEvent.xml

Groovyとして表示
<configuration>
  <appender name="EMAIL" class="ch.qos.logback.classic.net.SMTPAppender">
    <evaluator class="ch.qos.logback.classic.boolex.GEventEvaluator">
      <expression>
        e.marker?.contains("NOTIFY_ADMIN") || e.marker?.contains("TRANSACTION_FAILURE")
      </expression>
    </evaluator>    
    ... same as above
  </appender>
</configuration>

マーカーを指定されなかったロギングイベントの場合、e.markerはnullになるので注意してください。この例ではGroovyの安全なデリファレンス演算子である .? 演算子を使っています。

認証/ STARTTLS / SSL

SMTPAppenderでは、平文のユーザーパスワード認証だけでなく、STARTTLSとSSLプロトコルの両方をサポートしています。STARTTLSとSSLの違いは、STARTTLSでは接続を確立するときは暗号化されないこと、そして、クライアントがSTARTTLSコマンドを発行してサーバがサポートしている場合はSSL接続に切り替えることです。SSLでは始めから通信が暗号化されます。

GmailにSSLで接続するSMTPAppenderの設定

次の例はGmailにSSLプロトコルで接続するSMTPAppenderの設定です。

例:GmailにSSLで接続するSMTPAppenderの設定(logback-examples/src/main/java/chapters/appenders/mail/gmaliSSL.xml

Groovyとして表示
<configuration>
  <appender name="EMAIL" class="ch.qos.logback.classic.net.SMTPAppender">
    <smtpHost>smtp.gmail.com</smtpHost>
    <smtpPort>465</smtpPort>
    <SSL>true</SSL>
    <username>YOUR_USERNAME@gmail.com</username>
    <password>YOUR_GMAIL_PASSWORD</password>

    <to>EMAIL-DESTINATION</to>
    <to>ANOTHER_EMAIL_DESTINATION</to> <!-- additional destinations are possible -->
    <from>YOUR_USERNAME@gmail.com</from>
    <subject>TESTING: %logger{20} - %m</subject>
    <layout class="ch.qos.logback.classic.PatternLayout">
      <pattern>%date %-5level %logger{35} - %message%n</pattern>
    </layout>	    
  </appender>

  <root level="DEBUG">
    <appender-ref ref="EMAIL" />
  </root>  
</configuration>

STARTTLSでGmailに接続するSMTPAppenderの設定

次の例はGmailにSTARTTLSで接続するSMTPAppenderの設定です。

例:GmailにSTARTTLSで接続するSMTPAppenderの設定(logback-examples/src/main/java/chapters/appenders/mail/gmailSTARTTLS.xml

Groovyとして表示
<configuration>	  
  <appender name="EMAIL" class="ch.qos.logback.classic.net.SMTPAppender">
    <smtpHost>smtp.gmail.com</smtpHost>
    <smtpPort>587</smtpPort>
    <STARTTLS>true</STARTTLS>
    <username>YOUR_USERNAME@gmail.com</username>
    <password>YOUR_GMAIL_xPASSWORD</password>
    
    <to>EMAIL-DESTINATION</to>
    <to>ANOTHER_EMAIL_DESTINATION</to> <!-- additional destinations are possible -->
    <from>YOUR_USERNAME@gmail.com</from>
    <subject>TESTING: %logger{20} - %m</subject>
    <layout class="ch.qos.logback.classic.PatternLayout">
      <pattern>%date %-5level %logger - %message%n</pattern>
    </layout>	    
  </appender>

  <root level="DEBUG">
    <appender-ref ref="EMAIL" />
  </root>  
</configuration>

MDCDiscriminatorを指定したSMTPAppenderの設定

前述したように、SMTPAppenderにデフォルト以外の弁別器を指定すれば、特定のユーザ、ユーザセッション、送信元のIPアドレスを含むロギングイベントが発生した場合にだけメールメッセージを生成することができます。

次の例はMDCBasedDiscriminatorに"req.remoteHost"というキーを指定したものです。値として、架空のWebアプリケーションにアクセスしてきたクライアントのリモートホストのIPアドレスが設定されることを想定しています。WebアプリケーションならMDCInsertingServletFilter使ってMDCに値を設定することができます。

例:MDCDiscriminatorを指定したSMTPAppenderの設定(logback-examples/src/main/java/chapters/appenders/mail/mailWithMDCBasedDiscriminator.xml

Groovyとして表示
<configuration>	  
  <appender name="EMAIL" class="ch.qos.logback.classic.net.SMTPAppender">
    <smtpHost>ADDRESS-OF-YOUR-SMTP-HOST</smtpHost>
    <to>EMAIL-DESTINATION</to>
    <from>SENDER-EMAIL</from>

    <discriminator class="ch.qos.logback.classic.sift.MDCBasedDiscriminator">
      <key>req.remoteHost</key>
      <defaultValue>default</defaultValue>
    </discriminator>

    <subject>${HOSTNAME} -- %X{req.remoteHost} %msg"</subject>
    <layout class="ch.qos.logback.classic.html.HTMLLayout">
      <pattern>%date%level%thread%X{req.remoteHost}%X{req.requestURL}%logger%msg</pattern>
    </layout>
  </appender>

  <root>
    <level level="DEBUG"/>
    <appender-ref ref="EMAIL" />
  </root>  
</configuration>

こうすると、SMTPAppenderの送信するメールそれぞれに固有のIPアドレスが記録されるようになるので、問題解決に役立ちます。

高負荷システムにおけるバッファ管理(★要見直し)

内部的な事情ですが、弁別器が返す値ごとに循環バッファが作成されます。しかし、ほとんどの場合maxNumberOfBuffers(デフォルト値は64)はそのままです。バッファの数がmaxNumberOfBufferesを越えてしまうと、一番最近更新されたバッファはすぐに自動的に破棄されてしまいます。もう一つの予防策として、直近の30分間に更新されなかったバッファはやはり自動的に破棄されてしまいます。

毎分大量のトランザクションを処理するシステムでは、maxNumberOfBuffers(デフォルト値は64)が小さいと送信するメールに含まれるロギングイベントの数がとても少なくなってしまいます。大量のトランザクションが発生する場合、同じトランザクションには1つ以上のバッファが関連付けられてしまいます。弁別器が同じトランザクションには同じ値を返すので、バッファの破棄と生成が繰り返されてしまうからです。高負荷システムであっても、循環バッファの上限はmaxNumberOfBufferesによって制御されてしまうので注意してください。

ヨーヨー効果を避けるため、SMTPAppenderは"FINALIZE_SESSION"というマーカーの指定されたロギングイベントを見つけたら、そのロギングイベントに対して弁別器の返す値に関連付けられたバッファを直ちに開放するようになっています。これにより、トランザクションの終了時に適切にバッファを廃棄できるようになります。そうすれば、maxNumberOfBuffersには、安全のためより大きな値の512や1024を指定することができます。メモリ不足の危険性はありません。

循環バッファを管理するために協調的に機能する、3つの相補的な仕組みがあります。これらの仕組みが、高負荷システムであっても常に有効なバッファが利用できることを保証するのです。

DBAppender

DBAppenderはデータベース上の3つのテーブルに、Javaプログラミング言語に依存しない形式のロギングイベントを登録します。

3つのテーブルとは、logging_eventlogging_event_propertylogging_event_exceptionです。DBAppenderを使う前に事前に用意しておかなければなりません。logack の配布物にテーブルを作成するSQLスクリプトが含まれています。フォルダの場所は logback-classic/src/main/java/ch/qos/logback/classic/db/scriptです。一般的なデータベースそれぞれのスクリプトが用意されています。あなたの使用するデータベース用のスクリプトが無かったとしても、既存のスクリプトを参考にすれば自分で作るのは簡単です。logback の開発メンバーに教えてくれれば、喜んで今後のリリースに含めるようにします。

あなたが使用しているJDBCドライバが、JDBC3.0で導入されたgetGeneratedKeys()メソッドをサポートしているなら、紹介したスクリプトでテーブルを作ること以外に必要な作業はありません。そうはいっても、データベースに対応するSQLDialectを指定しなければなりません。今のところ logback が対応しているSQL方言は、H2、HSQL、MS SQLServer、MySQL、Oracle、PostgreSQL、SQLite、Sybase です。

データベースの種類と、getGeneratedKeys()メソッドの対応状況を表にまとめました。

データベースの種類 テストしたバージョン テストしたJDBCドライバのバージョン {0}getGeneratedKeys(){/0}メソッド の対応状況
logbackがSQL方言を提供しているかどうか
DB2 未テスト 未テスト 不明 無し
H2 1.2.132 - 不明 提供している
HSQL 1.8.0.7 - 未対応 提供している
Microsoft SQL Server 2005 2.0.1008.2(sqljdbc.jar) 対応済み 提供している
MySQL 5.0.22 5.0.8(mysql-connector.jar) 対応済み 提供している
PostgreSQL 8.x 8.4-701.jdbc4 未対応 提供している
Oracle 10g 10.2.0.1(ojdbc14.jar) 対応済み 提供している
SQLLite 3.7.4 - 不明 提供している
Sybase SQLAnywhere 10.0.1 - 不明 提供している

検証したところ、"標準的"なPCでは1つのロギングイベントをデータベースに書き込むのにおよそ10ミリ秒かかるようです。コネクションプールを使えば1ミリ秒くらいは速くなるでしょう。一般的に利用できるほとんどのJDBCドライバではコネクションプールを使うことができるので、ぜひそうしてください。

DBAppenderの設定方法はいろいろありますが、データベースに接続するツールや、データベース自体によって異なります。DBAppenderの設定で重要なのは、ConnectionSourceです。どういうものか簡単に説明しましょう。

DBAppenderがデータベースに接続できたら、ロギングイベントは指定されたデータベースに送信されます。前述したとおり、logbackはロギングイベントを3つのテーブルに格納します。

logging_eventテーブルには次のようなカラムがあります。

カラム名 説明
timestamp big int ロギングイベントが作成された時のタイムスタンプ。
formatted_message text org.slf4j.impl.MessageFormatterで書式化されてからロギングイベントに設定されたメッセージ。引数のオブジェクトはメッセージにくっついています。
logger_name varchar ロギング要求を発行したロガーの名前。
level_string varchar ロギングイベントのレベル。
reference_flag smallint

このフィールドは、ロギングイベントに例外オブジェクトが含まれているか、もしくは、MDCに関連する値が設定されていないかどうかを判定するため、logback が使用します。

値はch.qos.logback.classic.db.DBHelperが算出します。ロギングイベントにMDCあるいはContextプロパティが含まれるバア愛、このフラグ値は1になります。例外オブジェクトが含まれる場合は2になります。両方の要素が含まれている場合は3になります。

caller_filename varchar ロギング要求を発行した場所が含まれるファイル名。
caller_class varchar ロギング要求を発行したクラス名。
caller_method varchar ロギング要求を発行したメソッド名。
caller_line char ロギング要求を発行した場所の行番号。
event_id int データベースが払いだしたロギングイベントのID。

logging_event_propertyには、MDCまたはContextに含まれるキーと値を格納します。次のようなカラムがあります。

カラム名 説明
event_id int データベースが払いだしたロギングイベントのID。
mapped_key varchar MDCのキー値。
mapped_value text MDCの値。

logging_event_exceptionテーブルには、次のようなカラムがあります。

カラム名 説明
event_id int データベースが払いだしたロギングイベントのID。
i smallint 完全なスタックトレースを文字列化した際の各行の添字。
trace_line varchar スタックトレースの文字列中の1行。

DBAppenderを使用した結果をもっと視覚的にわかりやすくお見せしましょう。次に示すのは、DBAppenderがMySQLデータベースを使用した場合のスクリーンショットです。

logging_eventテーブル

ロギングイベントテーブル

logging_event_exceptionテーブル

ロギングイベント例外テーブル

logging_event_propertyテーブル

イベントログ記録Propertyテーブル

ConnectionSource

ConnectionSourceインターフェイスは、logbackがjava.sql.Connectionを取得するためのJDBC接続を透過的に取得するためのプラグイン可能な機能を提供するものです。ConnectionSourceの実装クラスは3つあります。DataSourceConnectionSourceDriverManagerConnectionSourceJNDIConnectionSourceです。

最初に、DriverManagerConnectionSourceを使ってMySQLデータベースに接続する例を見てみましょう。次の設定ファイルを見てください。

例: DBAppenderの設定(logback-examples/src/main/java/chapters/appenders/db/append-toMySQL-with-driveManger.xml

Groovyとして表示
<configuration>

  <appender name="DB" class="ch.qos.logback.classic.db.DBAppender">
    <connectionSource class="ch.qos.logback.core.db.DriverManagerConnectionSource">
      <driverClass>com.mysql.jdbc.Driver</driverClass>
      <url>jdbc:mysql://host_name:3306/datebase_name</url>
      <user>username</user>
      <password>password</password>
    </connectionSource>
  </appender>
  
  <root level="DEBUG" >
    <appender-ref ref="DB" />
  </root>
</configuration>

正しいJDBCドライバクラス名を指定してください。この例ではcom.mysql.jdbc.Driverです。urljdbc:mysql://で始まらなければなりません。

DriverManagerConnectionSourceConnectionSourceの実装クラスで、JDBCの伝統的なやり方(接続用URLに基づくやり方)でデータベース接続を取得します。

このクラスはgetConnection()メソッドが呼ばれるたびに新しいConnectionを生成することに注意してください。コネクションプーリングをサポートしているJDBCドライバーを使うか、コネクションプーリングを利用するConnectionSourceを自分で実装することをおすすめします。Java EE アプリケーションサーバの上で、javax.sql.DataSourceをサポートしたJNDI実装を利用する場合は、後述するJNDIConnectionSourceを参照してください。

DataSourceを使ってデータベースに接続する場合もだいたい同じです。設定ファイルではDataSourceConnectionSourceを指定してください。JDBCの推奨するやり方(javax.sql.DataSourceに基づくやり方)でデータベース接続(Connection)を取得します。

例: DBAppenderの設定(logback-examples/src/main/java/chapters/appenders/db/append-with-datasource.xml

Groovyとして表示
<configuration  debug="true">

  <appender name="DB" class="ch.qos.logback.classic.db.DBAppender">
     <connectionSource class="ch.qos.logback.core.db.DataSourceConnectionSource">
       
       <dataSource class="${dataSourceClass}">
       	 <!-- Joran cannot substitute variables
       	 that are not attribute values. Therefore, we cannot
       	 declare the next parameter like the others. 
       	 -->
         <param name="${url-key:-url}" value="${url_value}"/>
         <serverName>${serverName}</serverName>
         <databaseName>${databaseName}</databaseName>
       </dataSource>
       
       <user>${user}</user>
       <password>${password}</password>
     </connectionSource>
  </appender>

  <root level="INFO">
    <appender-ref ref="DB" />
  </root>  
</configuration>

この設定例ではたくさん変数を使っているので気をつけてください。1つの設定ファイルに接続情報の詳細をまとめておくと、logback と他のフレームワークで設定内容を共有できるので便利です。

JNDIConnectionSource

JNDIConnectionSourceもlogbackの配布物に含まれるConnectionSourceの実装クラスです。名前のとおり、JNDIからjavax.sql.DataSourceを取得し、そこからjava.sql.Connectionを取得します。JNDIConnectionSourceは、Java EE アプリケーションサーバの内部か、アプリケーションサーバのクライアント(アプリケーションサーバのjavax.sql.DataSourceにリモートアクセスできることを想定しています)で使用することを念頭に設計されています。したがって、他にどんな便利機能を提供しているかはともかくとして、少なくともコネクションプーリングを利用することができるはずです。もっと重要なのは、logback.xmlDataSourceを定義しなくてもよくなるので、アプリケーションをよりDRYにできることです。

次の例はTomcat用の設定ファイルから抜粋したものです。PostgreSQL で使うための設定ですが、サポートしているデータベースならどれでも同じように動くはずです。

<Context docBase="/path/to/app.war" path="/myapp">
  ...
  <Resource name="jdbc/logging"
               auth="Container"
               type="javax.sql.DataSource"
               username="..."
               password="..."
               driverClassName="org.postgresql.Driver"
               url="jdbc:postgresql://localhost/..."
               maxActive="8"
               maxIdle="4"/>
  ...
</Context>

Java EE アプリケーションサーバで定義したDataSourceをlogbackの設定ファイルから参照するのは簡単です。

例:JNDIConnectionSourceを使ったDBAppenderの設定(logback-examples/src/main/java/chapters/appenders/db/append-via-jndi.xml

Groovyとして表示
<configuration debug="true">
  <appender name="DB" class="ch.qos.logback.classic.db.DBAppender">
    <connectionSource class="ch.qos.logback.core.db.JNDIConnectionSource">
      <!-- please note the "java:comp/env/" prefix -->
      <jndiLocation>java:comp/env/jdbc/logging</jndiLocation>
    </connectionSource>
  </appender>
  <root level="INFO">
    <appender-ref ref="DB" />
  </root>  
</configuration>

このクラスは引数無しのコンストラクタでjavax.naming.InitialContextのインスタンスを生成するので注意してください。ほとんどの Java EE コンテナで正常に動作します。Java EE コンテナ以外で動かすときは、JNDIプロバイダのドキュメントで説明されたとおりにjndi.propertiesを用意してください。

コネクションプーリング

ロギングイベントはかなり高い頻度で生成されることがあります。ロギングイベントが生成されるのに合わせてデータベースに登録していなければなりません。そのためには、DBAppenderでコネクションプーリングを利用するとよいでしょう。

DBAppenderでコネクションプーリングを利用すると、著しく性能が改善することが実証されています。次の設定ファイルは、コネクションプーリング無しでMySQLデータベースにロギングイベントを登録するものです。

例: コネクションプーリング無しのDBAppenderの設定(logback-examples/src/main/java/chapters/appenders/db/append-toMySQL-with-datasource.xml

Groovyとして表示
<configuration>

  <appender name="DB" class="ch.qos.logback.classic.db.DBAppender">
    <connectionSource class="ch.qos.logback.core.db.DataSourceConnectionSource">
      <dataSource class="com.mysql.jdbc.jdbc2.optional.MysqlDataSource">
        <serverName>${serverName}</serverName>
        <port>${port$</port>
        <databaseName>${dbName}</databaseName>
        <user>${user}</user>
        <password>${pass}</password>
      </dataSource>
    </connectionSource>
  </appender>
    
  <root level="DEBUG">
    <appender-ref ref="DB" />
  </root>
</configuration>

この設定ファイルでは、MySQLデータベースに500件のロギングイベントを送信するのになんと5秒もかかりました。つまり、1件あたり10ミリ秒もかかるのです。大規模なアプリケーションでは使いものにならないことがよくわかると思います。

DBAppenderでコネクションプーリングを利用するには、外部ライブラリが必要です。次の例はc3p0を使っています。c3p0を利用するには、まずダウンロードして、c3p0-VERSION.jarをクラスパス上に配置しなければなりません。

例: DBAppenderでコネクションプーリングを利用する設定(logback-examples/src/main/java/chapters/appenders/db/append-toMySQL-with-datasource-and-pooling.xml

Groovyとして表示
<configuration>

  <appender name="DB" class="ch.qos.logback.classic.db.DBAppender">
    <connectionSource
      class="ch.qos.logback.core.db.DataSourceConnectionSource">
      <dataSource
        class="com.mchange.v2.c3p0.ComboPooledDataSource">
        <driverClass>com.mysql.jdbc.Driver</driverClass>
        <jdbcUrl>jdbc:mysql://${serverName}:${port}/${dbName}</jdbcUrl>
        <user>${user}</user>
        <password>${password}</password>
      </dataSource>
    </connectionSource>
  </appender>

  <root level="DEBUG">
    <appender-ref ref="DB" />
  </root>
</configuration>

この設定ファイルを使った場合、MySQLデータベースに500件のロギングイベントを送信するのにかかった時間は約0.5秒でした。1件あたりの所要時間は1ミリ秒です。つまり、10倍高速化できたことになります。

SyslogAppender

syslogプロトコルは非常に単純なプロトコルです。syslogの送信者は小さなメッセージをsyslogの受信者に送信します。一般的に受信者はsyslogデーモンsyslogサーバと呼ばれます。logbackは、SyslogAppenderを使ってリモートのsyslogデーモンにメッセージを送信することができます。

SyslogAppenderの設定可能なプロパティは次のとおりです。

プロパティ名 説明
syslogHost String syslogサーバのホスト名。
port String syslogサーバーのポート番号。ほとんどの場合はデフォルト値の514を使うでしょう。
facility String

facilityは、メッセージの送信元を区別するためのものです。

facilityオプションには、次のいずれかの文字列を指定しなければなりません。KERN、USER、MAIL、DAEMON、AUTH、SYSLOG、LPR、NEWS、UUCP、CRON、AUTHPRIV、FTP、NTP、AUDIT、ALERT、CLOCK、LOCAL0、LOCAL1、LOCAL2、LOCAL3、LOCAL4、のlocal5、LOCAL6、LOCAL7。大文字小文字は区別されません。

suffixPattern String

suffixPatternオプションには、syslogサーバに送信されるメッセージ中の任意部分の書式を指定します。デフォルトは"[%thread] %logger %msg"です。PatternLayoutで使用できるものは、全てsuffixPatternに指定することができます。

stackTracePattern String

stackTracePatternプロパティには、スタックトレースの各行の先頭に表示する文字列を指定します。デフォルトはタブ文字、つまり "\t"です。PatternLayoutで使用できるものは、全てstackTracePatternに指定することができます。

throwableExcluded boolean trueを指定すると、Throwableに関連付けられているスタックトレースの情報を省略するようになります。デフォルトはfaleseです。ですので、syslogサーバにはスタックトレースの情報が送信されます。

ロギングイベントに対する syslog の severity(重要度)は、ロギングレベルを変換したものになります。DEBUG7INFOは6、WARN4ERROR3に変換されます。

syslog要求の書式には厳密なルールがあるので、SyslogAppenderはレイアウトを使用しません。しかし、suffixPatternオプションを使えば思った通りの内容を表示することができます。

SyslogAppenderの設定例を見てみましょう。

例: SyslogAppenderの設定(logback-examples/src/main/java/chapters/appenders/conf/logback-syslog.xml

Groovyとして表示
<configuration>

  <appender name="SYSLOG" class="ch.qos.logback.classic.net.SyslogAppender">
    <syslogHost>remote_home</syslogHost>
    <facility>AUTH</facility>
    <suffixPattern>[%thread] %logger %msg</suffixPattern>
  </appender>

  <root level="DEBUG">
    <appender-ref ref="SYSLOG" />
  </root>
</configuration>

この設定ファイルを試してみるときは、リモートsyslogデーモンが外部からのリクエストを受け付けるようになっていることを先に確認しておいてください。経験上、デフォルトではsyslogデーモンはネットワークからのリクエストを拒否するようになっていることが多いです。

SiftingAppender

名前のとおり、SiftingAppenderは設定に基づいてロギングイベントを分配(あるいはふるいにかける)ことができます。例えば、SiftingAppenderがユーザーセッションに基づいてロギングイベントを分配するようになっていれば、ユーザーごとにログファイルを生成するようになるでしょう。

プロパティ名 説明
timeout Duration タイムアウト時間を経過してもアクセスされなかったアペンダーは、古くなったものと判断されます。古くなったアペンダーは閉じられて、SiftingAppenderから切り離されます。デフォルトは30分です。
maxAppenderCount integer SiftingAppenderが作成して管理できるアペンダーの最大数を指定します。デフォルトはInteger.MAX_VALUEです。

SiftingAppenderは実行時にアペンダーを作成します。SiftingAppenderの設定で指定された設定テンプレート(sift要素で囲まれた部分です。後で例を示します)に従って作成します。SiftingAppenderには、子アペンダーのライフサイクルを管理する責任があります。たとえば、 SiftingAppenderは古くなったアペンダー自動的に閉じて削除します。タイムアウト時間で指定された時間を過ぎてもアクセスの無かったアペンダーを無効とみなすのです。

ロギングイベントが発生したら、SiftingAppenderは委譲する子アペンダーを選択します。選択条件は弁別器によって実行時に計算されます。利用者はDiscriminatorで選択条件を指定することができます。例を見てみましょう。

SiftExampleアプリケーションは、アプリケーションが起動したことを表すメッセージをロギングします。その後、MDCのキー"uesrid"に"Alice"を設定して、メッセージをロギングします。該当するコードは次のとおりです。

logger.debug("Application started"); MDC.put("userid", "Alice"); logger.debug("Alice says hello");

SiftingAppenderで使う設定ファイルのテンプレートは次のようになっています。

例: SiftingAppenderの設定(logback-examples/src/main/java/chapters/appenders/sift/byUserid.xml

Groovyとして表示
<configuration>

  <appender name="SIFT" class="ch.qos.logback.classic.sift.SiftingAppender">
    <!-- in the absence of the class attribute, it is assumed that the
         desired discriminator type is
         ch.qos.logback.classic.sift.MDCBasedDiscriminator -->
    <discriminator>
      <key>userid</key>
      <defaultValue>unknown</defaultValue>
    </discriminator>
    <sift>
      <appender name="FILE-${userid}" class="ch.qos.logback.core.FileAppender">
        <file>${userid}.log</file>
        <append>false</append>
        <layout class="ch.qos.logback.classic.PatternLayout">
          <pattern>%d [%thread] %level %mdc %logger{35} - %msg%n</pattern>
        </layout>
      </appender>
    </sift>
  </appender>

  <root level="DEBUG">
    <appender-ref ref="SIFT" />
  </root>
</configuration>

discriminator 要素にclass属性がない場合、MDCBasedDiscriminatorが設定されます。弁別器は、keyプロパティで指定されたキーのMDC値を返します。MDC値がnullの場合は、defaultValueプロパティで指定した値を返します。

SiftingAppenderによる子アペンダーの管理機能は独特なものです。上記の例では、SiftingAppenderによって複数のFileAppenderが作られます。それぞれのFileAppenderはキー"userid"でMDCに登録された値によって区別されます。キー"uesrid"のMDC値が新しい値のときは、新しいFileAppenderインスタンスをゼロから作ります。SiftingAppenderは自分が作ったアペンダーを追跡し続けます。30分間使われなかったアペンダーは自動的に閉じられ、破棄されます。

変数の公開出力先リソースが別々な複数のアペンダーのインスタンスを利用するにはどうしたらいいでしょうか?弁別器のキーが変数として公開されるので、上記の例の場合は、アペンダーの設定テンプレート内で"userid"を指定しています。こうすると、子アペンダーに別々の出力先を設定できるようになります。

SiftExampleアプリケーションの引数に設定ファイル"byUserid.xml"を指定して実行すると、"unknown.log" と"Alice.log"という2つのログファイルが作成されます。

ローカルスコープの変数logback 1.0.12 から、ネストされたアペンダーからローカルスコープの変数が利用できるようになりました。また、sift要素変数を定義したり、変数の値を実行時に算出できるようになりました。sift要素の外側で定義された変数の値を組み合わせることもできます。

適切なタイムアウトを設定する

アプリケーションによっては、適切なタイムアウト時間を決めるのが難しいことがあります。タイムアウト時間が短すぎると、ネストされたアペンダーが削除された直後に新しく生成されるようになってしまいます。これはゴミ漁りと呼ばれる事象です。タイムアウト時間が長すぎると、立て続けにアペンダーが生成されるとリソース不足になってしまうかもしれません。maxAppenderCountが小さすぎても同じようにゴミ漁りが発生するかもしれません。

どんな場合でも、ネストされたアペンダーが不要になる箇所を特定するのは簡単でしょう。そういう場所が特定できたら、あるいはほぼ確実にそうだと言える場所が特定できたら、そこに書かれているロギング式にFINALIZE_SESSIONマーカーを指定しましょう。SiftingAppenderはFINALIZE_SESSIONマーカーを指定されたロギングイベントを見つけたら、関連付けられたアペンダーはもう破棄していいいものと判断します。破棄されることになったアペンダーは、数秒間到着するであろうロギングイベントに備えて活性化した後、何も到着しなければそのままクローズします。

import org.slf4j.Logger;
import static ch.qos.logback.classic.ClassicConstants.FINALIZE_SESSION_MARKER;

  void job(String jobId) {
   
    MDC.put("jobId", jobId);
    logger.info("Starting job.");

    ... do whather the job needs to do
    
    // will cause the nested appender reach end-of-life. It will
    // linger for a few seconds.
    logger.info(FINALIZE_SESSION_MARKER, "About to end the job");

    try {
      .. perform clean up
    } catch(Exception e);  
      // This log statement will be handled by the lingering appender. 
      // No new appender will be created.
      logger.error("unexpected error while cleaning up", e);
    }
  }

AsyncAppender

AsyncAppenderはILoggingEventを非同期的にロギングします。単にイベントディスパッチャとして機能するので、意味のある仕事をさせるには他のアペンダーを参照させなければなりません。

80%を越えると消えてしまう AsyncAppenderはロギングイベントをBlockingQueueに蓄積します。AsyncAppenderのワーカースレッドは、キューの先頭からロギングイベントを取り出して、AsyncAppenderに割り当てられたアペンダーに振り分けます。キューの使用量が80%を超えている場合、デフォルトではTRACE、DEBUG、および、INFOレベルのログを捨ててしまいます。これは、ロギングイベントを失ってしまうリスクに見合うだけの性能影響があります。

アプリケーションの停止と再デプロイ アプリケーションを停止、あるいは再デプロイする前に、 AsyncAppenderを停止しなければなりません。全てのロギングイベントをキューから吐き出すためです。そのためには、LoggerContextを停止すればよいです。AsyncAppenderを含む全てのアペンダーを閉じます。

AsyncAppenderの設定可能なプロパティは次のとおりです。

プロパティ名 説明
queueSize int キューの最大容量。デフォルトは256です。
discardingThreshold int デフォルトは20%です。キューの使用量が閾値を越えたら、INFO以下のロギングイベントは破棄、WARN以上のロギングイベントだけをキューイングするようになります。0を指定するとロギングイベントを破棄しないようになります。
includeCallerData boolean 送信者情報の抽出自体に時間がかかることがあります。性能上の兼ね合いにより、デフォルトではロギングイベントをキューに追加するとき、関連する送信者情報を抽出しないようになっています。代わりに、スレッド名などが"軽い"情報だけをMDCにコピーします。trueを指定した場合、完全な送信者情報を含めるようになります。

デフォルトでは、イベントキューに登録可能な件数は256件になっています。キューが一杯になると、新しくロギングイベントを登録しようとしていたアプリケーションスレッドは、ワーカースレッドによってキューのロギングイベントが処理されるまで、ブロックします。キューの使用量が最大使用量を下回るまで、アプリケーションスレッドはロギング要求を発行できなくなります。したがって、ロギングイベントのバッファ使用量が最大値付近で推移しているとき、このアペンダーはほぼ同期的なロギングをすることになります。これは必ずしも悪いことではありません。このアペンダーは、ロギングイベントのバッファ使用量が閾値を超えるほどに増加するまでは、ロギング処理よりもアプリケーションの実行時間が長くなるように設計されています。

アプリケーションのスループットを最大化するためには、アペンダーのイベントキューの大きさを最適化する必要があり、それはいくつもの要因が絡んでいます。次に示す要因のいずれか、あるいは全てが、擬似的な同期的動作をさせる可能性があります。

一般的には、アプリケーションの使用するヒープを減らして、ロギングイベントのキューを大きくすればよいでしょう。

非可逆な振る舞い 上記の議論を踏まえながらブロックする可能性を減らすため、AsyncAppenderはデフォルトでは利用可能なキューが最大値の20%未満になったらTRACE、DEBUG、INFOレベルのロギングイベントを破棄し、WARNとERRORレベルのロギングイベントだけを残すようになっています。そうすると、TRACE、DEBUG、INFOレベルのロギングイベントにコストをかけなくてもよくなるので、非同期処理を妨げず、高い性能を維持することができます。しきい値discardingThresholdを0にすればロギングイベントを破棄しないようにすることもできます。

例: AsyncAppenderの設定(logback-examples/src/main/java/chapters/appenders/conc/logback-async.xml

Groovyとして表示
<configuration>
  <appender name="FILE" class="ch.qos.logback.core.FileAppender">
    <file>myapp.log</file>
    <encoder>
      <pattern>%logger{35} - %msg%n</pattern>
    </encoder>
  </appender>

  <appender name="ASYNC" class="ch.qos.logback.classic.AsyncAppender">
    <appender-ref ref="FILE" />
  </appender>

  <root level="DEBUG">
    <appender-ref ref="ASYNC" />
  </root>
</configuration>

アペンダーを自作する

AppenderBaseを使えば簡単にアペンダーを自作することができます。この基底クラスでは、フィルターやステータスメッセージの管理などほとんどのアペンダーが備えている機能を提供するものです。派生クラスでやることと言えば、append(Object eventObject)メソッドを実装するだけです。

次に示すCountingConsoleAppenderは、限られた数のロギングイベントをコンソールに出力する自作アペンダーです。指定された数のロギングイベントを処理したらシャットダウンします。ロギングイベントを書式化するためにPatternLayoutEncoderを使用します。そして、limitというプロパティを設定することができます。また、append(Object eventObject)メソッド以外にもいくつかメソッドを用意しなければなりません。例を見ればわかりますが、これらのパラメータは logback のさまざまな設定の仕組みによって自動的に設定されます。

例4: CountingConsoleAppender (logback-examples/src/main/java/chapters/appenders/CountingConsoleAppender.java)
package chapters.appenders;

import java.io.IOException;

import ch.qos.logback.classic.encoder.PatternLayoutEncoder;
import ch.qos.logback.classic.spi.ILoggingEvent;
import ch.qos.logback.core.AppenderBase;


public class CountingConsoleAppender extends AppenderBase<ILoggingEvent> {
  static int DEFAULT_LIMIT = 10;
  int counter = 0;
  int limit = DEFAULT_LIMIT;
  
  PatternLayoutEncoder encoder;
  
  public void setLimit(int limit) {
    this.limit = limit;
  }

  public int getLimit() {
    return limit;
  }
  
  @Override
  public void start() {
    if (this.encoder == null) {
      addError("No encoder set for the appender named ["+ name +"].");
      return;
    }
    
    try {
      encoder.init(System.out);
    } catch (IOException e) {
    }
    super.start();
  }

  public void append(ILoggingEvent event) {
    if (counter >= limit) {
      return;
    }
    // output the events as formatted by our layout
    try {
      this.encoder.doEncode(event);
    } catch (IOException e) {
    }

    // prepare for next event
    counter++;
  }

  public PatternLayoutEncoder getEncoder() {
    return encoder;
  }

  public void setEncoder(PatternLayoutEncoder encoder) {
    this.encoder = encoder;
  }
}

start()メソッドでは、PatternLayoutEncoderが設定されているかチェックしています。エンコーダーが設定されなかった場合、アペンダーの起動は失敗となり、エラーメッセージを出力します。

この自作アペンダーの見どころは次の2点です。

CountingConsoleAppenderは他のアペンダーと同じように定義することができます。設定ファイルの例としてlogback-examples/src/main/java/chapters/appenders/countingConsole.xmlも見てください。

Logback Access

logback-classic のほとんどのアペンダーと同じものが logback-access にもあります。基本的には同じように動きます。以降の節ではそれらの使い方を説明します。

SocketAppenderとSSLSocketAppender

SocketAppenderは、シリアライズしたAccessEventをネットワーク上のリモートホストに送信するために設計されています。リモートロギングは、アクセスイベントが重要であろうとなかろうと盗聴できないようになっています。受信したイベントをデシリアライズした後は、自分で生成したロギングイベントと同じように扱うことができます。

SSLSocketAppenderは基本的なSocketAppenderを拡張したもので、Secure Sockets Layer(SSL)を介してリモートホストにログを転送します。

logback-access のSocketAppenderで設定可能なプロパティは、logback-classic のSocketAppenderと同じです。

ServerSocketAppenderとSSLServerSocketAppender

SocketAppenderと同様に、ServerSocketAppenderはシリアライズしたAccessEventをネットワーク上のリモートホストに送信するために設計されています。ServerSocketAppenderはサーバとして動作します。つまり、外部のクライアントがTCP接続してくるのを待ち受けます。アペンダーに渡されたロギングイベントは、接続している全てのクライアントに配布されます。

SSLSocketAppenderは基本的なServerSocketAppenderを拡張したもので、Secure Sockets Layer(SSL)を介してリモートホストにログを転送します。

logback-access のServerSocketAppenderで設定可能なプロパティは、logback-classic のServerSocketAppenderと同じです。

SMTPAppender

logback-access のSMTPAppenderは、logback-classic と同じように動作します。tだ、評価器の設定はだいぶ違います。デフォルトでは、 URLEvaluatorオブジェクトが使用されます。この評価器はロギング要求のURLと突き合わせるURLのリストを持っています。いずれかのURLにマッチするロギング要求が発生したら、SMTPAppenderはメールを送信します。

logback-access のSMTPAppenderの設定例を見てみましょう。

例: SMTPAppenderの設定(logback-examples/src/main/java/chapters/appenders/conf/access/logback-smtp.xml

<appender name="SMTP"
  class="ch.qos.logback.access.net.SMTPAppender">
  <layout class="ch.qos.logback.access.html.HTMLLayout">
    <pattern>%h%l%u%t%r%s%b</pattern>
  </layout>
    
  <Evaluator class="ch.qos.logback.access.net.URLEvaluator">
    <URL>url1.jsp</URL>
    <URL>directory/url2.html</URL>
  </Evaluator>
  <from>sender_email@host.com</from>
  <smtpHost>mail.domain.com</smtpHost>
  <to>recipient_email@host.com</to>
</appender>

このやり方では、特別な処理プロセスの中で重要な手順を通ったときにメールを送信するようなことができます。メールには、トリガとなったWebページにアクセスする一つ前のWebページが含まれます。他の情報を含めることもできます。

DBAppender

DBAppenderはアクセスイベントをデータベースに登録するために使用します。

DBAppenderaccess_eventテーブルとaccess_event_headerテーブルを使います。いずれもDBAppenderを使用する前に準備しなければなりません。テーブルを作成するSQLスクリプトはlogback の配布物に含まれています。logback-access/src/main/java/ch/qos/logback/access/db/scriptディレクトリにあるはずです。一般的なデータベースそれぞれの専用スクリプトがあります。あなたの使用しているデータベース用のスクリプトが無かったとしても、他のスクリプトを参考にすれば簡単に作成できます。あなたの使っているデータベース用のスクリプトが無かった時は、作成したスクリプトをlogbackプロジェクトに送ってください。

access_eventテーブルのカラムは次のとおりです。

カラム名 説明
timestamp big int アクセスイベントの作成時のタイムスタンプ。
requestURI varchar 要求されたURI。
requestURL varchar 要求されたURL。これはリクエストメソッド、リクエストURI、リクエストプロトコルを組み合わせた文字列です。
remoteHost varchar リモートホストの名前。
remoteUser varchar リモートユーザの名前。
remoteAddr varchar リモートIPアドレス。
protocol varchar HTTPまたはHTTPSなどのリクエストプロトコル、。
method varchar リクエストメソッド、GETPOSTになるでしょう。
serverName varchar リクエストを受け付けたサーバーの名前。
event_id int アクセスイベントのデータベース上のID。

access_event_headerテーブルには、リクエストのヘッダ情報が登録されます。次のようなものです。

カラム名 説明
event_id int アクセスイベントのデータベース上のID。
header_key varchar User-Agentなどのリクエストヘッダー名。
header_value varchar Mozilla/5.0 (Windows; U; Windows NT 5.1; fr; rv:1.8.1) Gecko/20061010 Firefox/2.0のようなリクエストヘッダー値。

logback-access のDBAppenderに設定可能なプロパティは、logback-classic のDBAppenderでも利用できます。logback-access の DBAppender でだけ設定可能なプロパティは次のとおりです。

プロパティ名 説明
insertHeaders boolean trueの場合、ロギング要求に含まれる全てのヘッダ情報を登録するようになります。

DBAppenderの設定例を見てください。

例:DBAppenderの設定(logback-examples/src/main/java/chapters/appenders/conf/access/logback-DB.xml

<configuration>

  <appender name="DB" class="ch.qos.logback.access.db.DBAppender">
    <connectionSource class="ch.qos.logback.core.db.DriverManagerConnectionSource">
      <driverClass>com.mysql.jdbc.Driver</driverClass>
      <url>jdbc:mysql://localhost:3306/logbackdb</url>
      <user>logback</user>
      <password>logback</password>
    </connectionSource>
    <insertHeaders>true</insertHeaders>
  </appender>

  <appender-ref ref="DB" />
</configuration>

SiftingAppender

logback-access のSiftingAppenderは、logback-classic のSiftingAppenderとほとんど同じです。主な違いは、デフォルトの弁別器がMDCを参照するものではなく、AccessEventDiscriminatorになっていることです。AccessEventDiscriminatorは、その名のとおり、ネストされたアペンダーを選択するために、AccessEventのフィールドを使用します。指定されたフィールドの値がnullの場合DefaultValueプロパティの値が使用されます。

AccessEventのフィールドとして指定できるのはCOOKIE、REQUEST_ATTRIBUTE、SESSION_ATTRIBUTE、REMOTE_ADDRESS、LOCAL_PORT、REQUEST_URIです。最初の3つのフィールドは単独で指定することはできません。AdditionalKey要素が必要なので注意してください。

設定ファイルの例を示します。

例:SiftingAppenderの設定(logback-examples/src/main/java/chapters/appenders/conf/sift/access-siftingFile.xml

<configuration>
  <appender name="SIFTING" class="ch.qos.logback.access.sift.SiftingAppender">
    <Discriminator class="ch.qos.logback.access.sift.AccessEventDiscriminator">
      <Key>id</Key>
      <FieldName>SESSION_ATTRIBUTE</FieldName>
      <AdditionalKey>username</AdditionalKey>
      <defaultValue>NA</defaultValue>
    </Discriminator>
    <sift>
       <appender name="access-$id-$username" class="ch.qos.logback.core.FileAppender">
        <file>byUser/access-$id-$username.log</file>
        <layout class="ch.qos.logback.access.PatternLayout">
          <pattern>%h %l %u %t \"%r\" %s %b</pattern>
        </layout>
      </appender>
    </sift>
  </appender>
  <appender-ref ref="SIFTING" />
</configuration>

この例ではSiftingAppenderFileAppenderをネストしています。キー要素の値"id"は、ネストされたFileAppenderで変数として使用することができます。デフォルトの弁別器でもあるAccessEventDiscriminatorは AdditionalKey 要素で指定した"username" をAccessEventのセッション属性から探します。指定した属性が無かったら、defaultValue要素の値"NA"を使用します。セッション属性の"username"には、アプリケーションのログインユーザー名が含まれていることを想定しています。ユーザーごとのアクセスログは、byUserフォルダの下にユーザー名入りのファイル名で出力されます。