Java でロガーを SLF4J + Logback に統一する冴えたやり方 / build.gradle 記入法 / 2021年

SLF4J を使いたいこんな人向けの記事です

* log4j 2.x を使っていない事を確かにしたい (2021年12月追記)
* SLF4J に統一したはずなのに、たまにコンソールに謎ログが出てる。時刻が出ないとかある（実は log4j 1.x 経由でログがでてる）
  * あるいは `log4j:WARN No appenders could be found for logger (com.example.Log4j1).
log4j:WARN Please initialize the log4j system properly.
log4j:WARN See http://logging.apache.org/log4j/1.2/faq.html#noconfig for more info.` みたいなログが出る。
* `Class path contains multiple SLF4J bindings. SLF4J: See http://www.slf4j.org/codes.html#multiple_bindings for an explanation.` なるログが出る
* `ERROR StatusLogger Log4j2 could not find a logging implementation. Please add log4j-core to the classpath. Using SimpleLogger to log to the console...` なるログができる
* gradle/maven で exclude すると除外できるのは知ってるけど、新規ライブラリを追加したタイミングで log4j の実装が混入してログがでなくなったりしている
* まとめて log4j や commons-logging を取り除きたい

Update
----
* Spring Boot 2.0 でだいぶ世界観が変わったので書き直しました

Tips
----

### 除外について
以下の構文で一括 除外 ができる

```groovy
configurations.all {
    exclude
}
```

### 依存バージョンの統一

[3\.2\. Using Spring Boot’s dependency management in isolation](https://docs.spring.io/spring-boot/docs/current/gradle-plugin/reference/html/#managing-dependencies-using-in-isolation) が便利

Spring Boot を使っていなくても Gradle Plugin だと思って使っておくとよさそう。

```kotlin:build.gradle.kts
plugins {
    id("org.springframework.boot") version "2.6.5" apply false
}

apply {
    plugin("io.spring.dependency-management")
}

dependencyManagement {
    imports {
        mavenBom(org.springframework.boot.gradle.plugin.SpringBootPlugin.BOM_COORDINATES)
    }
}
```

これで
* 実際使う時にはバージョンを個別に書く必要がなくなります

```gradle:build.gradle
dependencies {
	implementation("org.springframework.boot:spring-boot-starter-web")
}
```

### 複数の SLF4J jar があると怒られる Class path contains multiple SLF4J bindings. について

IntelliJ の Issue のようだけど、依存ライブラリで SLF4J を使っていて、runtime で別のバージョンのライブラリを導入した場合、IntelliJ 内から Debug/Run したときに両方の Jar が Classpath に含まれてしまう問題があるようだ。。

基本的に、dependencyManagement をしておけば大丈夫。


TL;DR
======

Pattern 1
---------

全てのプロジェクトに 各種ログライブラリ > SLF4J で出力、に必要なモジュールを入れて、各種ログライブラリの実装のほうを Exclude してやればよい。

```kotlin:build.gradle.kts
buildscript {
    repositories {
        maven {
            url("https://plugins.gradle.org/m2/")
        }
    }
    dependencies {
        classpath("org.springframework.boot:spring-boot-gradle-plugin:2.6.5")
    }
}

subprojects {
    apply {
        plugin("org.gradle.java")
        plugin("io.spring.dependency-management")
    }

    repositories {
        mavenCentral()
    }

    dependencyManagement {
        imports {
            mavenBom(org.springframework.boot.gradle.plugin.SpringBootPlugin.BOM_COORDINATES) {
                extra["log4j2.version"] = "2.15.0"
                // Force update log4j2 from latest spring-boot preferred version.
                // It's not required to avoid CVE-2021-44228 because we don't use log4j2,
                // but seeing log4j-api < 2.15.0 is confusing.
            }
        }
    }

    dependencies {
        // Used compile/coding time.
        implementation("org.springframework.boot:spring-boot-starter-logging") // 実際は starter-web の依存とかに入ってることが多い

        // Used Runtime
        runtimeOnly("org.slf4j:log4j-over-slf4j")
        // log4j 1.x だけ個別にルーティングする必要がある
        // 2015 年に EOL のため 2017 年に spring-boot-starter-logging からも取り除かれている
        // https://github.com/spring-projects/spring-boot/issues/11148

        testImplementation("org.springframework.boot:spring-boot-starter-test")
    }

    configurations.all {
        // We are using logback for logging implementation (formatting & appenders).
        // Other logging implementation and "from slf4j bridge" are not necessary.
        // Let's remove those.
        exclude(group = "log4j") // = Old Log4j (1.x) implementation. Replaced by log4j-over-slf4j.
        exclude(module = "slf4j-log4j12") // = SLF4J -> Log4J Implementation by SLF4J. Not used (or cyclic dependences lol).
        exclude(module = "slf4j-jdk14") // = SLF4J -> JDK14 Binding. Not used.
        exclude(module = "slf4j-jcl") // = SLF4J -> Commons Logging. Not used.
        exclude(module = "commons-logging") // Because bridged by org.slf4j:jcl-over-slf4j.
        exclude(module = "commons-logging-api") // Replaced by org.slf4j:jcl-over-slf4j.
        exclude(group = "org.apache.logging.log4j", module = "log4j-core") // Remove log4j 2.x implementation. org.apache.logging.log4j:log4j-to-slf4j (Included in spring-boot-starter-logging) proxies log4j-api:2.x to slf4j.
    }
}

```

Pattern 2
------
ログの設定などを含めたログ専用プロジェクトを作ってしまうのもおすすめ

後で書く

```groovy:build.gradle

```

```groovy:log/build.gradle
```