/*
 * blanco Framework
 * Copyright (C) 2004-2007 IGA Tosiki
 * 
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 */
package blanco.log.logging;

import java.io.PrintWriter;
import java.io.StringWriter;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.logging.Formatter;
import java.util.logging.LogRecord;

/**
 * java.util.logging の出力を Apache log4j 風のフォーマットに変換するためのフォーマッタクラス。
 * 
 * <P>
 * blanco.log.logging.BlancoLogSimpleFormatter クラスを利用すると、java.util.logging の出力を
 * Apache log4j 風のフォーマットに変換することができます。
 * </P>
 * <P>
 * java.util.logging の java.util.logging.SimpleFormatter
 * クラスのフォーマットは、あまり一般的な「シンプルな」フォーマットではないと感じる方が多いのでは無いでしょうか。BlancoLogSimpleFormatter
 * クラスを利用すると、Apache log4j 風のシンプルなフォーマットを得ることが出来ます。Apache
 * log4jの「log4j.appender.XX.layout.ConversionPattern=%d %-5p %c -
 * %m%n」に相当する出力となっています。
 * </P>
 * <P>
 * <FONT color="#ff00ff">利用方法</FONT>
 * </P>
 * <P>
 * 下記のような手順で利用することが出来ます。
 * </P>
 * <UL>
 * <LI>クラスパス上に以下のファイルを配置します。<BR>
 * ※Java実行環境の jre\lib\ext ディレクトリに配置するという方法もあります。
 * <UL>
 * <LI>blancolog-?.?.?.jar: blancoLogのjarファイル
 * </UL>
 * 
 * 
 * <LI>java.util.logging の設定を変更します。<BR>
 * ※最も単純な変更方法は、Java実行環境の jre\lib ディレクトリの logging.properties
 * を変更する方法です。なお、変更する前にオリジナルのファイルをバックアップすることを推奨します。
 * <UL>
 * <LI>Java実行環境の jre\lib ディレクトリ の logging.properties ファイルの、以下の箇所を変更します。(<STRONG>他の箇所は変更しないようにします</STRONG>)<BR>
 * &lt;logging.properties&gt;<BR>
 * <TABLE border="1"> <TBODY>
 * <TR>
 * <TD>…省略…<BR>
 * java.util.logging.ConsoleHandler.formatter=blanco.log.logging.BlancoLogSimpleFormatter<BR>
 * …省略…</TD>
 * </TR>
 * </TBODY> </TABLE> これにより、コンソール出力のフォーマットが変更されます。
 * <LI>java.util.logging からは、これ以外にも幾つかの設定変更方法が提供されています。
 * </UL>
 * </UL>
 * <p>
 * <FONT color="#ff00ff">java.util.logging.Level と ログのレベル文字列との対応付け</FONT>
 * </p>
 * <P>
 * java.util.logging と Apache log4j
 * におけるログのレベル文字列とでは粒度が異なります。このため、下記のようなマッピングを行っています。
 * </P>
 * <BLOCKQUOTE> <TABLE border="1"> <TBODY>
 * <TR>
 * <TD bgcolor="#ffff00"><STRONG>java.util.logging のログレベル</STRONG></TD>
 * <TD bgcolor="#ffff00"><STRONG>ログのレベル文字列</STRONG></TD>
 * <TD bgcolor="#ffff00"><STRONG>備考</STRONG></TD>
 * </TR>
 * <TR>
 * <TD>java.util.logging.Level.FINEST</TD>
 * <TD>TRACE</TD>
 * <TD>「詳細レベル(高)」。</TD>
 * </TR>
 * <TR>
 * <TD>java.util.logging.Level.FINER</TD>
 * <TD>TRACE</TD>
 * <TD>「詳細レベル(中)」。</TD>
 * </TR>
 * <TR>
 * <TD>java.util.logging.Level.FINE</TD>
 * <TD>DEBUG</TD>
 * <TD>「詳細レベル(低)」。</TD>
 * </TR>
 * <TR>
 * <TD>java.util.logging.Level.CONFIG</TD>
 * <TD>CONF (*1)</TD>
 * <TD>「設定」。<BR>
 * (*1)Apache log4j には、このログレベル文字列は存在しません。</TD>
 * </TR>
 * <TR>
 * <TD>java.util.logging.Level.INFO</TD>
 * <TD>INFO</TD>
 * <TD>「情報」。</TD>
 * </TR>
 * <TR>
 * <TD>java.util.logging.Level.WARNING</TD>
 * <TD>WARN</TD>
 * <TD>「警告」。</TD>
 * </TR>
 * <TR>
 * <TD>java.util.logging.Level.SEVERE</TD>
 * <TD>ERROR<BR>
 * FATAL (*2)</TD>
 * <TD>「致命的」。<BR>
 * (*2)メッセージがfatalから始まるもののみ FATAL にマップします。</TD>
 * </TR>
 * </TBODY> </TABLE> </BLOCKQUOTE>
 * 
 * @author IGA Tosiki
 */
