java.util.loggingの闇
Javaの1.4からjava.util.logging
(以下JULと表記)というロギングパッケージが標準で使えるようになって、ログ出力のためにlog4jなどのサードパーティライブラリをいちいち導入したりする必要がなくなりみんな幸せになりました。
と言いたいところですこいつが超不便なAPIをしていてとてもとてもとっっっても使い辛い。ふざけんな。
まずさらっと使ってみましょう。Java 7です。
Logger.getGlobal().info("log") Logger.getLogger("foo").info("log")
出力はこうなります。
Jan 15, 2015 5:11:41 PM JUL main INFO: log Jan 15, 2015 5:11:41 PM JUL main INFO: log
はい、キモイですね。軽くつっこむと
- なんで2行なんだよ
- 日時AM/PM表記かよ、ミリ秒なしかよ
- ロガー名どこだよ
JULの酷さ談義で一晩酒が飲めそうですが、JULの特徴を簡潔にまとめます。
- ログ出力設定ファイルはシステムプロパティで指定
-Djava.util.logging.config.file=logging.properties
- 引数はURLは受け付けずファイルパスのみ
- 間違ってると一切ログ出さない
- デフォルトフォーマッタは
java.util.logging.SimpleFormatter
- ログメッセージのフォーマットは {0}, {1} のような
java.text.MessageFormat
形式 - logメソッドで可変長引数の関連でパラメータとExceptionを同時に受け取れないのでLogRecordを使う必要があり、シンプルなログ出力に数行を要する
- ログレベル名微妙にダサイ感 (ERRORではなくSEVERE。シビアのスペルを覚える機会なんて滅多になさそう)
ログ設定ファイルはこんな感じ。最後の一行の呪文があるとフォーマットは多少マシになります。フォーマットだけは。
handlers=java.util.logging.ConsoleHandler .level=INFO java.util.logging.ConsoleHandler.level=INFO java.util.logging.ConsoleHandler.formatter=java.util.logging.SimpleFormatter java.util.logging.SimpleFormatter.format=%1$tY-%1$tm-%1$td %1$tH:%1$tM:%1$tS.%1$tL %4$s [%3$s] %5$s (%2$s) %6$s%n
handlersと.levelの書き方が異なるのが気になると思いますが、これは読み込まれ方が異なります。前者はトップレベルの設定、後者はロガー名が先頭につく形式ですが、ルートなので空文字でこのような表記になっています。
例外とメッセージパラメータの両方がある場合は以下のようにLogRecordを使って4行も書かなければいけません。これはJavaメソッドシグネチャの制約によるもので、可変長引数が出現するとその後には引数を宣言できないため、最終引数となるThrowable
の行き場がなくなった、という理由からです。さらにトドメで、LogRecordのパラメータは可変長引数に対応してません。
Logger logger = Logger.getLogger("foo"); String message = "{0} failed"; Exception ex = new Exception(); LogRecord lc = new LogRecord(SEVERE, message); lc.setParameters(new String[]{"JUL"}); lc.setThrown(ex); logger.log(lc);
ちなみにSLF4Jでは可変長引数の最終引数が例外であれば特別視して例外として別に扱う、というそれはそれでキモいAPIになっています。でも便利です。
LogRecordは冗長なので、Loggerに渡す前にString.format()
でメッセージを組み立てるという方法をよく使います。String.format()
はsprintfフォーマットなので、Loggerのjava.text.MessageFormat
とは形式が異なります。こちらのほうが一般的に好まれるフォーマットでしょう。
String message = String.format("%s sucks", "JUL"); logger.log(SEVERE, message, new Exception());
依存なしでログ出したいという局面はそこそこあるんですが、JULのAPIがこんな感じなのでいつもがっかりしながら使っているのでした。
依存が許容される場合はJBoss Logging + JBoss LogManagerか、SLF4J + Logbackのどちらかの組み合わせを使えばいいと思います。