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
のサブクラスとして作るのが良い。まずはこんな設定ができればよいだろう。
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 の実装例を示す。コンストラクタやエラー処理は適当に書いたので、必要に応じて修正されたい。
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);
}
}