public class BlancoLogSimpleFormatter extends Formatter {
    /**
     * ISO 8601 フォーマットの日付。
     */
    protected final SimpleDateFormat sdFormat = new SimpleDateFormat(
            "yyyy-MM-dd HH:mm:ss,SSS");

    public String format(final LogRecord argLogRecord) {
        final StringBuffer buf = new StringBuffer(1024);

        logRecordMessage2Buffer(argLogRecord, buf);

        if (argLogRecord.getThrown() != null) {
            // 例外オブジェクトの出力。
            logRecordThrown2Buffer(argLogRecord.getThrown(), buf);
        }

        return buf.toString();
    }

    protected void logRecordMessage2Buffer(final LogRecord argLogRecord,
            final StringBuffer argBuf) {
        argBuf.append(sdFormat.format(new Date(argLogRecord.getMillis())));
        argBuf.append(" ");

        switch (argLogRecord.getLevel().intValue()) {
        case BlancoLogLoggingConstants.FINEST:
        case BlancoLogLoggingConstants.FINER:
            argBuf.append("TRACE");
            break;
        case BlancoLogLoggingConstants.FINE:
            argBuf.append("DEBUG");
            break;
        case BlancoLogLoggingConstants.CONFIG:
            argBuf.append("CONF ");
            break;
        case BlancoLogLoggingConstants.INFO:
            argBuf.append("INFO ");
            break;
        case BlancoLogLoggingConstants.WARNING:
            argBuf.append("WARN ");
            break;
        case BlancoLogLoggingConstants.SEVERE:
            boolean isFatal = false;
            if (argLogRecord.getMessage().length() >= 5) {
                final String head = argLogRecord.getMessage().substring(0, 5);
                if ("fatal".equals(head.toLowerCase())) {
                    // メッセージが fatal または FATAL で開始される場合にのみ fatal にマッピング。
                    isFatal = true;
                }
            }
            if (isFatal) {
                argBuf.append("FATAL");
            } else {
                argBuf.append("ERROR");
            }
            break;
        default:
            argBuf.append(Integer.toString(argLogRecord.getLevel().intValue()));
            argBuf.append("  ");
            break;
        }
        argBuf.append(" ");
        argBuf.append(argLogRecord.getLoggerName());
        argBuf.append(" - ");
        argBuf.append(argLogRecord.getMessage());
        argBuf.append("\n");
    }

    protected void logRecordThrown2Buffer(final Throwable argEx,
            final StringBuffer argBuf) {
        final StringWriter writerResult = new StringWriter(1024);
        final PrintWriter writer = new PrintWriter(writerResult);
        argEx.printStackTrace(writer);
        writer.close();

        argBuf.append(writerResult.toString());
        // スタックトレースの最後には改行が最初から付いています。このため、更なる改行付与は不要です。
    }
}
