何気に使用しているSLF4Jの仕様やその仕組みを整理したいと思います。
前提
- 元ネタはSLF4J Manualサイトです。
- 2021年1月時点でSLF4Jは2.0系、1.8系、1.7系の3つがあります。
ここでは唯一の安定版である1.7系(1.7.30)を使用しています。
なお、1.8系に関してですが、途中から開発が2.0系に移行しており、安定版はリリースされないようです。詳細はこちらをご覧ください。 - maven(pom.xml)を使う前提の説明になります。
- 説明するサンプルはこちらで公開しています。
- 1.7系は古くなっており、最新のJDKのランタイムの機能を生かし切れていません…SLF4Jの2.0系安定版がリリースされたら、別途記事を書こうと思います…
- ログ全般の設計・実装ポリシーに興味がある方には、こちらが参考になれば幸いです。
SLF4jとは?
特定のログ出力の実装に依存しない、汎用的なログ出力API(クラス・メソッド)です。
これまでにJavaにおけるログ出力するためのライブラリとしてlog4j, JDK付属のLogging, log4j2, logback等が公開されてきました。それぞれのライブラリは、ログの出力方法(プログラム)が異なるため、例えばAPサーバ等の実行環境が変わった際にプログラムを修正する必要があります。このように、特定の実装(ライブラリ)に依存してプログラムを作ってしまうと、後からの変更に手間がかかったり困難になる場合があります。
このような問題を回避するために「インターフェイスと実装の分離」という考えを取り入れています。これは、プログラミングに必要となるAPI(インターフェイス)と、その具体的な処理内容(実装)を分離する、という考え方です。
SLF4Jでは、どのログ出力ライブラリを使ってもいいような汎用的なログ出力APIを提供するとともに、実行時にどのログ出力ライブラリを使用するかを指定するための機能を提供します。
こうすることで、プログラマは特定のログ出力ライブラリに依存しないプログラミングができるようになります。また、実行時に、要件や環境に合わせて柔軟にログ出力ライブラリを切り替えることが可能となります。
とりあえず動かしてみる
SLF4Jで”Hello World”をログに出力してみます。
- SLF4J API及びライブラリの追加
次のようにpom.xmlに定義し、mavenのプロジェクト更新を行います。
“slf4j-api”がSLF4J API(ログ出力の実装なし)、”slf4j-simple”がSLF4Jに対応するログ出力ライブラリ(後述のバインディング)です。12345678910111213141516...<dependencies><dependency><groupId>org.slf4j</groupId><artifactId>slf4j-api</artifactId><version>1.7.30</version></dependency><dependency><groupId>org.slf4j</groupId><artifactId>slf4j-simple</artifactId><version>1.7.30</version></dependency></dependencies>... - プログラム例1234567891011package ndw.slf4j.example;import org.slf4j.Logger;import org.slf4j.LoggerFactory;public class HelloWorld {public static void main(String[] args) {Logger logger = LoggerFactory.getLogger(HelloWorld.class);logger.info("Hello World");}}
- 実行結果
上記を実行すると次のようにログが出力されます。120:28:07:023 +0900 [main] INFO ndw.slf4j.example.HelloWorld - Hello World
SLF4J APIとバインディング
SLF4Jでは、汎用的なログ出力API(クラス群)を提供するSLF4J APIと、実行時に実際のログ出力を行うログ出力ライブラリで構成されます。
プログラミング時にはSLF4J APIのクラスを使ってログ出力のプログラムを作成することができますが、これだけではログ出力の機能を持っていないのでログ出力できません。そのため、少なくても実行時には「どのログ出力ライブラリを使うか?」を指定する必要があります。このログ出力ライブラリのことをバインディングと呼びます。
バインディングのイメージは次の図のようになります。
個人的には、そっと画面を閉じたくなる図ですが、記載されている文言を落ち着いて読めば難しい話ではありません。
バインディングには大きく分けて、(1)APIを直接実装しているネイティブバインディング、(2)既存のログ出力ライブラリにログ出力を任せるアダプタ、があります。
(図の一番左のように、バインディングを指定しない方法もありますが推奨される使い方ではなく、実行時する際にコンソールへ警告が出力されます。)
ネイティブバインディング
SLF4Jのクラス(インターフェース)を実装しているバインディングです。
アダプタレイヤのバインディングのようにログ出力要求を特定のログ出力ライブラリ用の要求に変換する必要はないので、無駄が少なく、比較的高速に動作します。
ライブラリ(jar) | 説明 |
---|---|
(図の一番右) slf4j-nop.jar | ログをどこにも出力しません。(全て破棄。) |
(図の右から2番目) slf4j-simple.jar | 標準エラー出力にログを出力します。 シンプルなアプリで使用する想定のものです。 個人的には、JUnitや独自ツールでログを出力する際に使用します。 |
(図の左から2番目) logback-classic.jar logback-core.jar | SLF4J APIを直接実装するlogbackログ出力ライブラリです。 後述のアダプタのように処理を別のログ出力ライブラリに任せるわけではないので、アダプタ型のバインディングに比べ、無駄なく動作します。 |
アダプタ
SLF4Jに対するログ出力要求を、log4jやjul等の既存のログ出力ライブラリ用のログ出力要求に変換します。
このような変換するためのバインディングを使用することで、既存のログ出力ライブラリを変更せずに使用できます。
ライブラリ(jar) | 説明 |
---|---|
(図の左から3番目) slf4j-log4j12.jar | ログ出力ライブラリとしてlog4j(バージョン1.2)を使用する場合のアダプタです。 ※log4jバージョン1.2は2018にサポート期限切れになっており、log4j2の使用が推奨される。 |
(図の右から3番目) slf4j-jdk14.jar | JDK 1.4ロギングを使う場合のアダプタです。 |
slf4j-jcl.jar | Jakarta Commons Loggingを使う場合のアダプタです。 |
SLF4Jを使用するための設定方法
- 基本的には、SLF4J APIとバインディングに対応するjarをpom.xmlに追加します。
アダプタレイヤのバインディングの場合、log4jやjcl等の変換先のログ出力ライブラリの追加も必要になります。 - バインディングの指定は1つ(jarが1つという意味ではない)だけです。例えばバインディングとしてlogbackを使う場合、logback-classic, logback-coreという2つのjarが必要になります。
アプリケーションサーバ等の特定の環境では、実行環境側でバインディングを提供する場合があるので、バインディングを指定しなくても、ログ出力が可能です。例えば、JBoss EAP7(APサーバ)では、既定でjboss-loggingというバインディングを提供するため、アプリ側でバインディングの指定は不要です。 - ここで説明するサンプルの完全なコードは次のをご覧ください。GitHub
Contribute to nextdoorwith/example development by creating a…
ネイティブバインディングを使う場合
slf4j-simpleを使用する場合
slf4j-simpleを使う場合のpom.xmlです。
slf4j-simpleは非常に単純なため、製造での動作確認やテストでの使用は良いと思います。実運用環境では、logbackやlog4j2等の別のものをお薦めします。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | ... <dependencies> <!-- SLF4J API ======================================== --> <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-api</artifactId> <version>1.7.30</version> </dependency> <!-- SLF4J binding ======================================== --> <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-simple</artifactId> <version>1.7.30</version> </dependency> </dependencies> ... |
Logbackを使用する場合
Logbackを使用する場合、logback-classicとlogback-coreを使用します。
使用するバージョンに関して、2021年1月現在でLogbackは1.3系と1.2系があります。
1.3系はJava8以上かつslf4jの1.8以上が必要となります。ここでは安定版のslf4jの1.7系を使用するので、logbackの1.2系を使用したサンプルを紹介します。
Logbackがサポートする環境の詳細は、LogbackのLogback Newsやリファレンスをご覧ください。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | ... <dependencies> <!-- SLF4J API ======================================== --> <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-api</artifactId> <version>1.7.30</version> </dependency> <!-- SLF4J binding ======================================== --> <dependency> <groupId>ch.qos.logback</groupId> <artifactId>logback-classic</artifactId> <version>1.2.3</version> </dependency> <dependency> <groupId>ch.qos.logback</groupId> <artifactId>logback-core</artifactId> <version>1.2.3</version> </dependency> </dependencies> ... |
log4j2を使用する場合
log4j2を使用する場合、log4j-slf4j-implを使用します。
(SLF4Jの1.8以上を使用する場合、log4j-slf4j18-implを使用する必要があります。詳細はリファレンスをご覧ください。)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | ... <dependencies> <!-- SLF4J API ======================================== --> <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-api</artifactId> <version>1.7.30</version> </dependency> <!-- SLF4J binding ======================================== --> <dependency> <groupId>org.apache.logging.log4j</groupId> <artifactId>log4j-slf4j-impl</artifactId> <version>2.14.0</version> </dependency> </dependencies> ... |
これだけだと、ログの出力を確認できません。
“src/main/resources”等のクラスパスに含まれるフォルダに、次のようなコンソールにログを出力するための設定ファイルを配置して実行してください。
設定ファイルの詳細はリファレンスのConfigurationやAppenders等をご覧ください。
1 2 3 4 5 6 7 8 9 10 11 12 13 | <?xml version="1.0" encoding="UTF-8"?> <Configuration> <Appenders> <Console name="STDOUT" target="SYSTEM_OUT"> <PatternLayout pattern="%d{HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n" /> </Console> </Appenders> <Loggers> <Root level="info"> <AppenderRef ref="STDOUT" /> </Root> </Loggers> </Configuration> |
アダプタバインディングを使う場合
個別のログ出力ライブラリの話になるため、ここでは割愛します。
ポピュラーなものについては、別途記事にする予定です。
よくあるエラーの説明
バインディングの指定がない場合は、次のように警告が標準エラーに出力されます。
SLF4J: Failed to load class "org.slf4j.impl.StaticLoggerBinder".SLF4J: Defaulting to no-operation (NOP) logger implementationSLF4J: See http://www.slf4j.org/codes.html#StaticLoggerBinder for further details.
バインディングの指定が複数ある場合は、次のように警告が標準エラーに出力されます。
(slf4j-simpleとslf4j-log4j12を指定した場合の例)
SLF4J: Class path contains multiple SLF4J bindings.SLF4J: Found binding in [jar:file:/C:/.../org/slf4j/impl/StaticLoggerBinder.class]SLF4J: Found binding in [jar:file:/C:/.../org/slf4j/impl/StaticLoggerBinder.class]SLF4J: See http://www.slf4j.org/codes.html#multiple_bindings for an explanation.SLF4J: Actual binding is of type [org.slf4j.impl.SimpleLoggerFactory]
SLF4Jへの移行
log4j, jcl等を使って既に実装されているログ出力処理を段階的にSLF4Jに移行するために使用するものがブリッジです。
公式サイトのコンセプトは次の通りです。
ブリッジは、log4jやjcl等のログ出力APIのフリをしますが、そのAPIの中ではSLF4JのAPIを実行します。こうすることで、log4jやjcl等のログ出力APIを使うプログラムを修正せずに、ログ出力をSLF4Jに統一できます。SLF4Jからどのようにログ出力するかについては、前述の通り、バインディングの設定が必要になります。
代表的なブリッジは次の通りです。
ライブラリ(jar) | 説明 |
---|---|
log4j-over-slf4j.jar | log4jのログ出力をSLF4Jに転送します。 |
jcl-over-slf4j.jar | Jakarta Commons Loggingのログ出力をSLF4Jに転送します。 |
jul-to-slf4j.jar | julのログ出力をSLF4J APIに転送します。 |
jclやlog4jの場合は”xxx-over-slf4j”なのに、julの場合だけ”xxx-to-slf4j”になっている理由についてです。
例えばlog4j-over-slf4jの場合、log4jのフリをするためにlog4jと同じパッケージ名を使ったクラスを提供します。両者のjarがクラスパスにあると、パッケージ名重複エラーになってしまうので、クラスパス上からlog4jを除外し、log4j-over-slf4jを配置します。こうすることで、呼び出し側のプログラムを修正せずに、slf4jのログ出力が可能となります。
julの場合、パッケージ名”java.util.logging”はJava実行環境に含まれており、上記のように「クラスパスから除外」ということができません。そのため、julの場合のみ別の仕組みで実現しています。
前者の場合、SLF4J APIの上に別のログ出力APIをかぶせるイメージでoverと思われます。後者の場合は、julの出力をslf4jに転送するイメージでtoになっていると思われます。
活用事例やより高度な参考
実業務を想定した活用事例や参考を紹介します。