SHOEISHA iD

※旧SEメンバーシップ会員の方は、同じ登録情報(メールアドレス&パスワード)でログインいただけます

連載記事

CodeZine編集部では、現場で活躍するデベロッパーをスターにするためのカンファレンス「Developers Summit」や、エンジニアの生きざまをブーストするためのイベント「Developers Boost」など、さまざまなカンファレンスを企画・運営しています。

CodeZine BOOKS(コードジン・ブックス)は、CodeZineの連載からカットアップした、開発現場の課題解決に役立つ書籍シリーズです。

書籍に関する記事を見る

'); googletag.cmd.push(function() { googletag.pubads().addEventListener('slotRenderEnded', function(e) { var ad_id = e.slot.getSlotElementId(); if (ad_id == 'div-gpt-ad-1659428980688-0') { var ad = $('#'+ad_id).find('iframe'); if ($(ad).width() == 728) { var ww = $(window).width(); ww = ww*0.90; var style = document.createElement("style"); document.head.appendChild( style ); var sheet = style.sheet; sheet.insertRule( "#div-gpt-ad-1659428980688-0 iframe {-moz-transform: scale("+ww/728+","+ww/728+");-moz-transform-origin: 0 0;-webkit-transform: scale("+ww/728+","+ww/728+");-webkit-transform-origin: 0 0;-o-transform: scale("+ww/728+","+ww/728+");-o-transform-origin: 0 0;-ms-transform: scale("+ww/728+","+ww/728+");-ms-transform-origin: 0 0;}", 0 ); sheet.insertRule( "#div-gpt-ad-1659428980688-0 div{ height:"+(90*ww/728)+"px;width:"+728+"px;}", 0 ); } else { if ($(window).width() < 340) { var ww = $(window).width(); ww = ww*0.875; var style = document.createElement("style"); document.head.appendChild( style ); var sheet = style.sheet; sheet.insertRule( "#div-gpt-ad-1659428980688-0 iframe {-moz-transform: scale("+ww/320+","+ww/320+");-moz-transform-origin: 0 0;-webkit-transform: scale("+ww/320+","+ww/320+");-webkit-transform-origin: 0 0;-o-transform: scale("+ww/320+","+ww/320+");-o-transform-origin: 0 0;-ms-transform: scale("+ww/320+","+ww/320+");-ms-transform-origin: 0 0;}", 0 ); sheet.insertRule( "#div-gpt-ad-1659428980688-0 div{ height:"+(180*ww/320)+"px;width:"+320+"px;}", 0 ); } } } }); }); } else { document.write('
'); document.write('
'); }
特集記事

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

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


  • X ポスト
  • このエントリーをはてなブックマークに追加

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

  • X ポスト
  • このエントリーをはてなブックマークに追加

はじめに

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

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

 本稿ではRollingFileAppenderとDailyRollingFileAppenderの機能を組み合わせた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 SpecificationのDailyRollingFileAppenderの項を参照ください。

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

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

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

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

CompositeRollingFileAppenderの実装

パッケージ

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

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

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

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

プロパティ

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

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

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

設定ファイル例

 以下のサンプルは、最大ファイルサイズ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に手を加える必要がある場合に役に立つはずです。

会員登録無料すると、続きをお読みいただけます

新規会員登録無料のご案内

  • ・全ての過去記事が閲覧できます
  • ・会員限定メルマガを受信できます

メールバックナンバー

次のページ
CompositeRollingFileAppenderのコンパイル

この記事は参考になりましたか?

  • X ポスト
  • このエントリーをはてなブックマークに追加
特集記事連載記事一覧

もっと読む

この記事の著者

小川 浩司(オガワ ヒロシ)

Eclipseに関する情報やLog4j Q&A等を書いています。

※プロフィールは、執筆時点、または直近の記事の寄稿時点での内容です

この記事は参考になりましたか?

この記事をシェア

  • X ポスト
  • このエントリーをはてなブックマークに追加
