アプリ開発ときどきアウトドア

主にJavaを使ったアプリ開発やトラブルシューティング等のノウハウ、キャンプや登山の紹介や体験談など。

1. システムエンジニアリング Java 実装技術

SLF4Jの仕組みと使用方法

投稿日:2019年1月12日 更新日:


何気に使用しているSLF4Jの仕様やその仕組みを整理したいと思います。

前提

SLF4jとは?

特定のログ出力の実装に依存しない、汎用的なログ出力API(クラス・メソッド)です。

これまでにJavaにおけるログ出力するためのライブラリとしてlog4j, JDK付属のLogging, log4j2, logback等が公開されてきました。それぞれのライブラリは、ログの出力方法(プログラム)が異なるため、例えばAPサーバ等の実行環境が変わった際にプログラムを修正する必要があります。このように、特定の実装(ライブラリ)に依存してプログラムを作ってしまうと、後からの変更に手間がかかったり困難になる場合があります。

このような問題を回避するために「インターフェイスと実装の分離」という考えを取り入れています。これは、プログラミングに必要となるAPI(インターフェイス)と、その具体的な処理内容(実装)を分離する、という考え方です。
SLF4Jでは、どのログ出力ライブラリを使ってもいいような汎用的なログ出力APIを提供するとともに、実行時にどのログ出力ライブラリを使用するかを指定するための機能を提供します。
こうすることで、プログラマは特定のログ出力ライブラリに依存しないプログラミングができるようになります。また、実行時に、要件や環境に合わせて柔軟にログ出力ライブラリを切り替えることが可能となります。

とりあえず動かしてみる

maven(pom.xml)に、SLF4J APIライブラリを追加を追加します。
アプリでは、SLF4Jのロガーを使って任意のログを出力します。

  1. SLF4J API及びライブラリの追加
    pom.xmlに次を追加し、mavenのプロジェクト更新を行います。
    “slf4j-api”がSLF4J API(ログ出力の実装なし)、”slf4j-simple”がSLF4Jに対応するログ出力ライブラリ(後述のバインディング)です。

    ...
    	<dependencies>
    		<dependency>
    			<groupId>org.slf4j</groupId>
    			<artifactId>slf4j-api</artifactId>
    			<version>1.7.25</version>
    		</dependency>
    		<dependency>
    			<groupId>org.slf4j</groupId>
    			<artifactId>slf4j-simple</artifactId>
    			<version>1.7.25</version>
    		</dependency>
    	</dependencies>
    ...
    
  2. プログラム例
    package 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");
    	}
    }
    
  3. 実行結果
    上記を実行すると次のようにログが出力されます。

    20:28:07:023 +0900 [main] INFO ndw.slf4j.example.HelloWorld - Hello World
    

SLF4J APIとバインディング

SLF4Jでは、汎用的なログ出力API(クラス群)を提供するSLF4J APIと、実行時に実際のログ出力を行うログ出力ライブラリで構成されます。
プログラミング時にはSLF4J APIのクラスを使ってログ出力のプログラムを作成することができますが、これだけではログ出力の機能を持っていないのでログ出力できません。そのため、少なくても実行時には「どのログ出力ライブラリを使うか?」を指定する必要があります。このログ出力ライブラリのことをバインディングと呼びます。
バインディングのイメージは次の図のようになります。

公式サイト引用:

個人的には、そっと画面を閉じたくなる図ですが、記載されている文言を落ち着いて読めば難しい話ではありません。
バインディングには大きく分けて、(1)APIを直接実装しているネイティブバインディング、(2)既存のログ出力ライブラリにログ出力を任せるためのアダプタレイヤ、があります。
(図の一番左のように、バインディングを指定しない方法もありますが推奨される使い方ではなく、実行時する際にコンソールへ警告が出力されます。)

(1)ネイティブバインディング:
SLF4Jのクラス(インターフェース)を実装しているバインディングです。
アダプタレイヤのバインディングのようにログ出力要求を特定のログ出力ライブラリ用の要求に変換する必要はないので、無駄が少なく、比較的高速に動作します。

