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"をつけるだけ。

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のロガー実装切替アルゴリズムが理解できる。

オレオレJUnit フレームワーク

JUnitでオレオレフレームワーク(というほどのものじゃないが)を書きました。
こうしておくとちょっとだけ楽になります。

public class MyTestFrame extends TestCase{

    private long start;
    private long end;

    protected static int count = 0;

    @Override
    public void setUp(){
        System.out.println();
        start = System.currentTimeMillis();
    }
    @Override
    public void tearDown(){
        end = System.currentTimeMillis();
        System.out.println(getMethodName(count++)+": "+(end-start)+"ms.");
    }

    private static String methodNames[] = null;
    private String getMethodName(int num){
        if(methodNames == null || methodNames.length == 0){
            List<String> tmpNames = new ArrayList<String>();
            Method ms[] = this.getClass().getMethods();
            String mn = null;
            for(Method m : ms){
                mn = m.getName();
                if(mn.startsWith("test")) tmpNames.add(mn);
            }
            methodNames = new String[tmpNames.size()];
            methodNames = tmpNames.toArray(methodNames);
        }
        return methodNames.length <= num ? "null" : methodNames[num];
    }
}

こんな感じで標準出力されるのでコマンドベースで実行したりする場合などには役に立ちます。

.
testSimple: 769ms.
.
testBuffered: 206ms.
.
testStream: 307ms.
.
testBufferedStream: 236ms.
.
testApacheIO: 282ms.

ファイル入出力 Nullの挿入

基本的にNULLを処理しようとするとNullPointerExceptionが発生してしまいます。
そのためNULLにならないように常に考慮しなければなりません。

今まで紹介したファイル入出力でNULLデータを書きこむとどうなるか、テストを行ってみました。

    public void testNullWrite(){
        String data = null;
        try{
            SimpleFileInOut.write(makeOutFileName(),data);
            count++;
        }catch(NullPointerException e){
            System.out.println("nullpo SimpleFileInOut");
        }

        try{
            BufferedFileInOut.write(makeOutFileName(),data);
            count++;
        }catch(NullPointerException e){
            System.out.println("nullpo BufferedFileInOut");
        }

        try{
            StreamFileInOut.write(makeOutFileName(),data);
            count++;
        }catch(NullPointerException e){
            System.out.println("nullpo StreamFileInOut");
        }

        try{
            BufferedStreamFileInOut.write(makeOutFileName(),data);
            count++;
        }catch(NullPointerException e){
            System.out.println("nullpo BufferedStreamFileInOut");
        }

        try{
            FileUtils.write(new File(makeOutFileName()), data);
        }catch(NullPointerException e){
            System.out.println("nullpo FileUtils");
        }catch (IOException e) {
            e.printStackTrace();
        }
    }

処理結果

nullpo SimpleFileInOut
nullpo BufferedFileInOut
nullpo StreamFileInOut
nullpo BufferedStreamFileInOut
testNullWrite: 3ms.

つまり、ApacheIOのFileUtilsでのみNULLデータを書きこんでもぬるぽが起きない。
ちなみに0Byteのファイルが作成されます。

ファイル入出力 ApacheCommons IOの例

Apache CommonsのIOライブラリを利用したファイル入出力を紹介します。
簡単です。

private String inFileName = "resources/file/dummy.dat";
private String outFileName = "tmp/dummy.dat";
public void testApacheIO() throws IOException{
    String data = FileUtils.readFileToString(new File(inFileName));
    FileUtils.write(new File(outFileName), data);
}

ファイル入出力 処理速度比較_1

今まで記事にしたファイル入出力メソッドの処理速度の比較表。
ファイル入出力 シンプルな例 - clash_m45の開発日記
ファイル入出力 Bufferedな例 - clash_m45の開発日記
ファイル入出力 Streamクラスでの例 - clash_m45の開発日記

ファイル入出力速度計測

クラス 処理時間(ms) 順位
FileReader,FileWriter 235 3*1
BufferedReader,BufferedWriter 205 1
FileInputStream,FileOutputStream 299 4
BufferedInputStream,BufferedOutputStream 235 2

このことから標準Java APIでファイル入出力最速は、
BufferedReader,BufferedWriterとなります。

個人的にはBufferedInputStream,BufferedOutputStreamと
BufferedReader,BufferedWriterで差が出たことが発見でした。


今度はApache Commonsの IOライブラリを使った例を紹介します。
早くなるのかな?遅くなるのかな?
乞うご期待!(なんつって)

*1:ほぼ2位同等速度ですが、たまに遅いことがあるため3位としました。