北海道苫小牧市出身の初老PGが書くブログ

永遠のプログラマを夢見る、苫小牧市出身のおじさんのちらしの裏

Logging API でログと上手に付き合う

最近Logging APIを使ってます。あんま使い勝手はよくないんですが、慣れてきました(笑)。*1 あ、ちなみに先に言っておくと、log4jは触ったことないので、そっちと比べての善し悪しはわかりません*2log4jの方が便利だとは思いますけどね。


さて、Loggingですが、まず定石としてはこう。

public class Main {
	private static final Logger logger = 
            Logger.getLogger(Main.class.getName()); 

...

これで各クラスにLoggerを持たせます。そうすると、ロギングをする特殊なクラス(ロギングの仕方によっては上位APIに居やがることも多いorz)と関連を持たずに済むので、依存関係が複雑にならずに幸せになれます。実際のロギングはlogger.info("hogehoge")・・・と、まあ、どこのサイトでも解説してるので略。


で、これをやると各クラスにLoggerが散らばるので、個々のLoggerに投げられたログを一括して表示や保存をするクラスが必要になります。それがHandlerの役目です。FileHanlerをlogging.propertiesから指定して使ってもいいのですが、あまり使い勝手がよくないので、使うのならプログラム内でセットしたほうが良さそうです。コンストラクタに好きなファイル名パターンを渡せるので、ファイル名に日付等を入れるといいと思います。


作ったハンドラはLoggerに設定しなければならないのですが、MainクラスのLoggerに設定するとMainクラスのログしか取れません。そこで、Loggerの親子関係を利用します。ログを取りたい部分の根元のパッケージのLoggerを作り、そこにHandlerをaddします。


例えば、proj.*, proj.main.*, proj.util.*, util.hoge.*, util.foo.* と言うようなパッケージを開発していて、この部分だけログが取りたい場合はメインルーチンや初期化処理のどこかで以下のようにします。

Logger.getLogger("proj").addHandler(handler);
Logger.getLogger("util").addHandler(handler);

これで、先ほどのパッケージのログは全て取れます。ここの最大のミソは、"proj"と"util"と言う名前のLoggerはハンドラをかけるためだけに使っていて、実際にコイツらのlog()メソッドを呼ぶことはないってことです。この例では、ログを記録するHandlerを"proj"と言う名前のLoggerにaddしますが、実際にログを書き込む先は"proj.main.Main"と言う別のLoggerです。この親子関係のお陰で、proj.main.Mainクラスは、"proj.main.Main"と言う名のLoggerにログを送るだけでそれ以外のLoggerの存在は気にしなくても、ログを取りたいクラスは勝手に親Logger経由で取ってってくれます。


これに加えて自分で作ったHandlerやLevelを上手に使えば、業務クラス・低レベルAPIのクラスからの例外や警告を効果的に追跡出来ます。特に、低レベルAPIの警告を上手に扱えるのは便利。SEVEREレベルの情報なら例外をthrowすることで簡単に上位に伝えられますが、warningを上位に伝えるのはなかなか面倒なもんです。低レベルAPI→Logger→独自Handler→上位APIと言うルートで警告を伝えさせれば、低レベルAPIは上位APIを知らなくてもLoggerさえ知ってれば済むので、疎結合を保てます。


後、Handlerの実装によってはWARNINGやSEVEREの発生回数を数えたりもできます。それによってアプリの終了状態を知るのにも役に立ちます。
HandlerでWARNINGやSERVEREの回数を追うと言う考えには致命的な欠陥がありまして、それは、Loggerのロギングレベルの影響をモロ受けてしまうってことです。SERVEREが発生しまくっても、LoggerのロギングレベルがOFFだと、Handlerにはこれらのイベントは伝わらず、正常に処理が終了したように見えてしまいます。


独自のLevel実装に関しては、業務層に近い部分をLoggerで取り扱うのに使うといいと思います。多分INFO, WARNING, SEVEREだと足りなくなるので・・・。


後、デフォルトのlogging.propertiesはConsoleHandlerが元気良すぎるので、設定は自前で用意した方がいいと思います。



おまけ。開発中にパッケージ名が頻繁に変わる人*3は、"ベースパッケージ".util.Utilクラスを作って、こんなメソッドを用意しておくと便利。

public class MyUtil {
	private static final Class MYCLASS = MyUtil.class;

	static public String basePackageName(){
		String pkg = MYCLASS.getPackage().getName();
		int ptr = pkg.lastIndexOf('.');
		return pkg.substring(0, ptr).intern();
	}
}

パッケージ名を一つだけさかのぼって返してくれます。org.dyndns.hiratara.hoge で開発していたら、org.dyndns.hiratara.hoge.util.MyUtilにこれを作れば、org.dyndns.hiratara.hogeが返ってきます。

Logger.getLogger(MyUtil.basePackageName()).addHandler(handler);

これで多少気が変わっても大丈夫! 自分で作ったクラスのログはすべてhandlerに集まります。・・・のはず。

*1:しかし、ログ関連のコーディングやってるとアスペクトの強さが痛感出来ますな。。。

*2:まだjavaは初心者なんですよ。。。

*3:俺w