ライブラリ(jar) 説明
(図の一番右)
slf4j-nop.jar
ログをどこにも出力しません。(全て破棄。)
(図の右から2番目)
slf4j-simple.jar
標準エラー出力にログを出力します。
シンプルなアプリで使用する想定のものです。
個人的には、JUnitや独自ツールでログを出力する際に使用します。
(図の左から2番目)
logback-classic.jar
logback-core.jar
SLF4J APIを直接実装するlogbackログ出力ライブラリです。
後述のアダプタのように処理を別のログ出力ライブラリに任せるわけではないので、アダプタ型のバインディングに比べ、無駄なく動作します。

(2)アダプタレイヤ:
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というバインディングを提供するため、アプリ側でバインディングの指定は不要です。

(1)ネイティブバインディングを使う場合
下記の例では、SLF4J APIとバインディングとしてslf4j-simpleを使う場合のpom.xmlです。
参考として、slf4j-nopやlogbackバインディングを使う場合の定義もコメントアウトした状態で記載しました。
これらのバインディングを使う場合、その他のバインディングをコメントアウトしてください。

<project ...>
...
	<dependencies>

		<!-- SLF4J API ========================================-->
		<dependency>
			<groupId>org.slf4j</groupId>
			<artifactId>slf4j-api</artifactId>
			<version>1.7.25</version>
		</dependency>

		<!-- SLF4J binding ========================================-->

		<!-- slf4j-nopを使う場合-->
<!-- 		<dependency> -->
<!-- 			<groupId>org.slf4j</groupId> -->
<!-- 			<artifactId>slf4j-nop</artifactId> -->
<!-- 			<version>1.7.25</version> -->
<!-- 		</dependency> -->

		<!-- slf4j-simpleを使う場合 -->
		<dependency>
			<groupId>org.slf4j</groupId>
			<artifactId>slf4j-simple</artifactId>
			<version>1.7.25</version>
		</dependency>

		<!-- logbackを使う場合 -->
		<!-- サポートするSLF4Jバージョンは下記を参照のこと。-->
		<!-- https://logback.qos.ch/dependencies.html -->
<!-- 		<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>
...

(2)アダプタバインディングを使う場合
個別のログ出力ライブラリの話になるため、ここでは割愛します。
ポピュラーなものについては、別途記事にする予定です。

【補足】
バインディングの指定がない場合は、次のように警告が標準エラーに出力されます。

SLF4J: Failed to load class "org.slf4j.impl.StaticLoggerBinder".
SLF4J: Defaulting to no-operation (NOP) logger implementation
SLF4J: 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になっていると思われます。



(adsbygoogle = window.adsbygoogle || []).push({});


(adsbygoogle = window.adsbygoogle || []).push({});

-1. システムエンジニアリング, Java, 実装技術

執筆者:


comment

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です

関連記事

リモートからのwarデプロイの自動化

JavaEEベースのツールを公開しているが、デプロイの都度、warファイルをサーバにコピーしてwildflyにデプロイするのが面倒なので、mavenで自動化しました。 前提 mavenのプラグインと後 …

Webアプリテスト用のHTTPヘッダの追加

フロントに配置されたリバースプロキシサーバやロードバランサで設定されたHTTPヘッダを使用するWebアプリを開発することが多々あります。 このようなシナリオでは、設計に基づいて実装することはできますが …

JavaEE7のJSF, Facelets, JSPの関係

JavaEEを使ったアプリ開発の際に、いつも気になるが後回しにしていたこと… HTML5への対応方法の調査等、今後の理解促進のために、調べてみた。 FaceletsとJSFとの関係は? J …

JSFラジオボタン・チェックボックスとbootstrap

boostrapでラジオボタンやチェックボックスを使用する場合、div, input, label要素にbootstrapのCSSクラスを指定する必要がある。 JSFでラジオボタンを使用する場合、se …

DB操作フレームワーク はJPA or mybatis?

開発に向けた準備で、開発標準を準備するフレームワーク(FW)チーム、それらを使って実装を行う業務チームが集まって、「DB操作を行うためのFWは何を使うか?」という協議になった。 FWチームは、FW・J …

プロフィール ゆっきーです。
都内でシステムエンジニアをやっています。
もっと詳細を見る