CLOVER🍀

That was when it all began.

SLF4J Localizationを試す

SLF4J extensionsの中にある、SLF4J Localizationですが、こちらをちょっと試しておきたいなと思いまして。

Localization

Javadocは、こちら。

SLF4J 1.8.0-beta2 API

このSLF4J Localizationを使うことで、ログメッセージを国際化対応させることができます。

使い方はある程度ドキュメントを見たらわかるのですが、

  • 通常のSLF4JのLoggerの代わりに、LocLoggerを使う
  • ログメッセージを記述した.propertiesファイルを用意する(使用するLocaleの分だけ)
  • 用意したログメッセージの.propertiesファイルの各キーに対応する、Enumを用意する
  • 用意したEnumを引数に、LocLoggerを使ってログ出力

といった感じです。

要するにログメッセージは外部ファイル管理になり、メッセージはEnumで指定するという使い方になります。

また、Pluggable Annotation Processing APIを使用しており、Enumとプロパティファイルのキーが合わなかったりすると、コンパイルエラーになります。

内部的にはCompiler Assisted Localization (CAL10N)というものを使っているので、さらに詳細はこちらを参照。

CAL10N Home

Manual

では、使ってみましょう。

ちなみに、ログメッセージの国際化対応というよりは、プロパティファイルでの管理を試してみたい、という動機だったりします…。

準備

まずは、Maven依存関係から。

        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-api</artifactId>
            <version>1.7.21</version>
        </dependency>
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-simple</artifactId>
            <version>1.7.21</version>
        </dependency>
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-ext</artifactId>
            <version>1.7.21</version>
        </dependency>

        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.12</version>
            <scope>test</scope>
        </dependency>

「slf4j-ext」が必要になります。「slf4j-api」は、「slf4j-ext」が推移的に解決してくれるので、なくてもいいです。ログ出力は今回はslf4j-simpleとしました。

JUnitはただの実行用です。

使ってみる

それでは、SLF4J Localizationを使ってみましょう。

とりあえず、雛形的にこんなコードを用意。
src/test/java/org/littlewings/slf4j/localization/Slf4jLocalizationTest.java

package org.littlewings.slf4j.localization;

import java.util.Locale;

import ch.qos.cal10n.IMessageConveyor;
import ch.qos.cal10n.MessageConveyor;
import org.junit.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.slf4j.cal10n.LocLogger;
import org.slf4j.cal10n.LocLoggerFactory;

public class Slf4jLocalizationTest {
    // ここに、テストを書く!
}

まずは、普通にSLF4Jを使った場合。見慣れたものかと思います。

    @Test
    public void useNormalSlf4j() {
        Logger logger = LoggerFactory.getLogger(getClass());
        logger.info("Hello SLF4J!!");
        logger.info("Hello {}", "SLF4J!!");
    }

続いて、SLF4J Localizationを使う場合。

最初にEnumを定義しておく必要があります。
src/test/java/org/littlewings/slf4j/localization/HelloLocalization.java

package org.littlewings.slf4j.localization;

import ch.qos.cal10n.BaseName;
import ch.qos.cal10n.Locale;
import ch.qos.cal10n.LocaleData;

@BaseName("hello-localization")
@LocaleData(value = @Locale("ja_JP"),
        defaultCharset = "UTF-8")
public enum HelloLocalization {
    HELLO_WORLD,
    HELLO_WORLD_WITH_ARGS
}

作成したEnumでは、@BaseNameアノテーションで作成するログメッセージを管理する、基底部分の名前を定義します。また、@LocaleDataで利用するLocaleを記述する必要があります。今回は、「ja_JP」のみとしました。

また、用意するプロパティファイルは、native2asciiをかけておく必要はなかったりします(かかっていてもいいみたいですが)。

とりあえず、用意したプロパティファイルはこちら。今回は@Localeで「ja_JP」を指定したので、用意するファイルは「hello-localization_ja_JP.properties」となります。
src/test/resources/hello-localization_ja_JP.properties

HELLO_WORLD=こんにちは、世界
HELLO_WORLD_WITH_ARGS=こんにちは、{0} {1}

プロパティファイルのエンコーディングの指定は、@LocaleDataのdefaultCharset、または@Localeのcharsetで指定が可能です。

