Java ロギング

2008年01月24日
Java 標準のロギング機能である Java Logging API と、高いパフォーマンスと実績で定評のある Jakarta Log4j を採り上げつつ、ロギングとは何をするものかなどを説明します。

ログ出力

プログラミングにおけるログ (Log) とは、簡単に言えばプログラムの動作記録の事です (もっと広義には人の操作やシステムの動作履歴なども意味します)。一般的なプログラマは、自分のプログラムに不審な動きがあった時にすぐ原因を分析できるよう、コードのあちこちにちょっとした "記録処理" を仕掛けておきます。

Java において最も簡単に書けるログは System.out.println() です。プログラマは実行中のプログラムの標準出力 (コマンドプロンプト) に表示されるログを追えばプログラムのどの位置が実行されたかを把握することが出来ます。

if(value != null){
    System.out.println("値が入力されました: " + value);
    ...
}

ではなぜわざわざログ用の API を使う必要があるのか? それは長くなるので後半にまとめます。

Java Logging API

Java Logging APIGoogle I'm Feeling Lucky™ とは Java2 SE 1.4 から標準機能となったログ出力のための API です。

Jakarta Log4j

Jakarta Log4jGoogle I'm Feeling Lucky™ とは

Jakarta Commons Logging

工事中
Under Construction
Jakarta Commons LoggingGoogle I'm Feeling Lucky™ とは

ロギング機能

開発側からログライブラリへの要求というのは、簡単に言えば以下の 3 点に集約されます。

  1. [使いやすさ] プログラム内のどこにでもシンプルに、可視性を損ねない方法で仕込めること
  2. [柔軟性・機能性] 出力先や書式などをコードを変更しないで一括変更できること
  3. [パフォーマンス] 実動作に著しい影響を与えないこと

ロギングというものは開発物の付加的なものであり、その機能自体にこだわるのはあまり意味の無いことです。

重要度とドメイン指定

レベルによるログのフィルタリング

ログというものは問題を分析する時に有用な情報であって、問題が発生していない状況では無駄な情報、無駄な処理、無駄な記録であることも確かです。しかし不要になったからと全てを手作業で削除したり、再び必要になったからと追加したりを繰り返していては作業効率が悪く、またミスによってバグが入り込む可能性も出てきます。

ログの情報は必要に応じて記録する/しないを切り替えられなければいけません。もっと親切な設計なら、全てを出すか出さないかの二者択一ではなく、問題分析に必要な部分のログに限定して出せるよう設計してあるべきです。これはログに重要度 (Priority) とドメイン (Domain) が指定出来るかどうかということを意味します。

ログの重要度に関しては古来から閾値 (Threshold) を設ける手法として使われて来ました。例えば Logging API でも Logger#fine()Java™ API リファレンスLogger#severe()Java™ API リファレンス などのメソッドを使い分ける事自体が、出力しようとしているログの重要度を示していることになります。

ドメインとは、そのログ出力の所属グループのような意味です (Logging API、Log4j 共に Logger を取得するときに指定します)。例えば Java の完全限定名*1 (FQN; Fully Qualified Name) をドメインとして使用することで、特定のパッケージに所属するクラスのみを一括制御することが出来ます。他にも "group.username" のようなドメインを使用すれば特定のグループに所属するユーザ処理のログのみを制御できます。開発規模が大きくなってくると色々な切り口で設定範囲を限定する必要も出てきます。

もちろん、重要度もドメインも使い方を間違えれば意味がないのは言うまでもありません。

*1  java.lang.String のようにパッケージ名を省略しないクラス名。

出力先の多様性

ロギング機能の多態化

単なる動作確認が目的であればログをコマンドプロンプトなどで表示して方法が一番楽です。しかしこれを見ている時に運良く問題が起きてくれるとは限りません。問題が発覚したときにすぐ分析を始められるにはファイルなどの二次記憶装置に記録しておく必要があります。

