Shoeisha Technology Media

CodeZine(コードジン)

記事種別から探す

サイズと日付でローテートするLog4jのAppender作成

開発現場の要求に即した独自Appenderの作成

  • LINEで送る
  • このエントリーをはてなブックマークに追加
2006/10/03 00:00

Log4jは、ファイルサイズまたは日付でログファイルのローテーションを行うAppenderを提供しています。しかし、ファイルサイズと日付の両方でローテートするAppenderは提供されていないため、両方の機能を同時に利用することはできません。本稿では、これらを両立するAppenderを作成します。

目次

はじめに

 Apache Logging Services Projectが提供するLog4jは、ファイルサイズによってログファイルのローテーションを行うRollingFileAppenderや日付でローテーションを行うDailyRollingFileAppenderを提供しています。

 しかし、ファイルサイズと日付の両方でローテートするAppenderは提供されていないため、両方の機能を同時に利用することはできません。また、DailyRollingFileAppenderを利用する場合は、バックアップログファイル数を設定できないため、Disk Fullへの対策を検討する必要があります。

 本稿ではRollingFileAppenderDailyRollingFileAppenderの機能を組み合わせたAppenderを作成する方法を紹介し、その利用方法について示します。

対象読者

 Javaプログラミングの経験があり、Log4jを使ったことがある方を対象とします。Log4jの基本については、『Log4Jコネクションプーリング対応JDBCAppenderでパフォーマンスを向上する』や、筆者が管理するLog4j Q&Aを参照ください。

必要な環境

 なお、筆者は以下の環境で動作確認をしています。

  • JDK 5.0 Update 4
  • Log4j 1.2.13

RollingFileAppenderとDailyRollingFileAppender

 Log4jはさまざまなAppenderを用意していますが、操作ログやエラーログの記録のために、以下のAppenderが広く使われていると思います。

  • RollingFileAppender
  • DailyRollingFileAppender

RollingFileAppender

 ファイルサイズでログファイルのローテーションを行うAppenderです。ログファイルのサイズが最大ファイルサイズを超えたときに、ログファイルのローテーションが発生します。バックアップログファイル数が最大バックアップログファイル数を超えたら最も古いログファイルが削除されます。

プロパティ

 org.apache.log4j.RollingFileAppenderでは以下のプロパティが用意されています。

RollingFileAppenderクラスのプロパティ
プロパティ名設定例説明
MaxFileSize10MB最大ファイルサイズ。このファイルサイズを超えるとログファイルのローテーションが発生する
MaxBackupIndex10最大バックアップログファイル数。バックアップログファイル数がこの値を超えると、最も古いログファイルが削除される

DailyRollingFileAppender

 日付でログファイルのローテーションを行うAppenderです。DatePatternで示される日付が変わったときに、ログファイルのローテーションが発生します。

プロパティ

 org.apache.log4j.DailyRollingFileAppenderでは、以下のプロパティが用意されています。

DailyRollingFileAppenderクラスのプロパティ
プロパティ名設定例説明
DatePattern'.'yyyy-MM-ddログファイルのローリングのスケジュールを指定します。月、週、日、時、分などでローリングさせることが可能です。詳細な設定方法はLog4j API SpecificationDailyRollingFileAppenderの項を参照ください。

両Appenderの関係と新規Appenderの位置付け

 クラス名からはRollingFileAppenderDailyRollingFileAppenderに親子関係があるように思えますが、実際はそうではありません。そのため、DailyRollingFileAppenderRollingFileAppenderのプロパティを使うことはできません。Log4jでは「サイズと日付でローテートするAppender」は提供されていないのです。

 そこで、両者の機能を併せ持つCompositeRollingFileAppenderクラスを新規に作成します。既存のAppenderと同様にCompositeRollingFileAppenderFileAppenderを継承します。以下にUML図を示します。

各Appenderの位置付け
各Appenderの位置付け

CompositeRollingFileAppenderの実装

パッケージ

 org.apache.log4jへのパッケージアクセスが必要になるため、本クラスのパッケージをorg.apache.log4jとします。

ローテーション発生の検出

 Log4jではログイベントが発生すると、org.apache.log4j.WriterAppender#subAppend(LoggingEvent)メソッドが呼び出され、ログ出力が行われます。RollingFileAppenderDailyRollingFileAppenderでは、このメソッド内でローテーションの有無を検出しています。そこで、CompositeRollingFileAppendersubAppendメソッドは、両者のローテーションの検出を組み合わせた形で実装します。

