Javaのロガーの種類が多すぎ、一元化したい

Java のロガーってたくさんありますね。[Guava][] は [java.util.logging.Logger][jul] を使ってますし、[Spring Framework][] や Apache 関連のライブラリは [Apache Commons Logging][jcl] を使っています。パッと用例が出て来ませんが、[Log4J][] や、[SLF4J][] もあります。

[Guava]: https://code.google.com/p/guava-libraries/
[Spring Framework]: http://www.springsource.org/spring-framework
[jul]: http://docs.oracle.com/javase/jp/7/api/java/util/logging/Logger.html
[jcl]: http://commons.apache.org/proper/commons-logging/
[Log4J]: http://logging.apache.org/log4j/2.x/
[SLF4J]: http://www.slf4j.org/

まとめると有名所だけでも、これだけあります:

* [java.util.logging.Logger][jul]
* [Apache Commons Logging][jcl]
* [Log4J][]
* [SLF4J][]

<del>うんざりですね。</del>

これらを依存ライブラリがバラバラに使っていると、ログレベルごとの出力制御が一元化できなかったり、ログフォーマットのバラツキが発生しやすくなり、つらいです。

上に上がっている SLF4J には、これらの出力をブリッジして1つにまとめることのができるモジュールがあります。

この記事ではこれらのモジュールについて簡単にメモします。

ここに書いてあることは、基本的には [Log4j Bridge][] を見れば全て書いてありますので詳しく知りたい方は参照してください。

これを書くにあたり、以下のようなサンプルを作りました:

* [kui/logger-integration-sample · GitHub](https://github.com/kui/logger-integration-sample)

[Log4j Bridge]: http://www.slf4j.org/legacy.html

それぞれをブリッジしてくれるライブラリ
---------------------------------

以下のようになっています:

* java.util.logging.Logger → [jul-to-slf4j](http://search.maven.org/#artifactdetails%7Corg.slf4j%7Cjul-to-slf4j%7C1.7.5%7Cjar)
* Apache Commons Logging →[jcl-over-slf4j](http://search.maven.org/#artifactdetails%7Corg.slf4j%7Cjcl-over-slf4j%7C1.7.5%7Cjar)
* Log4J → [log4j-over-slf4j](http://search.maven.org/#artifactdetails%7Corg.slf4j%7Clog4j-over-slf4j%7C1.7.5%7Cjar)

`XXX-over-sjf4j` に関しては、maven の依存関係や、クラスパスに追加すれば自動的にブリッジしてくれるようになります。ただし、他の依存関係によって読み込んでいる `commons-logging` や `log4j` を [exclude しないと][exclusion] 予期しない挙動を起こす可能性があります。

`jul-to-slf4j` は他の2つと違い、ブリッジの実装が違うため、使い方も少し手間が必要になります。

[exclusion]: http://maven.apache.org/guides/introduction/introduction-to-optional-and-excludes-dependencies.html#Dependency_Exclusions

`jul-to-slf4j` によるブリッジ実現方法
-------------------------------------------------

[SLF4JBridgeHandler の javadoc][SLF4JBridgeHandler] にも書いてありますが、`java.util.logging.Logger` の出力をブリッジするには以下のようなメソッドの呼び出しが必要になります:

```java
SLF4JBridgeHandler.removeHandlersForRootLogger();
SLF4JBridgeHandler.install();
```

[サンプルではこう書きました。](https://github.com/kui/logger-integration-sample/blob/master/src/main/java/Main.java)

これ以降は、`java.util.logging.Logger` 自身の出力は抑制され（一行目）、代わりに SLF4J がログをとってくれるようになります（二行目）。

ただし、上記した [Log4j Bridge][] も書いてあるとおり注意が必要になります。

[SLF4JBridgeHandler]: http://www.slf4j.org/api/org/slf4j/bridge/SLF4JBridgeHandler.html

`jul-to-slf4j` の注意点
-------------------------------------------------

端的に言うと、ブリッジのオーバーヘッドがあまりにも大きいようです。

これに関しては、[Log4j Bridge の jul-to-slf4j](http://www.slf4j.org/legacy.html#jul-to-slf4j) の "Note on performance" に書いてあります:

> Consequently, j.u.l. to SLF4J translation can seriously increase the cost of disabled logging statements (60 fold or 6000%) and measurably impact the performance of enabled log statements (20% overall increase).

対策としては、`logback.xml` に以下のようなリスナーを追加すると、オーバーヘッドを軽減できるようです:

```xml
<configuration>
  <contextListener class="ch.qos.logback.classic.jul.LevelChangePropagator">
    <resetJUL>true</resetJUL>
  </contextListener>
  ...
</configuration>
```

[サンプルではこう書きました。](https://github.com/kui/logger-integration-sample/blob/master/src/main/resources/logback.xml)

以上です。おわり。