こんばんは。先日ブログの設定をいじっていたら、全てのページ(管理画面も!)でPHPエラーが表示されてかなり気落ちしました。PHPを書くのは久しぶりだったので、どこか書き間違えたんでしょう・・・。
さて、今日はロギングの話をします。
ロギングと言えばlog4jですが、JavaSEにもロギングを扱うための標準APIがあります(1.4から)。それがjava.util.loggingです。標準APIであるにも関わらず、なぜか情報が少ないんですね。簡単なプログラムを書こうとして、ついでだから標準API使ってみるか、と思い立つも、結局設定の仕方がわからずにlog4jに戻ったことが何度かありました。
まずは一番簡単な使い方から。
Logger logger = Logger.getLogger(Logger.GLOBAL_LOGGER_NAME);
logger.info("Loggerテストです。info");
logger.severe("Loggerテストです。severe");
これだけです。このコードを実行すると、私のマシンでは以下の出力が得られます。
2011/11/05 23:34:29 Sample1 main 情報: Loggerテストです。info 2011/11/05 23:34:29 Sample1 main 致命的: Loggerテストです。severe
簡単ですね。
実際実行してみるとわかる通り、先ほどのコードでは、ログはエラー出力に出力されます。標準出力に出力するには、ハンドラを定義します。
Logger logger = Logger.getLogger(Logger.GLOBAL_LOGGER_NAME);
logger.addHandler(new StreamHandler(){
{ setOutputStream(System.out); }
});
logger.setUseParentHandlers(false);
logger.info("Loggerテスト2です。");
parentHandlerについては後で説明することにして、このコードを実行すると以下の出力が(標準出力に)得られます。
2011/11/05 23:47:55 Sample2 main 情報: Loggerテスト2です。
では更に出力されるログのレベルを変更してみましょう。「どうせsetLogLevel()だろう?」ほぼ正解です! 以下、コードとその実行結果です。
Logger logger = Logger.getLogger(Logger.GLOBAL_LOGGER_NAME);
logger.addHandler(new StreamHandler(){
{
setOutputStream(System.out);
setLevel(Level.ALL);
}
});
logger.setUseParentHandlers(false);
logger.setLevel(Level.ALL);
logger.fine("Loggerテスト3です。fine");
logger.info("Loggerテスト3です。info");
logger.severe("Loggerテスト3です。severe");
2011/11/06 0:10:22 Sample3 main 詳細レベル (低): Loggerテスト3です。fine 2011/11/06 0:10:22 Sample3 main 情報: Loggerテスト3です。info 2011/11/06 0:10:22 Sample3 main 致命的: Loggerテスト3です。severe
loggerとhandler、両方にレベルを設定していることに注意してください。
loggerインスタンスを作成する度にコード内で設定を書くのはいかにも面倒です。log4jのように設定ファイルを使って設定したいものです。
まずは、先ほど横着してインナークラスにしていた”標準出力へログ出力するハンドラ”を通常のクラスとして定義しておきます。
sample.StdConsoleHandler
package sample;
import java.util.logging.StreamHandler;
public class StdConsoleHandler extends StreamHandler {
{
setOutputStream(System.out);
}
}
次にログ設定ファイルを作成します。
customLogging.properties
handlers= sample.StdConsoleHandler .level= ALL sample.StdConsoleHandler.level = ALL
最後にログ出力するためのコードです。
try {
LogManager.getLogManager().readConfiguration(
Sample4.class.getResourceAsStream("customLogging.properties"));
} catch (SecurityException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
Logger logger = Logger.getLogger(Logger.GLOBAL_LOGGER_NAME);
logger.fine("Loggerテスト4です。fine");
logger.info("Loggerテスト4です。info");
logger.severe("Loggerテスト4です。severe");
ログ設定ファイルと実行コードを同じディレクトリに置いて実行してください。すると以下の出力が得られます。
2011/11/06 0:47:32 Sample4 main 詳細レベル (低): Loggerテスト4です。fine 2011/11/06 0:47:32 Sample4 main 情報: Loggerテスト4です。info 2011/11/06 0:47:32 Sample4 main 致命的: Loggerテスト4です。severe
getLogger()をコールするときにロガーの名前を指定します。ここまでは簡易的に汎用ロガーを使用してきましたが、実際の開発では幾つものモジュールを開発するので、モジュール毎にログ設定をしたいことも多いでしょう。
ロガーを使い分けるには、以下のようなコードにします。ロガーを3つ使ってみました。
try {
LogManager.getLogManager().readConfiguration(
Sample5.class.getResourceAsStream("customLogging.properties"));
} catch (SecurityException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
Logger loggerX = Logger.getLogger("xxx");
Logger loggerY = Logger.getLogger("xxx.yyy");
Logger loggerZ = Logger.getLogger("xxx.zzz");
loggerX.fine("LoggerXテスト5です。fine");
loggerX.info("LoggerXテスト5です。info");
loggerY.fine("LoggerYテスト5です。fine");
loggerY.info("LoggerYテスト5です。info");
loggerZ.fine("LoggerZテスト5です。fine");
loggerZ.info("LoggerZテスト5です。info");
それから、先ほどのログ設定ファイルを以下のように書き換えましょう。
# custom handler sample.StdConsoleHandler.level = ALL # custom logger xxx.handlers = sample.StdConsoleHandler xxx.level = INFO xxx.useParentHandlers = false xxx.zzz.level = ALL
コードを実行すると、以下の出力が得られます。
2011/11/06 1:32:15 Sample5 main 情報: LoggerXテスト5です。info 2011/11/06 1:32:15 Sample5 main 情報: LoggerYテスト5です。info 2011/11/06 1:32:15 Sample5 main 詳細レベル (低): LoggerZテスト5です。fine 2011/11/06 1:32:15 Sample5 main 情報: LoggerZテスト5です。info
お気づきでしょうか? loggerZではfineのログまで出力されていますが、loggerX、loggerYではinfoまでしか出力されていません。
これがロガーツリーです。Logger.getLogger("xxx")で取得したロガーは、Logger.getLogger("xxx.yyy")・Logger.getLogger("xxx.zzz")で取得したロガーの親ロガーになります。
ここにはポイントが2つあります。
実はJREにはデフォルトのログ設定ファイルがあります。通常$JAVA_HOME/lib/logging.propertiesにありますので開いてみてください。".level"・"handlers"・"ConsoleHandler"・"FileHandler"などの設定がされているのがわかると思います。
先ほど、"xxx"のロガーは"xxx.yyy"のロガーの親ロガーになると言いましたが、実際にはさらに全ての(明示的な親ロガーを持たない)ロガーの親としてルートロガーなるものが存在しています。そしてルートロガーのログレベルは設定ファイルの".level"、ハンドラは設定ファイルの"handlers"で決定されます。
getLogger(Logger.GLOBAL_LOGGER_NAME)で取得できるロガーの親ロガーもルートロガーです。最初の例でエラー出力に出力されたのはルートロガーのハンドラがConsoleHandlerだったからなんですね。ただConsoleHandlerがなぜエラー出力を使う仕様になっているのかは謎です(笑)。少し不便ですよね。
また、2番目以降の例でsetUseParentHandlers(false)を使用しているのは、ルートロガーのハンドラ(ConsoleHandler)へログメッセージを発行しないようにするためでした。
ここまでjava.util.loggingを見てきましたが、いかがでしょうか。log4jなどに慣れていると戸惑う部分もありますが、標準でついている機能を「わからないから使わない」のではもったいないと思います。違いを理解した上で使い分けたいものですね。