jul-to-slf4jのしくみ

# jul-to-slf4jとは
* `jul-to-slf4j`とは、`jul`のAPI経由でロギングを行っていても、`slf4j`に処理を移譲してくれる便利なやつです。
* `jul`とは、`java.util.logging`パッケージのことで、JavaにおけるデフォルトのロギングAPIです。
* `slf4j`とは、ログファサードライブラリです。
    * ファサードとは、Gofのデザインパターンの一つの**ファサードパターン**からきています。
        * ファサードパターンとは、僕の理解では複数の処理系の実行を呼び出し側から見て単純化するための窓口、インタフェース(≠言語としての`interface`)という感じだと思ってます。
        * `slf4j`では、上記でいう複数の処理系は`logback`、`log4j(2)`等の各ロギングライブラリを指し、それらの処理に対するインタフェースとしての役割を持ちます。
            * 実際に、`logback`や`log4j2`のAPIを直接叩くことは昨今ではあまり無いのではないでしょうか。実際に動くロギングライブラリ毎の設定自体はどうやっても無くしようがないですが。
* 以外といろんなものがこいつに依存してます。
* 古いライブラリとかを使っていると`jul`に直接依存している場合がまぁありますが、その場合であっても`slf4j`に透過的に処理を移譲されるようにしてくれるのがこいつ、`jul-to-slf4j`です。
    * 実際には、どこかで（可能な限りVM起動直後）で1つAPIを叩くことで適用されます。
    * さらにいうと、大抵のフレームワークは初期化時に上記のAPIを叩いてくれるので、ほとんどの場合アプリケーション実装者は`jul-to-slf4j`を意識する必要はありません。

# 使用方法
```java
SLF4JBridgeHandler.install();
```
って叩くだけです。

* くどいようですが、ほとんどの場合アプリケーション実装者は上記APIを意識する必要はありません。
* ただ、Java EEの上でフレームワークを挟まず直接アプリケーションを開発している場合等はその限りでは無いかと思います。
 

# 使用例

## `Spring Boot`のなかでは