セキュリティの厳しいアプレットはどうでしょうか? 一般的なアプレットにはファイルという選択肢はなくアプレットコンソールと呼ばれるコマンドプロンプトに似たウィンドウに出力しなければなりません。イントラのネットワークコンピュータなら? エラーログを SOAP などでサーバに送信する必要があるかもしれません。単純にログを出力すると言ってもプログラムの構成やシステム要件によりさまざまな設計が必要です。

これはオブジェクト指向のポリモーフィズム (Polymorphism; 多態化) を使用することで解決することができます。Logging API では HandlerJava™ API リファレンス 、Log4j では AppenderJava™ API リファレンス のサブクラスが実際の物理デバイスへのログ出力を実装しています。アプリケーションからは自分たちが出力を行う LoggerJava™ API リファレンス, LoggerJava™ API リファレンス だけを意識していれば良いので、プログラムのコードから実際の出力方法を分離する事が出来ます。

プログラムから実際の出力先を切り離しておけば、後で出力先やシステム構成が変更になったとしても既存のプログラムを書き直す必要がありません。Logging API にしても Log4j にしても Logger に出力した内容がどこにどう出力されるかは、いつでも設定ファイルで変更できます。

書式の柔軟さ

現在時刻、スレッド名、出力レベル、メッセージ…ログに出せる情報はたくさんありますが、全てを出すのは冗長すぎます。必要な情報を見やすい形式で出力するために、ログの書式はカスタマイズ可能でなければいけません。ログ出力する文字列の組み立ては、Logging API では FormatterJava™ API リファレンス、 Log4j では LayoutJava™ API リファレンス のサブクラスが行います。

Log4j には PatternLayoutJava™ API リファレンス という String#format()Java™ API リファレンス に似たフォーマットを行うクラスが存在します。このクラスはかなり優秀で、固定フォーマットだからと自前で実装した Layout クラスより速いこともしばしばあります。

Logging API の FormatterSimpleFormatterJava™ API リファレンスXMLFormatterJava™ API リファレンス という 2 種類の簡単なフォーマットクラスしか用意されていません (Java SE 6 時点)。これらには PatternLayout のような書式指定を行うことが出来ないため、ログの書式を変えたいと思ったら自分で Formatter クラスを実装するしかありません。

パフォーマンス

当然ですがログ出力とは問題分析のための副次的な処理であり主役ではありません。 CPU 時間、メモリ、ディスク、全てにおいてアプリケーションの主役となる処理に与える影響を小さく抑えるべきです。

幸いにして Logging API も Log4j もパフォーマンスに関してはどちらも実用的に問題ないレベルです。

設定の一元化と分散化

多くのシステムでは最終的にログの設定はシステム運用者が一元管理します。設定変更時にあちこちのファイルを探さなくても済むように、設定ファイルはなるべく目的ごとに一つに収める方針になります。

一方で、いくつものチームが一つのアプリケーションを開発しているような現場では、それぞれのチームが自分たちのログレベルを自由にカスタマイズできるよう設定を分けられる必要があります。

つまりログの設定は必要に応じて 1 ファイルにまとめたり複数に分散できなければいけません。

足りないものは何か?

ここで提案しても独り言以上の何物でもないわけですが、いつか私が別の言語で汎用的なログ出力機能を実装する必要が出た時のためのメモ程度に…

一面的なドメイン

どのロギング機能も、一つのログ出力先 (Logger インスタンス) に対して静的な一つのドメインしか持つ事が出来ません。これはパフォーマンス的な観点から尤もな設計だとは思いますが、セカンダリに動的かつ多面的なドメインの追加方法を許可すべきだと考えます。具体的には、スレッドに結び付けられた追加の名前に対する出力も同時に行われるべきでしょう。設定を変更したいドメインの選択の切り口は一面的ではありません。

可変引数の未サポート
Java の可変引数は Java2 SE 5.0 からサポートされているわけですが、どのロギング機能も Java SE 6 に至ってまだ可変引数での記述をサポートしていません。これは可変引数の方が記述が楽だという事ではなくて、引数の文字列変換と連結を行う前にログ出力が必要かどうかを判断出来るため、if で囲まなければならない箇所を少なくできる (それを意識していないコードならパフォーマンスを上げられる) という意味です。
CVS 2008/01/30