ActiveObjects にログを吐かせる

 XML と完全にお別れしたい諸氏にとって(dicon だって XML だ)、Wicket + Guice + ActiveObjects という組み合わせは魅力的です。

 しかしながら、そのままでは懇切丁寧なログを吐く S2DaoS2JDBC に比べて ActiveObjects は物静かで中で何をやっているのかわかりません。

 とはいえ、ActiveObjectsS2DaoS2JDBC には遠く及びませんが、ログをとってないわけではないのです。



 ActiveObjects は、Java Logging API という J2SE 1.4 が出た当時は「お?」と注目されつつも現在は見る影もない(笑)ロガーを使っています。ところが、どういうわけか EntityManager が生成されたときにログレベルを OFF にしてしまっているようなのです。

 ということで、以下のようにすれば ActiveObjects が投げた SQL 文がロギングされるようになります。エンティティマネージャを生成した後に設定するのがミソです。

// エンティティマネージャの生成
EntityManager manager = new EntityManager("jdbc:mysql://127.0.0.1:3306/db", "username", "password");
// ActiveObjects のロガーを取得
Logger logger = Logger.getLogger("net.java.ao");
// ログレベルの設定
logger.setLevel(Level.FINE);

 で、ここまでは ActiveObjects のドキュメントにも書いてあることなのですが、Wicket と一緒に使っているのならば SLF4J でログを吐きたいというのが人情。ちょっと無理やりくさいですが、以下のようなユーティリティクラスとハンドラを書けばいけそうです。

package example.logging;

import java.util.logging.Level;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * ロギングに関するユーティリティ・クラスです。
 * 
 * @author itoasuka
 */
public final class LoggingUtils {
    /** 非常に詳細なトレースメッセージを示すログレベルです。 */
    public static final int FINEST = 300;

    /** かなり詳細なトレースメッセージを示すログレベルです。 */
    public static final int FINER = 400;

    /** トレース情報を提供するメッセージレベルです。 */
    public static final int FINE = 500;

    /** 静的な構成メッセージのメッセージレベルです。 */
    public static final int CONFIG = 700;

    /** メッセージを情報として提供するメッセージレベルです。 */
    public static final int INFO = 800;

    /** 潜在的な問題を示すメッセージレベルです。 */
    public static final int WARNING = 900;

    /** 重大な障害を示すメッセージレベルです。 */
    public static final int SEVERE = 1000;

    /**
     * 指定したロガー名を持つ SLF4J のロガーのログレベルから、Java Logging API のログレベルを取得します。
     * 
     * @param name
     *            ロガー名
     * @return ログレベル
     */
    public static Level getLevel(String name) {
        Logger logger = LoggerFactory.getLogger(name);

        if (logger.isTraceEnabled()) {
            return Level.ALL;
        }
        if (logger.isDebugEnabled()) {
            return Level.FINE;
        }
        if (logger.isInfoEnabled()) {
            return Level.CONFIG;
        }
        if (logger.isWarnEnabled()) {
            return Level.WARNING;
        }
        if (logger.isErrorEnabled()) {
            return Level.SEVERE;
        }
        return Level.OFF;
    }

    private LoggingUtils() {
        // 何もしない
    }
}
package example.logging;

import java.util.logging.Handler;
import java.util.logging.LogRecord;

import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * Java Logging API によるロギングを SLF4J に食わせるためのハンドラです。
 * 
 * @author itoasuka
 */
public class Slf4jHandler extends Handler {
    private Logger logger;

    /**
     * {@inheritDoc}
     */
    @Override
    public void close() {
        // 何もしない
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void flush() {
        // 何もしない
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void publish(LogRecord record) {
        int level = record.getLevel().intValue();
        String message = StringUtils.defaultString(record.getMessage());
        Throwable throwable = record.getThrown();
        Logger logger = LoggerFactory.getLogger(record.getLoggerName());

        switch (level) {
        case LoggingUtils.FINEST:
        case LoggingUtils.FINER:
            if (throwable == null) {
                logger.trace(message);
            } else {
                logger.trace(message, throwable);
            }
            break;
        case LoggingUtils.FINE:
            if (throwable == null) {
                logger.debug(message);
            } else {
                logger.debug(message, throwable);
            }
            break;
        case LoggingUtils.CONFIG:
        case LoggingUtils.INFO:
            if (throwable == null) {
                logger.info(message);
            } else {
                logger.info(message, throwable);
            }
            break;
        case LoggingUtils.WARNING:
            if (throwable == null) {
                logger.warn(message);
            } else {
                logger.warn(message, throwable);
            }
            break;
        case LoggingUtils.SEVERE:
        default:
            if (throwable == null) {
                logger.error(message);
            } else {
                logger.error(message, throwable);
            }
        }
    }
}

それで、これを以下のように組み込みます。

// エンティティマネージャの生成
EntityManager manager = new EntityManager("jdbc:mysql://127.0.0.1:3306/db", "username", "password");
// ActiveObjects のロガーを取得
Logger logger = Logger.getLogger("net.java.ao");
// ログレベルの設定
logger.setLevel(LoggingUtils.getLevel("net.java.ao"));
// ハンドラの追加
logger.addHandler(new Slf4jHandler());
// 親ロガーのハンドラは使わない
logger.setUseParentHandlers(false);