subAppendメソッド
protected void subAppend(LoggingEvent event) {
    // 日付ベースのローテーションが発生するかどうかチェックし、
    // 発生する場合はローテーションを行う。
    long n = System.currentTimeMillis();
    if (n >= nextCheck) {
        now.setTime(n);
        nextCheck = rc.getNextCheckMillis(now);
        try {
            rollOverTime();
        } catch (IOException ioe) {
            LogLog.error("rollOver() failed.", ioe);
        }
    }

    // サイズベースのローテーションが発生するかどうかチェックし、
    // 発生する場合はローテーションを行う
    if ((fileName != null) && ((CountingQuietWriter) qw).getCount()
        >= maxFileSize) {
        rollOverSize();
    }

    super.subAppend(event);
}

サイズベースのローテーション

 サイズベースのローテーションでは、まずバックアップログファイル数がMaxBackupIndex以上であるかチェックし、そうであれば最も古いログファイルを削除します。

 RollingFileAppenderではファイルサイズによるローテーションのみ行われるため、ログファイルのバックアップインデックスから最も古いログファイルを特定できますが、CompositeRollingFileAppenderではローテーションの方法が2種類(サイズベースと日付ベース)あるため、別の判定方法が必要になります。本稿の実装ではログファイルのタイムスタンプから最も古いログファイルを判断しています。

 次に、バックアップログファイルの名称を変更します。具体的にはファイル名に付けられたインデックスを増やすようにします。この時、既に日付でローテートされたログファイルはローテーションの対象にしません。

 以下のソースコードは、サイズベースのローテーションを行う処理の抜粋です。詳細はサンプルファイルに含まれるソースコードを参照下さい。

サイズベースのローテーションを行う
public void rollOverSize() {

    if (maxBackupIndex > 0) {
        rotateLogFilesBy(BY_SIZE);
    }

    try {
        this.setFile(fileName, false, bufferedIO, bufferSize);
    } catch (IOException e) {
        LogLog.error("setFile(" + fileName + ", false) call failed.", e);
    }
}

private void rotateLogFilesBy(int mode) {

    // 日付が付けられていないログファイルを検索する
    List notDailyLogs = new ArrayList();
    File[] allLogs = getAllLogFiles();
    for (int i = 0; i < allLogs.length; i++) {
        if (!isDailyLotatedLog(allLogs[i])) {
            notDailyLogs.add(allLogs[i]);
        }
    }
    int notDailyLogNum = notDailyLogs.size();

    // モードに従ってローテーションを行う
    if (mode == BY_SIZE) {
        File file = null;
        File target = null;

        // 最も古いログファイルを削除する
        if (getBackupLogFileNum() >= maxBackupIndex) {
            deleteOldestFile();
        }

        // Map {(maxBackupIndex - 1), ..., 2, 1} to
        // {maxBackupIndex, ..., 3, 2}
        for (int i = notDailyLogNum - 1; i >= 1; i--) {
            file = new File(fileName + "." + i);
            if (file.exists()) {
                target = new File(fileName + '.' + (i + 1));
                LogLog.debug("Renaming file " + file + " to " + target);
                file.renameTo(target);
            }
        }

        // Rename fileName to fileName.1
        target = new File(fileName + "." + 1);

        this.closeFile(); // keep windows happy.

        file = new File(fileName);
        LogLog.debug("Renaming file " + file + " to " + target);
        file.renameTo(target);
    } else if (mode == BY_DATE) {
        ・・・
    }
}

日付ベースのローテーション

 日付ベースのローテーションでも、まずバックアップログファイル数がMaxBackupIndex以上であるかチェックし、そうであれば最も古いログファイルを削除します。

 次に、バックアップログファイル名に日付を付与します。日付が付けられたバックアップログファイルは以後リネームされることはありません。以下のソースコードは、日付ベースのローテーションを行う処理の抜粋です。

日付ベースのローテーションを行う
public void rollOverTime() throws IOException {

    /* Compute filename, but only if datePattern is specified */
    if (datePattern == null) {
        errorHandler.error("Missing DatePattern option in rollOver().");
        return;
    }

    String datedFilename = fileName + sdf.format(now);
    // It is too early to roll over because we are still within the
    // bounds of the current interval. Rollover will occur once the
    // next interval is reached.
    if (scheduledFilename.equals(datedFilename)) {
        return;
    }

    rotateLogFilesBy(BY_DATE);

    try {
        // This will also close the file. This is OK since multiple
        // close operations are safe.
        this.setFile(fileName, false, this.bufferedIO, this.bufferSize);
    } catch (IOException e) {
        errorHandler.error("setFile(" + fileName + ", false) call failed.");
    }
    scheduledFilename = datedFilename;
}

