nekop's blog

OpenShift / JBoss / WildFly / Infinispanの中の人 http://twitter.com/nekop

JBoss Logging Toolsを利用してアノテーションでログ出力と国際化メッセージを管理

JBoss Advent Calendar 2012の4日目のエントリです。JBoss Loggingまわりのことを書いてたらTwitter上で開発者にふぁぼられてました。

JBoss Logging Toolsアノテーションを利用してタイプセーフで高速で国際化対応されたログを出力するためのツールです。ログ定義をするアノテーションと、それに対応するアノテーションプロセッサ(APT)を提供します。JBoss Loggingと組み合わせて利用します。また、ログ出力だけではなく、画面表示などに利用する国際化メッセージも同じように定義して取得することができます。

実際の例を見てみましょう。以下はJBoss AS7の起動時のログです。

11:00:00,000 INFO  [org.jboss.as] (Controller Boot Thread) JBAS015874: JBoss EAP 6.0.1.GA (AS 7.1.3.Final-redhat-4) started in 1552ms - Started 136 of 217 services (80 services are passive or on-demand)

これを出力するコードの呼び出しは以下のようになっています。多少計算が入っているので見づらいかもしれませんが、ログのパラメータは5つです。

ServerLogger.AS_ROOT_LOGGER.startedClean(prettyVersion, elapsedTime, started, active + passive + onDemand + never, onDemand + passive);

定義しているクラスを見てみましょう。

@MessageLogger(projectCode = "JBAS")
public interface ServerLogger extends BasicLogger {
    /**
     * A logger with the category {@code org.jboss.as}.
     */
    ServerLogger AS_ROOT_LOGGER = Logger.getMessageLogger(ServerLogger.class, "org.jboss.as");

    @LogMessage(level = INFO)
    @Message(id = 15874, value = "%s started in %dms - Started %d of %d services (%d services are passive or on-demand)")
    void startedClean(String prettyVersionString, long time, int startedServices, int allServices, int passiveOnDemandServices);
}

通常のログインタフェースのログメッセージパラメータは Object... params などと型はObjectになっているものがほとんどだと思いますが、JBoss Logging Toolsを利用するとログメッセージパラメータは型が明示されていてタイプセーフとなります。また、ログメッセージパラメータのプレイスホルダの形式も標準のJULのjava.text.MessageFormat形式({0})とは異なり、java.util.Formatter形式(%s)で強力です。また、この例では使っていませんが例外パラメータは@Causeというアノテーションでマークするとよきにはからってくれます。

AS_ROOT_LOGGERが何なのかを見ていきましょう。JBoss LoggingのLogger.getMessageLogger()というメソッドJBoss Logging Toolsが生成したタイプセーフロガークラスを読み込むためのメソッドです。通常のgetLogger()のパラメータはカテゴリ名のみの指定ですが、getMessageLogger()ではタイプセーフロガークラスを読み込むために定義クラスも一緒に渡す必要があります。

Logger.getMessageLogger(ServerLogger.class, "org.jboss.as");

コンパイル時にJBoss Logging Toolsのアノテーションプロセッサが生成するクラスは以下のような名前になっていて、この生成されたクラスが実際に読み込まれることになります。コード生成にはCodemodelを利用しています。

org.jboss.as.server.ServerLogger_$logger

javapしてみます。

$ javap -classpath . org.jboss.as.server.ServerLogger_\$logger
public class org.jboss.as.server.ServerLogger_$logger extends org.jboss.logging.DelegatingBasicLogger implements java.io.Serializable,org.jboss.as.server.ServerLogger,org.jboss.logging.BasicLogger{
    public final void startedClean(java.lang.String, long, int, int, int);
    protected java.lang.String startedClean$str();
}

コンパイルしたときに、国際化のために以下のようにプロパティファイルの元も生成されます。

target/generated-translation-files/org/jboss/as/server/ServerLogger.i18n_locale_COUNTRY_VARIANT.properties

これを翻訳して配置しなおします。

server/src/main/resources/org/jboss/as/server/ServerLogger.i18n_ja.properties

もう一回ビルドすると……

org.jboss.as.server.ServerLogger_$logger_ja

こんなクラスができます。プロパティファイルに記述した翻訳メッセージはクラスにインライン展開されるので、国際化しても一切パフォーマンスに影響を与えることなく高速にログメッセージを組み立てることができるようになっています。プロパティファイルはランタイムでは利用されません。

実際に試したい人はJBossのクイックスタートにJBoss Logging Toolのチュートリアルが用意されています。

JBoss AS7の製品版(安定度マシマシサポートつきバージョン)であるJBoss EAP 6のエンジニアリングに僕も参加していたのですが、国際化ログメッセージの初期版はいろいろ問題があってリリース延期の可能性まで出てきたので急遽飛び込んでごっそり調べたり直したりしたので詳しくなってしまったのでした。あ、リリースはちゃんと間に合わせましたよ。

というわけで、JBoss Logging Toolsの紹介でした。Happy Logging!