@LocaleData(value = @Locale("ja_JP"),
        defaultCharset = "UTF-8")

Pick your charset, per locale (no native2ascii)

しれっと引数を取るメッセージも用意していますが、こちらについてはMessageFormatの書式で指定することができます。

HELLO_WORLD_WITH_ARGS=こんにちは、{0} {1}

Retrieving internationalized messages

では、呼び出し側を書いてみましょう。

    @Test
    public void useLocLogger() {
        IMessageConveyor messageConveyor = new MessageConveyor(Locale.JAPAN);
        LocLoggerFactory loggerFactory = new LocLoggerFactory(messageConveyor);
        LocLogger logger = loggerFactory.getLocLogger(getClass());

        logger.info(HelloLocalization.HELLO_WORLD);
        logger.info(HelloLocalization.HELLO_WORLD_WITH_ARGS, "SLF4J", "Localization");
    }

通常のSLF4Jとは違い、先にIMessageConveyorのインスタンスを生成する必要があります。IMessageConveyorは、CAL10N側のインターフェースです。

        IMessageConveyor messageConveyor = new MessageConveyor(Locale.JAPAN);

このIMessageConveyorを使ってLocLoggerFactoryを生成し、それからLocLoggerを取得します。

        LocLoggerFactory loggerFactory = new LocLoggerFactory(messageConveyor);
        LocLogger logger = loggerFactory.getLocLogger(getClass());

あとは、LocLoggerのinfoやdebugなどの各種ログ出力時に、第1引数に先ほど作成したEnumを渡してログ出力を行います。

        logger.info(HelloLocalization.HELLO_WORLD);
        logger.info(HelloLocalization.HELLO_WORLD_WITH_ARGS, "SLF4J", "Localization");

実行結果はこちら。

[main] INFO org.littlewings.slf4j.localization.Slf4jLocalizationTest - こんにちは、世界
[main] INFO org.littlewings.slf4j.localization.Slf4jLocalizationTest - こんにちは、SLF4J Localization

なお、LocLoggerはSLF4JのLoggerを拡張したものでもあるので、通常のSLF4JのLoggerとしても使うことができます。

        logger.error("エラー!");

ところで、Pluggable Annotation Processing APIでチェックが入るよ、ということでしたが、例えばEnum側にしか存在しないキーを書いて

@BaseName("hello-localization")
@LocaleData(value = @Locale("ja_JP"),
        defaultCharset = "UTF-8")
public enum HelloLocalization {
    HELLO_WORLD,
    HELLO_WORLD_WITH_ARGS,
    ONLY_ENUM_KEY
}

コンパイルしようとすると、プロパティファイルの方にそんなキーないよ、と怒られます。

[ERROR] Failed to execute goal org.apache.maven.plugins:maven-compiler-plugin:3.1:testCompile (default-testCompile) on project slf4j-localization-example: Compilation failure
[ERROR] /path/to/src/test/java/org/littlewings/slf4j/localization/HelloLocalization.java:[10,8] Key [ONLY_ENUM_KEY] present in enum type [org.littlewings.slf4j.localization.HelloLocalization] but absent in resource bundle named [hello-localization] for locale [ja_JP]

反対に、プロパティファイルにしかないキーを定義しても

HELLO_WORLD=こんにちは、世界
HELLO_WORLD_WITH_ARGS=こんにちは、{0} {1}
ONLY_PROPERTY_FILE_KEY=こんにちは、{0} {1}

やっぱりエラーになります。

[ERROR] Failed to execute goal org.apache.maven.plugins:maven-compiler-plugin:3.1:testCompile (default-testCompile) on project slf4j-localization-example: Compilation failure
[ERROR] /paht/to/src/test/java/org/littlewings/slf4j/localization/HelloLocalization.java:[10,8] Key [ONLY_PROPERTY_FILE_KEY] present in resource bundle named [hello-localization] for locale [ja_JP] but absent in enum type [org.littlewings.slf4j.localization.HelloLocalization]

詳細は、このあたりを。

JSR-269 support, i.e. verification at compile time

使うかどうかは考えどころですが、こういうのがあるということは押さえておこうかなと思います。