Java
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); } } |