CodeZine(コードジン)
https://codezine.jp/article/detail/524 2006/12/08 20:04
" ); }

おすすめ

アクセスランキング

  1. 1
    管理職の24.1%、今後管理職を「続けたくない」と回答。理由は「責任やストレス」が最多に NEW
  2. 2
    フロントエンドの定番ライブラリ「React」バージョン19の新機能を紹介──アクションによる非同期処理の進化
  3. 3
    IPA、DXの先進事例を素早く効率的に検索できるWebサイト「デジタル事例データベース」を公開
  4. 4
    ランサーズ、「2024年必要とされたスキルランキング」を公開。「Lancers」上のデータを集計
  5. 5
    いいエンジニアになるための2つのポイント ──元Google技術者・石原氏が説く「シリコンバレー流ソフトウェア開発術」
  1. 6
    Apple、2024 App Store Awardsの受賞者発表 NEW
  2. 7
    Python初心者向けチュートリアル「Python Boot Camp in 鹿児島 3rd」、2025年1月11日に開催 NEW
  3. 8
    「CentOS Stream 10」発表、Linuxカーネル 6.12 LTSを搭載
  4. 9
    「CUDA」 ~マンガでプログラミング用語解説
  5. 10
    楽天、日本語に最適化したAIモデル「Rakuten AI 2.0」と「Rakuten AI 2.0 mini」を発表 NEW

アクセスランキング

  1. 1
    管理職の24.1%、今後管理職を「続けたくない」と回答。理由は「責任やストレス」が最多に NEW
  2. 2
    フロントエンドの定番ライブラリ「React」バージョン19の新機能を紹介──アクションによる非同期処理の進化
  3. 3
    IPA、DXの先進事例を素早く効率的に検索できるWebサイト「デジタル事例データベース」を公開
  4. 4
    ランサーズ、「2024年必要とされたスキルランキング」を公開。「Lancers」上のデータを集計
  5. 5
    いいエンジニアになるための2つのポイント ──元Google技術者・石原氏が説く「シリコンバレー流ソフトウェア開発術」
  6. 6
    Apple、2024 App Store Awardsの受賞者発表 NEW
  7. 7
    Python初心者向けチュートリアル「Python Boot Camp in 鹿児島 3rd」、2025年1月11日に開催 NEW
  8. 8
    「CentOS Stream 10」発表、Linuxカーネル 6.12 LTSを搭載
  9. 9
    「CUDA」 ~マンガでプログラミング用語解説
  10. 10
    楽天、日本語に最適化したAIモデル「Rakuten AI 2.0」と「Rakuten AI 2.0 mini」を発表 NEW
  1. 1
    いいエンジニアになるための2つのポイント ──元Google技術者・石原氏が説く「シリコンバレー流ソフトウェア開発術」
  2. 2
    「CUDA」 ~マンガでプログラミング用語解説
  3. 3
    ITエンジニア本大賞2025、投票締切直前! みんなで選んだ歴代の大賞本を振り返って一挙紹介
  4. 4
    デスクトップアプリを開発しよう! 「Rust」と「Tauri 2.0」の基本情報と環境整備の仕方を解説
  5. 5
    今後生成AIとどう向き合うべきなのか? 現場のエンジニアと研究者が最新研究事例から語り合う
  6. 6
    2024年12月に開催される注目のITエンジニア向けカンファレンス5選
  7. 7
    日本在住の英語を話すソフトウェア開発者、年収の中央値は950万円に
  8. 8
    Vue.js3.4~3.5の新機能をまとめて紹介! 新しいAPIやSSRの改善
  9. 9
    VSCodeをドキュメント作成に活用――テキストエディタ、Markdownエディタの設定と拡張機能を解説
  10. 10
    2024年の提示年収が高いプログラミング言語は? paiza調査によるランキングが発表

イベント

