logbackを使う

今まではJavaでログ出力といえば、log4jだったが、最近ではlogbackも使いやすくなっている。
[追記]
logbackはintra-martで採用されたりしているので既にかなりメジャーであると言える。
http://www.intra-mart.jp/apilist/v70/doclet/im_commons/jp/co/intra_mart/common/platform/log/rolling/ExtendedTimeBasedRollingPolicy.html
[追記-終]

logbackでログをファイル出力する場合は下記のAppenderクラスを使う。

詳細はリンクを参照。

logbackではログローテーションを実現する際に、RollingPolicyクラスとTriggeringPolicyクラスというものを使うことになる。
簡単にいうと、RollingPolicyはローテ時のバックアップファイル名についての規定を提供。
TriggeringPolicyはログローテーションのローテタイミングについての規定と機能を提供、という感じ。
ローテーションを実現するにはRollingPolicyとTriggeringPolicyの2つを定義する必要がありそう、とここでイメージできる。

主なRollingPolicyクラスは以下

日次ローテ、毎時ローテなどを提供する。
TimeBasedRollingPolicyはTriggeringPolicyとRollingPolicyが同居する。(つまり定義一つでOK)

バックアップファイル名をlog.1,log.2の形で作成できる。
別にSizeBasedTriggeringPolicyを定義する必要がある。

日時ローテとサイズローテを組み合わせたい場合に使う。
使いたい場合は、TimeBasedRollingPolicyの中に組み込む形で定義するようだ。
詳細はリンクを参照。(サンプル有り)

主なTriggeringPolicyクラスは以下

言わずもがなサイズローテーション用TriggeringPolicy。
FixedWindowRollingPolicyと併用。

logbacklog4jより良い点として、"複数JVMによる同じログファイルへの書き込みをサポートしている"というところだろう。
FileAppenderに"prudent"(boolean)という設定項目がある。
デフォルトではfalseだが、trueにすることで、複数プロセスで同じログファイルへの出力を保証するモードを利用できる。
log4jでは複数プロセス(マルチJVM)で同じログファイルへ出力するとログが消えるなどしてしまう。

ちなみにprudent=trueの場合は通常ログ書き出しメソッド(OutputStreamAppender#writeOut)でなく、
独自のFileAppender#safeWriteメソッドを使う。
safeWriteはこれだ。

final private void safeWrite(E event) throws IOException {
  ResilientFileOutputStream resilientFOS = (ResilientFileOutputStream) getOutputStream();
  FileChannel fileChannel = resilientFOS.getChannel();
  if (fileChannel == null) {
      return;
  }
  FileLock fileLock = null;
  try {
      fileLock = fileChannel.lock();
      long position = fileChannel.position();
      long size = fileChannel.size();
      if (size != position) {
          fileChannel.position(size);
      }
      super.writeOut(event);
  } finally {
      if (fileLock != null) {
          fileLock.release();
      }
  }
}

つまりFileChannelで排他ロックを取りながらログ出力をする。
処理速度にいちゃもんが付きそうだが、正確にログが取りたい場合は使いたいところだ。


また、logbackslf4jというライブラリとセットで利用する。
ほとんどslf4jを使うことを意識する必要はないが、introductionを参照して理解しておくと良い。
また日本語サイトでは、ここもslf4jのロガー実装切替アルゴリズムが理解できる。

logbackで日次ログローテーションする

logbackで日次ローテーションをする時のサンプル。

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

実はlogbackダウンロードした中に入っている。
logback-0.9.29/logback-examples/src/main/java/chapters/appenders/conf/logback-RollingTimeBased.xml
他にもサイズローテなどのサンプルが入っているので開いてみると幸せになれる。

いくつか設定方法を紹介。(公式にも掲載されている)

logFile.%d{yyyy-MM-dd}.log
%d{}のフォーマットを書き換えることで、ローテーションタイミングが決まる。
フォーマットはjava.text.SimpleDateFormat参照。

  • 日次ローテ

logFile.%d.log
%dは"2006-11-24"形式。

  • 月次ローテ

logFile.%d{yyyy-MM}.log
例えば、"%d{yyyy/MM}/logFile.log"とすると、月次ディレクトリが作られる。
# 2011/08/logFile.log

  • 週次ローテ

logFile.%d{yyyy-ww}.log
localeに依存する。

  • 毎時ローテ

logFile.%d{yyyy-MM-dd_HH}.log

  • 毎分ローテ!

logFile.%d{yyyy-MM-dd_HH-mm}.log

また、ログファイルをアーカイブする際にzipさせることも可能。
※ただし注意事項があるようなので公式説明をよく読まれたし。

  • GZIPで圧縮する

logFile.%d.gz
ファイル名の最後に ".gz" もしくは ".zip"をつけるだけ。

FileAppender 動的にログファイル名を変える

通常logbackでFileAppenderやRollingFileAppenderを使う際に
ファイル名はfile項目へ設定する必要がある。
しかしこの場合ログファイル名は固定となる。

動的にログファイル名を変更したい場合について紹介したいと思う。
ここではRollingFileAppenderで日次ローテーション(TimeBasedRollingPolicy)する場合。

単純に言うと、 RollingFileAppenderTimeBasedRollingPolicy
継承したクラスを作成すれば良い。
そしてコンストラクタでファイル名を設定してあげればいいだけ。

MyRollingFileAppenderクラス

package log.logback;

import ch.qos.logback.classic.spi.ILoggingEvent;
import ch.qos.logback.core.rolling.RollingFileAppender;
import ch.qos.logback.core.rolling.TriggeringPolicy;

public class MyRollingFileAppender extends RollingFileAppender<TriggeringPolicy<ILoggingEvent>>{

    public MyRollingFileAppender(){
        super();
        setFile(getFileName());
    }

    private String getFileName(){
        // ここでファイル名を作成する.
        return "log/testFile.log";
    }
}

MyTimeBasedRollingPolicyクラス

package log.logback;

import ch.qos.logback.classic.spi.ILoggingEvent;
import ch.qos.logback.core.rolling.TimeBasedRollingPolicy;

public class MyTimeBasedRollingPolicy extends TimeBasedRollingPolicy<ILoggingEvent> {

    public MyTimeBasedRollingPolicy(){
        super();
        setFileNamePattern(getPattern());
    }

    private String getPattern(){
        // ここでファイル名パターンを作成する.
        return "log/testFile.log.%d{yyyy-MM-dd}.log";
    }
}

※packageは任意。

コンストラクタでRollingFileAppenderではsetFile(String)、TimeBasedRollingPolicyではsetFileNamePattern(String)で設定する。

これら独自クラスをlogback.xmlで使用するようにすればOK。

<configuration>

   <appender name="FILE" class="log.logback.MyRollingFileAppender">
   <!--
   <file>logFile.log</file>
   -->
   <rollingPolicy class="log.logback.MyTimeBasedRollingPolicy">
     <!-- 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>

ちなみにmaxHistoryもTimeBasedRollingPolicy#setMaxHistory(int)が存在するので、動的に変更可能。
# version 0.9.29ではコンストラクタは1つだけだが、今後コンストラクタが増えた場合はその分同じコンストラクタを作成したほうが無難。

また、この記事ではgetFileName()やgetPattern()メソッドを作成したが、もし別の名前で作成する場合は、
意図しない@Overrideをしないよう注意したい。