[参考URL](https://github.com/spring-projects/spring-boot/blob/master/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/logging/Slf4JLoggingSystem.java)

```Slf4jLoggingSystem.java
...
	private void configureJdkLoggingBridgeHandler() {
		try {
			if (isBridgeHandlerAvailable()) {
				removeJdkLoggingBridgeHandler();
				SLF4JBridgeHandler.install();
			}
		}
		catch (Throwable ex) {
			// Ignore. No java.util.logging bridge is installed.
		}
	}
...
```

* Spring Bootのロギングシステムの初期化の仕組みは、また今度書きたいと思います。

## `Play framework`のなかでは

[参考URL](https://github.com/playframework/playframework/blob/master/framework/src/play-logback/src/main/scala/play/api/libs/logback/LogbackLoggerConfigurator.scala)

```LogbackLoggerConfigurator.scala
...
  def configure(properties: Map[String, String], config: Option[URL]): Unit = {
    // Touching LoggerContext is not thread-safe, and so if you run several
    // application tests at the same time (spec2 / scalatest with "new WithApplication()")
    // then you will see NullPointerException as the array list loggerContextListenerList
    // is accessed concurrently from several different threads.
    //
    // The workaround is to use a synchronized block around a singleton
    // instance -- in this case, we use the StaticLoggerBinder's loggerFactory.
    loggerFactory.synchronized {
      // Redirect JUL -> SL4FJ

      // Remove existing handlers from JUL
      SLF4JBridgeHandler.removeHandlersForRootLogger()

      // Configure logback
      val ctx = loggerFactory.asInstanceOf[LoggerContext]

      // Set a level change propagator to minimize the overhead of JUL
      //
      // Please note that translating a java.util.logging event into SLF4J incurs the
      // cost of constructing LogRecord instance regardless of whether the SLF4J logger
      // is disabled for the given level. Consequently, j.u.l. to SLF4J translation can
      // seriously increase the cost of disabled logging statements (60 fold or 6000%
      // increase) and measurably impact the performance of enabled log statements
      // (20% overall increase). Please note that as of logback-version 0.9.25,
      // it is possible to completely eliminate the 60 fold translation overhead for
      // disabled log statements with the help of LevelChangePropagator.
      //
      // https://www.slf4j.org/api/org/slf4j/bridge/SLF4JBridgeHandler.html
      // https://logback.qos.ch/manual/configuration.html#LevelChangePropagator
      val levelChangePropagator = new LevelChangePropagator()
      levelChangePropagator.setContext(ctx)
      levelChangePropagator.setResetJUL(true)
      ctx.addListener(levelChangePropagator)
      SLF4JBridgeHandler.install()

      ctx.reset()

      // Ensure that play.Logger and play.api.Logger are ignored when detecting file name and line number for
      // logging
      val frameworkPackages = ctx.getFrameworkPackages
      frameworkPackages.add(classOf[play.Logger].getName)
      frameworkPackages.add(classOf[play.api.Logger].getName)

      properties.foreach { case (k, v) => ctx.putProperty(k, v) }

      config match {
        case Some(url) =>
          val initializer = new ContextInitializer(ctx)
          initializer.configureByResource(url)
        case None =>
          System.err.println("Could not detect a logback configuration file, not configuring logback")
      }

      StatusPrinter.printIfErrorsOccured(ctx)

    }
  }
...
```

ウッ、`scala` ...

# で、どういうしくみなの？

## jul-to-slf4jの実装

こんな感じになってる。

```SLF4JBridgeHandler.java
    public static void install() {
        LogManager.getLogManager().getLogger("").addHandler(new SLF4JBridgeHandler());
    }

...

    public void publish(LogRecord record) {
        // Silently ignore null records.
        if (record == null) {
            return;
        }

        Logger slf4jLogger = getSLF4JLogger(record);
        // this is a check to avoid calling the underlying logging system
        // with a null message. While it is legitimate to invoke j.u.l. with
        // a null message, other logging frameworks do not support this.
        // see also http://jira.qos.ch/browse/SLF4J-99
        if (record.getMessage() == null) {
            record.setMessage("");
        }
        if (slf4jLogger instanceof LocationAwareLogger) {
            callLocationAwareLogger((LocationAwareLogger) slf4jLogger, record);
        } else {
            callPlainSLF4JLogger(slf4jLogger, record);
        }
    }

```

結論から、行われていることだけ順を追ってみると

* `SLF4JBridgeHandler#install()`で`jul`上のルートロガーに`java.util.logging.Handler`を継承した`SLF4JBridgeHandler`を追加
* `java.util.logging.logger#info()`等と叩いた際に[なんだかんだ](http://grepcode.com/file/repository.grepcode.com/java/root/jdk/openjdk/6-b14/java/util/logging/Logger.java#Logger.log%28java.util.logging.LogRecord%29)あって`java.util.logging.Handler#publish()`に処理が移譲
* `SLF4JBridgeHandler#publish()`ではさらに`Slf4j`のロガーの処理を移譲しているため、`jul`経由のロギングは、`SLF4JBridgeHandler#install()`を実行しておくことで全て`slf4j`へ処理が移譲される。

## 詳しくみていく

`java.util.logging.Handler`は何者かというと、[JavaDoc](https://docs.oracle.com/javase/jp/8/docs/api/java/util/logging/Handler.html)によると、

> Handlerオブジェクトは、Loggerからログ・メッセージを受け取り、それらをエクスポートします。たとえば、このオブジェクトは、コンソールやファイルに書き込み、ネットワーク・ログ・サービスに送信し、OSログへの転送などを実行します。

とあります。
つまり`Logger`は、アプリケーションから見たインタフェースであり、実際に物理的なログ出力を行ってくれるのがこの`Handler`って感じでしょうか。
僕はなんとなく`logback`でいうところのアペンダーのような存在と理解しました。

そんでもって`LogManager`は何者かというと、`import`文を読む限りjulのクラスです。

```SLF4JBridgeHandler.java
import java.util.logging.LogManager;
```

また、`getLogger("")`というふうに、ロガー名として空文字を指定しているのがわかると思います。これはなんなのでしょうか。
名前が無いのでなんとなく想像が付かないわけでも無いですが、どうやら空文字のロガー名を指定するとルートロガーを取得できるらしいです。

```SLF4JBridgeHandler.java
    private static java.util.logging.Logger getRootLogger() {
        return LogManager.getLogManager().getLogger("");
    }
```

ここで前提として、他のロギングAPIの例に漏れず`jul`も全ロガーは子孫関係を持ち、必ずルートを継承します。
つまり、`jul`上のルートロガーに対して`SLF4JBridgeHandler`を追加することは、`jul`経由でのロギングはすべて`slf4j`に移譲されるということを意味します。

# 参考

* [slf4j/SLF4JBridgeHandler.java at master · qos-ch/slf4j](https://github.com/qos-ch/slf4j/blob/master/jul-to-slf4j/src/main/java/org/slf4j/bridge/SLF4JBridgeHandler.java)

* [GC: Logger - java.util.logging.Logger (.java) - GrepCode Class Source](http://grepcode.com/file/repository.grepcode.com/java/root/jdk/openjdk/6-b14/java/util/logging/Logger.java#Logger.log%28java.util.logging.LogRecord%29)

* [やっとわかった、java.util.logging – Akira Koyasu's WebLog](http://www.akirakoyasu.net/2011/11/06/finally-understood-java-util-logging/)

* [java.util.logging (Java Platform SE 8 )](https://docs.oracle.com/javase/jp/8/docs/api/java/util/logging/package-summary.html)