CodeZine編集部では、現場で活躍するデベロッパーをスターにするためのカンファレンス「Developers Summit」や、エンジニアの生きざまをブーストするためのイベント「Developers Boost」など、さまざまなカンファレンスを企画・運営しています。

新規会員登録無料のご案内

メールバックナンバー

アクセスランキング

  1. 1
    管理職の24.1%、今後管理職を「続けたくない」と回答。理由は「責任やストレス」が最多に NEW
  2. 2
    フロントエンドの定番ライブラリ「React」バージョン19の新機能を紹介──アクションによる非同期処理の進化
  3. 3
    IPA、DXの先進事例を素早く効率的に検索できるWebサイト「デジタル事例データベース」を公開
  4. 4
    ランサーズ、「2024年必要とされたスキルランキング」を公開。「Lancers」上のデータを集計
  5. 5
    いいエンジニアになるための2つのポイント ──元Google技術者・石原氏が説く「シリコンバレー流ソフトウェア開発術」
  1. 6
    Apple、2024 App Store Awardsの受賞者発表 NEW
  2. 7
    Python初心者向けチュートリアル「Python Boot Camp in 鹿児島 3rd」、2025年1月11日に開催 NEW
  3. 8
    「CentOS Stream 10」発表、Linuxカーネル 6.12 LTSを搭載
  4. 9
    「CUDA」 ~マンガでプログラミング用語解説
  5. 10
    楽天、日本語に最適化したAIモデル「Rakuten AI 2.0」と「Rakuten AI 2.0 mini」を発表 NEW

アクセスランキング

  1. 1
    管理職の24.1%、今後管理職を「続けたくない」と回答。理由は「責任やストレス」が最多に NEW
  2. 2
    フロントエンドの定番ライブラリ「React」バージョン19の新機能を紹介──アクションによる非同期処理の進化
  3. 3
    IPA、DXの先進事例を素早く効率的に検索できるWebサイト「デジタル事例データベース」を公開
  4. 4
    ランサーズ、「2024年必要とされたスキルランキング」を公開。「Lancers」上のデータを集計
  5. 5
    いいエンジニアになるための2つのポイント ──元Google技術者・石原氏が説く「シリコンバレー流ソフトウェア開発術」
  6. 6
    Apple、2024 App Store Awardsの受賞者発表 NEW
  7. 7
    Python初心者向けチュートリアル「Python Boot Camp in 鹿児島 3rd」、2025年1月11日に開催 NEW
  8. 8
    「CentOS Stream 10」発表、Linuxカーネル 6.12 LTSを搭載
  9. 9
    「CUDA」 ~マンガでプログラミング用語解説
  10. 10
    楽天、日本語に最適化したAIモデル「Rakuten AI 2.0」と「Rakuten AI 2.0 mini」を発表 NEW
  1. 1
    いいエンジニアになるための2つのポイント ──元Google技術者・石原氏が説く「シリコンバレー流ソフトウェア開発術」
  2. 2
    「CUDA」 ~マンガでプログラミング用語解説
  3. 3
    ITエンジニア本大賞2025、投票締切直前! みんなで選んだ歴代の大賞本を振り返って一挙紹介
  4. 4
    デスクトップアプリを開発しよう! 「Rust」と「Tauri 2.0」の基本情報と環境整備の仕方を解説
  5. 5
    今後生成AIとどう向き合うべきなのか? 現場のエンジニアと研究者が最新研究事例から語り合う
  6. 6
    2024年12月に開催される注目のITエンジニア向けカンファレンス5選
  7. 7
    日本在住の英語を話すソフトウェア開発者、年収の中央値は950万円に
  8. 8
    Vue.js3.4~3.5の新機能をまとめて紹介! 新しいAPIやSSRの改善
  9. 9
    VSCodeをドキュメント作成に活用――テキストエディタ、Markdownエディタの設定と拡張機能を解説
  10. 10
    2024年の提示年収が高いプログラミング言語は? paiza調査によるランキングが発表