SLF4J extensionsの中にある、SLF4J Localizationですが、こちらをちょっと試しておきたいなと思いまして。
Javadocは、こちら。
このSLF4J Localizationを使うことで、ログメッセージを国際化対応させることができます。
使い方はある程度ドキュメントを見たらわかるのですが、
- 通常のSLF4JのLoggerの代わりに、LocLoggerを使う
- ログメッセージを記述した.propertiesファイルを用意する(使用するLocaleの分だけ)
- 用意したログメッセージの.propertiesファイルの各キーに対応する、Enumを用意する
- 用意したEnumを引数に、LocLoggerを使ってログ出力
といった感じです。
要するにログメッセージは外部ファイル管理になり、メッセージはEnumで指定するという使い方になります。
また、Pluggable Annotation Processing APIを使用しており、Enumとプロパティファイルのキーが合わなかったりすると、コンパイルエラーになります。
内部的にはCompiler Assisted Localization (CAL10N)というものを使っているので、さらに詳細はこちらを参照。
では、使ってみましょう。
ちなみに、ログメッセージの国際化対応というよりは、プロパティファイルでの管理を試してみたい、という動機だったりします…。
準備
まずは、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
使うかどうかは考えどころですが、こういうのがあるということは押さえておこうかなと思います。