java.util.logging.Loggerをもっと便利かつ気楽に使ってみたい。

久々のマジ更新。


JDKの1.4から追加されたLoggerですが、何かといろいろ設定ファイルいじったりなんだかんだでめんどっちい。Logger.globalを使えば、System.outと同じ感覚で使えるのが便利だけども、フォーマットが気に入らない。
ここで、自分でLogger.globalに変わる物を作っちゃおう。って言うのが、今回の趣旨です。


2005-10-09 11:13:11 [FINEST] ファイネスト (10:Hoge#main)
2005-10-09 11:13:11 [FINER] ファイナー (10:Hoge#main)
2005-10-09 11:13:11 [FINE] ファイン (10:Hoge#main)
2005-10-09 11:13:11 [CONFIG] コンフィグ (10:Hoge#main)
2005-10-09 11:13:11 [INFO] インフォ (10:Hoge#main)
2005-10-09 11:13:11 [WARNING] ワーニング (10:Hoge#main)
2005-10-09 11:13:11 [SEVERE] 致命的 (10:Hoge#main)
こんな感じで出力されるスタティックアクセスなログを作ってみた。
フォーマットは、

yyyy-MM-dd YY:mm:ss [出力レベル] メッセージ内容 (スレッドNo:クラス名:メソッド名)
となっています。クラス名のフルパスと、ソースの行番号が出せればいいんだけどなあ……。
上記ログ出力用のソースコードは以下の通り。

public class Hoge{
public static void main(String[] args){
Log.out.finest("ファイネスト");
Log.out.finer("ファイナー");
Log.out.fine("ファイン");
Log.out.config("コンフィグ");
Log.out.info("インフォ");
Log.out.warning("ワーニング");
Log.out.severe("致命的");
}
}
お手軽です。
で、肝心のこのLogクラスは下のコードです。

import java.util.logging.Logger;
import java.util.logging.LogManager;
import java.util.logging.Handler;
import java.util.logging.ConsoleHandler;
import java.util.logging.Level;
import java.util.logging.Formatter;
import java.util.logging.LogRecord;
import java.util.Date;
import java.text.SimpleDateFormat;


/**
* ログクラス
* @auther m.inert
*/
public class Log extends Logger{
/** ロガーのログレベル */
public static Level LEVEL = Level.FINEST;
/** ハンドラの種別 */
public final static String handlerName = "java.util.logging.ConsoleHandler";


/** スタティックアクセスログインスタンス */
public static Log out = getLogger("myLog");

/**
* コンストラク
* @param 名称
* @param リソースバンドル
* @see java.util.logging.Logger
*/
protected Log(String name, String resourceBundleName){
super(name, resourceBundleName);
}
/**
* ログインスタンスを取得します。
* @param 名称
* @return ログインスタンス
* @see java.util.logging.Logger#getLogger
*/
public static synchronized Log getLogger(String name) {
// ログマネージャを取得します。
LogManager manager = LogManager.getLogManager();
// ログマネージャからインスタンスを取得します。
Log result = (Log)manager.getLogger(name);
// ログマネージャにロガーが登録されていたか判定。
if (result == null) {
// ログマネージャ未登録時は新規にインスタンスを生成します。
result = new Log(name, null);
// ロガーにレベルを設定します。
result.setLevel( LEVEL );
// 設定されたハンドラーを生成します。
try{
Handler handler = ( Handler )Class.forName( handlerName ).newInstance();
// ハンドラにフォーマットを設定します。
handler.setFormatter( new Formatter(){
public synchronized String format(LogRecord record) {
SimpleDateFormat formatter = new SimpleDateFormat( "yyyy-MM-dd HH:mm:ss" );
Date date = new Date( record.getMillis() );
StringBuffer sb = new StringBuffer();
sb.append( formatter.format( date ) );
sb.append( " [" );
// sb.append( record.getLevel().getLocalizedName() );
sb.append( record.getLevel().getName() );
sb.append( "] " );
sb.append( record.getMessage() );
sb.append( " (" );
sb.append( record.getThreadID() );
sb.append( ":" );
sb.append( record.getSourceClassName() );
sb.append( "#" );
sb.append( record.getSourceMethodName() );
sb.append( ")\n" );
return sb.toString();
}
} );
// ハンドラにレベルを設定します。
handler.setLevel( LEVEL );
// ハンドラをロガーに設定します。
result.addHandler( handler );
}catch( Exception e ){
System.err.println( "ハンドラの設定に失敗しました。[詳細]" + e.getMessage() );
}
// 設定したロガーをマネージャに登録します。
manager.addLogger( (Log)result);
// 念の為、マネージャから再度ロガーを取り直します。
result = (Log)manager.getLogger(name);
}
// ロガーを返します。
return (Log)result;
}
/**
* LogRecord のログをとります。
* スーパークラスへの処理委譲を殺してあります。
* @param ログレコード
* @see java.util.logging.Logger#log
*/
public void log(LogRecord record){
// ロガーインスタンスを取得します。
Logger logger = ( Logger )out;
// nullじゃなければ
while (logger != null) {
// ロガーから設定されたハンドラを全て取得します。
Handler targets[] = logger.getHandlers();
// ハンドラがnullでなければ…。
if( targets != null ){
// ハンドラにログレコードを設定し、ログを書き出します。
for (int i = 0; i < targets.length; i++) {
targets[i].publish(record);
}
}
// ここで処理終了(whileじゃなくて if でいいんだけどね)
break;
// スーパークラスへの委譲処理はコメントアウト
// if (!logger.getUseParentHandlers()) {
// break;
// }
// logger = logger.getParent();
}
}
}

ここで注意しないといけないのは、ログインスタンスとハンドラインスタンスの療法にログレベルを設定する必要があるということ。デフォルトでは、jreのlogging.properties似設定されたログレベルで初期化されるので、両インスタンスにレベル設定しないと、プロパティに設定されたレベル以上のログは出力できません。
もうひとつ注意事項、log(LogRecord record)メソッドは殆どLoggerクラスのソースコードのままですが、ここでコメントアウトしている部分が重要です。ここをコメントアウトしていない場合は、一度ログ出力を呼び出すと、LogクラスとLoggerクラス両方のハンドラにログが出力されます。つまり、二重三重に出力されちゃうと言う事。
あとは、フィールドで持っている設定ログレベルと出力先ハンドラをプロパティもちにすれば、お手軽ログクラスの出来上がり。(このままでも充分使えると思うけども)
ま。出力フォーマットなんてそんなにちょくちょく変えるもんでも無いし、外部持ちにする必要なんて無いでしょ。

使う時は、System.outと差はありません。
ま。これまたテストが足りないので問題が無いかはわからんですが、結構いい感じに出来たかと思います。