private void rotateLogFilesBy(int mode) {

    // 日付が付けられていないログファイルを検索する
    List notDailyLogs = new ArrayList();
    File[] allLogs = getAllLogFiles();
    for (int i = 0; i < allLogs.length; i++) {
        if (!isDailyLotatedLog(allLogs[i])) {
            notDailyLogs.add(allLogs[i]);
        }
    }
    int notDailyLogNum = notDailyLogs.size();

    // モードに従ってローテーションを行う
    if (mode == BY_SIZE) {
        ・・・
    } else if (mode == BY_DATE) {
        // close current file, and rename it to datedFilename
        this.closeFile();

        // 最も古いログファイルを削除する
        if (getBackupLogFileNum() >= maxBackupIndex) {
            deleteOldestFile();
        }

        // ログファイル名に日付を付与する
        for (int i = 1; i <= notDailyLogNum - 1; i++) {
            String from = fileName + '.' + i;
            String to = scheduledFilename + '.' + i;
            renameFile(from, to);
        }

        renameFile(fileName, scheduledFilename);
    }
}
本稿のソースコードとコメントについて
 RollingFileAppenderDailyRollingFileAppenderを組み合わせるという性質上、それぞれのクラスのソースコードをベースにしています。上記のソースコードにある英語のコメントは元のソースコードに付けられていたものです。
 またLog4jのバージョンによってRollingFileAppenderDailyRollingFileAppenderのソースコードも異なるため、それらを組み合わせる上記実装もベースとなるLog4jのバージョンによって異なります。本稿で紹介するコードはLog4j 1.2.13をベースにしています。

プロパティ

 org.apache.log4j.CompositeRollingFileAppenderで用意したプロパティは次のとおりです。

CompositeRollingFileAppenderクラスのプロパティ
プロパティ名設定例デフォルト値説明
MaxFileSize10MBjava.lang.Long#MAX_VALUE最大ファイルサイズ。日付でのみローテーションさせたい場合は、このオプションを省略してください
MaxBackupIndex101最大バックアップログファイル数
DatePattern'.'yyyy-MM-dd-HH'.'yyyy-MM-ddログファイルのローテーションのスケジュール

 それぞれのプロパティの詳細は、RollingFileAppenderDailyRollingFileAppenderと同じです。

設定ファイル例

 以下のサンプルは、最大ファイルサイズ10MB、最大バックアップログファイル数10で、日ごとにローテーションを行う「log4j.xml」の設定例です。

log4j.xml サンプル
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE log4j:configuration SYSTEM "log4j.dtd">

<log4j:configuration xmlns:log4j="http://jakarta.apache.org/log4j/">

  <appender name="FILE" 
            class="org.apache.log4j.CompositeRollingFileAppender">
    <param name="File"   value="log/sample.log" />
    <param name="MaxFileSize" value="10MB" />
    <param name="MaxBackupIndex" value="10" />
    <param name="datePattern" value="'.'yyyy-MM-dd" />

    <layout class="org.apache.log4j.PatternLayout">
      <param name="ConversionPattern" value="%d %t %-5p %c{2} - %m%n"/>
    </layout>
  </appender>

  <logger name="jp.co.okisoft.esc.log4j">
    <level value="debug" />
    <appender-ref ref="FILE" />
  </logger>

</log4j:configuration>

 ファイルサイズは無視して、とにかく日付だけでローテーションさせたい場合は、上記の設定においてMaxFileSizeプロパティを省略します。DailyRollingFileAppenderでは最大バックアップログファイル数を指定することができませんが、CompositeRollingFileAppenderを利用することで、バックアップ数制限付きの日付ベースローテーションを実現できます。

サンプルアプリケーション

 今回作成したCompositeRollingFileAppenderの動作確認のために用いるサンプルアプリケーションを以下に示します。whileループ内でINFOレベルのログを出力し続けるだけのシンプルなコードです。

サンプルアプリケーション
package jp.co.okisoft.esc.log4j;

import org.apache.log4j.Logger;

public class RollingSample {
    private static Logger logger = Logger.getLogger(RollingSample.class);

    public static void main(String[] args) throws Exception {
        while (true) {
            Thread.sleep(100);
            logger.info("test");
        }
    }
}
Log4j自体のデバッグログを有効にする
 デバッグ情報としてコンソールに出力するログをLog4jで有効にしたい場合は、起動時に以下のシステムプロパティを与えてください。
-Dlog4j.debug=true
 Log4jの内部ログを参照できます。Log4jに手を加える必要がある場合に役に立つはずです。

  • LINEで送る
  • このエントリーをはてなブックマークに追加

著者プロフィール

All contents copyright © 2005-2017 Shoeisha Co., Ltd. All rights reserved. ver.1.5