Posts Tagged ‘Log4j’

Log4jで同一内容を複数の出力先に出力させる

 システムを運用しているとログが重要な意味を持ってくる。

 カットオーバーしたばかりの頃はシステムも安定しおらず、初期不良とでも言うべき障害に見舞われることもある。そこで、安定するまではデバッグログやトレースログを出力しておき、障害の数が落ち着いてきたらそれらの出力を止めるというケースが考えられる。ただ、デバッグログやトレースログを出力している場合、ログファイルが膨らんでしまい、監視ツールなどにログファイルを食わせている場合には負荷の面で不安が出てくる。

 そういう時、FATALレベルやERRORレベルのログだけを別ファイルにミラーリング出来たら便利だ。そのエラーログはサイズも小さく、監視ツールに食わせても安心だ。そしてエラーを検出したらデバッグログやトレースログを調査するという運用が可能になる。

 では、そのようなログを出力するにはどうすればよいだろうか。

 エラーログ専用のロガーを定義して、プログラムからそれぞれログ記録メソッドを呼び出すという方法も無くはないが、コードとしては冗長で無意味である。そうではなく、以下の例のように1つのロガーに対して複数のアペンダを設定すればよい。ERRORレベル以上のログがエラーログにも出力されるようになる。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# RootLogger
log4j.rootLogger=TRACE,A,B

# Logger
log4j.logger.myLogger=TRACE,A,B

# Appender A
log4j.appender.A=org.apache.log4j.FileAppender
log4j.appender.A.Append=true
log4j.appender.A.threshold=DEBUG
log4j.appender.A.File=C:/temp/normal.log
log4j.appender.A.layout=org.apache.log4j.PatternLayout
log4j.appender.A.layout.ConversionPattern=%d{yyyy/MM/dd HH:mm:ss} %m%n

# Appender B
log4j.appender.B=org.apache.log4j.FileAppender
log4j.appender.B.Append=true
log4j.appender.B.threshold=ERROR
log4j.appender.B.File=C:/temp/error.log
log4j.appender.B.layout=org.apache.log4j.PatternLayout
log4j.appender.B.layout.ConversionPattern=%d{yyyy/MM/dd HH:mm:ss} %m%n

 尚、複数指定するアペンダは、同じアペンダクラスでなくても、例えばファイルとコンソールに出力するといった指定も可能である。

ログローテートせずに日付別のログ出力

Javaでログ出力ライブラリというと標準の java.util.logging よりは Apache log4j を利用する機会が多い。

さてLog4jでファイルにログを出力し続けると、ファイルのサイズが大きくなってしまうので日付ごとにログローテーションさせたいということがある。そこで org.apache.log4j.DailyRollingFileAppender を採用したところ、ローテート(ロールオーバ)されるタイミングが日付が変わった時点ではなく、日付が変わってから初めてログが出力される瞬間であることが判明した。これは通常は問題ないのだが、人によっては気になることだし顧客から指摘を受けることもあるだろう。実際ネット上では困っている人がそれなりにいるようだ。

これに対処するには日付が変わったタイミングで強引にログを出力させるやり方や、バッチ処理でログファイル名を変更するといったこともできないではないが、個人的にはあまり好きではない。

PHPでシステムを構築する際には個人的によく採用するパターンが、予めファイル名に日付を含めておくというものだ。例えば今日の日付が2012年4月15日だとしたら、 error_20120415.log といった具合にだ。実装としてはログ出力の際にシステム日付からファイル名を生成する。そうしておけば日付が変われば自動的に翌日の日付の入ったログファイルが生成される。

これをLog4jでやろうとしたがそういった機能は標準では備わっていないようだ。そこでAppenderを自作することにした。クラス名は仮に DailyFileAppender とでもしておこう。このクラスは org.apache.log4j.FileAppender のサブクラスとして作るのが良い。まずはこんな設定ができればよいだろう。

1
2
3
4
5
log4j.appender.A=DailyFileAppender
log4j.appender.A.Append=true
log4j.appender.A.FilePattern='C:/temp/error_'yyyyMMdd'.log'
log4j.appender.A.layout=org.apache.log4j.PatternLayout
log4j.appender.A.layout.ConversionPattern=%d{yyyy/MM/dd HH:mm:ss} %m%n

Dailyといってもファイル名の構成に java.text.SimpleDateFormat を利用するだけなので、月単位でも週単位でも、逆に時単位でも分単位でも自在に設定可能だ。日頃 FileAppender を使っている人は、 File というプロパティが FilePattern に換わったものと捉えればよいだろう。そこに SimpleDateFormat に渡すパターンを定義する。当然 SimpleDateFormat が解釈できる書式でなければならない。

以下、DailyFileAppender の実装例を示す。コンストラクタやエラー処理は適当に書いたので、必要に応じて修正されたい。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
public class DailyFileAppender extends FileAppender {
   
    // コンストラクタ
    public DailyFileAppender() {}
   
    public DailyFileAppender(Layout layout, String pattern) throws IOException {
        this(layout, pattern, true);
    }
   
    public DailyFileAppender(Layout layout, String pattern, boolean append) throws IOException {
        this.layout = layout;
        this.filePattern = pattern;
        String filename = this.generateFileName(pattern);
        this.setFile(filename, append, false, bufferSize);
    }
   
    public DailyFileAppender(Layout layout, String pattern, boolean append, boolean bufferedIO, int bufferSize) throws IOException {
        this.layout = layout;
        this.filePattern = pattern;
        String filename = this.generateFileName(pattern);
        this.setFile(filename, append, bufferedIO, bufferSize);
    }
   
    // ファイル名のパターン
    protected String        filePattern = null;
    private SimpleDateFormat    sdf;
   
    // プロパティのgetter/setter
    public String getFilePattern() {
        return filePattern;
    }
    public void setFilePattern(String filePattern) {
        this.filePattern = filePattern.trim();
    }
   
    // システム日付からファイル名を生成
    private String generateFileName(String pattern) {
        String fileName;
        sdf = new SimpleDateFormat(pattern);
        fileName = sdf.format(new Date());
        return fileName;
    }
   
    // プロパティのオプションを反映させる
    @Override
    public void activateOptions() {
        if (this.filePattern != null) {
            try {
                this.fileName = this.generateFileName(this.filePattern);
                setFile(this.fileName, this.fileAppend, this.bufferedIO,
                    this.bufferSize);
            }
            catch (java.io.IOException e) {
                errorHandler.error("setFile(" + fileName + "," + fileAppend
                        + ") call failed.", e, ErrorCode.FILE_OPEN_FAILURE);
            }
        }
        else {
            LogLog.error("File option not set for appender [" + name + "].");
        }
    }
   
    // 実際にログを出力する
    @Override
    protected void subAppend(LoggingEvent event) {
        String fileName = this.generateFileName(this.filePattern);
        if (!fileName.equals(this.fileName)) {
            try {
                this.setFile(fileName, this.getAppend(), this.bufferedIO,
                    bufferSize);
            }
            catch (IOException ioe) {
                LogLog.error("Failed open the file [" + fileName + "].", ioe);
            }
        }
        super.subAppend(event);
    }
}
アーカイブ