Spring Boot でロギングライブラリをLog4j2にする

Spring Boot のロギングライブラリのデフォルトは Logback で、ファサードとして SLF4J が使われる。
これを log4j2 に切り替える場合の設定を残しておく

## log4j2 のバージョンをカスタマイズする

12/11 追記: log4j2 の脆弱性の話があったので、log4j2 のバージョンをカスタマイズする方法を追記

```build.gradle.kts
...
apply(plugin = "io.spring.dependency-management")
...
extra["log4j2.version"] = "2.15.0"
...
```

- `io.spring.dependency-management` プラグインを適用して SpringBoot アプリケーションの依存関係を管理しておく（多分、大体追記されている？？）
- `spring boot`のバージョンがアップデートされて log4j2 のバージョンも上がったら、`extra["log4j2.version"] = "2.15.0"` は削除しないと逆に 2.15.0 に固定されてしまう
- 上記は Kotlin DSL の場合。Groovy なら `ext['log4j2.version'] = '2.15.0'`

参考リンク
[Spring Boot Gradle プラグインリファレンスガイド - 3.1.1. 管理バージョンのカスタマイズ](https://spring.pleiades.io/spring-boot/docs/2.6.1/gradle-plugin/reference/htmlsingle/#managing-dependencies.dependency-management-plugin.customizing)
[依存バージョン - 2.バージョンプロパティ](https://spring.pleiades.io/spring-boot/docs/current/reference/html/dependency-versions.html#dependency-versions.properties)
[log4j2 のバージョンアップのやりかた](https://irof.hateblo.jp/entry/2021/12/10/180215)

## 調査時のバージョン

`gradle`: 6.9
`spring-boot-starter-web`: 2.5.0
`spring-boot-starter-log4j2`: 2.5.0
`spring-boot-starter-aop`: 2.5.0
`jdk`: liberica 11.0.11

## ロギングライブラリを Logback から log4j2 に切り替える

1. spring-boot-starter-log4js を依存関係に含める
1. spring-boot-starter-logging を依存関係から除外する

```build.gradle.kts
...

dependencies {
    implementation("org.springframework.boot:spring-boot-starter-web")
    implementation("org.springframework.boot:spring-boot-starter-log4j2")
    implementation("org.springframework.boot:spring-boot-starter-aop")
    testImplementation("org.springframework.boot:spring-boot-starter-test")
}

configurations {
    all {
        exclude("org.springframework.boot", "spring-boot-starter-logging")
    }
}

...
```

### LoggingException が 発生する場合

maven だと、spring-boot-starter-web に対して、spring-boot-starter-logging を除外(`<exclusion>`)するとあったので、gradle でもそれに従って記述したが、起動時に`Caused by: org.apache.logging.log4j.LoggingException: log4j-slf4j-impl cannot be present with log4j-to-slf4j`が発生してしまった。

[stackoverflow](https://stackoverflow.com/questions/59629214/caused-by-org-apache-logging-log4j-loggingexception-log4j-slf4j-impl-cannot-be) にて「全てのライブラリから spring-boot-starter-logging を除外する必要がある」ことがわかったので、configuration.all で除外するように書き換えると log4j2 に切り替わった。

```build.gradle.kts
// LogingExceptionが発生する書き方
implementation("org.springframework.boot:spring-boot-starter-web"){
    exclude("org.springframework.boot","spring-boot-starter-logging")
}

// このように全ての依存関係から、spring-boot-starter-logging を除外する必要がある
configurations {
    all {
        exclude("org.springframework.boot", "spring-boot-starter-logging")
    }
}
```

### GradleならModule replacement が使える

`exclude` で除外しなくても dependenciesブロック内で次のように定義することで、spring-boot-starter-loggingを参照する場面で、spring-boot-starter-log4j2 に置き換えてくれる

``` build.gradle.kts
dependencies {
    implementation("org.springframework.boot:spring-boot-starter-log4j2")
    modules {
        module("org.springframework.boot:spring-boot-starter-logging") {
            replacedBy("org.springframework.boot:spring-boot-starter-log4j2", "Use Log4j2 instead of Logback")
        }
    }
}
```

https://docs.spring.io/spring-boot/docs/3.0.3/reference/htmlsingle/#howto.logging.log4j

## ロギングファサードもSLF4J から Log4j2 に切り替える

ここまでの設定だとログ出力をlogbackからlog4j2に変えただけであり、ロギングのファサードは slf4jのまま。つまり slf4j + log4j2 に切り替わったことになる。ロギングファサードもsl4jからlog4j2 に変えたい場合は、Logger クラスを変えれば良い

```kotlin

import org.apache.logging.log4j.LogManager
import org.apache.logging.log4j.Logger

// LogManagerクラスからLoggerクラスを取得する
private val logger: Logger = LogManager.getLogger(LoggingAdvice::class.java)

// SLF4J を使う場合は、LoggerFactoryを使う
//private val logger = LoggerFactory.getLogger(LoggingAdvice::class.java)

@Aspect
@Component
class LoggingAdvice {

...

}
```

:::note info
(2023.03.29補足)
ロギングファサードは SLF4J を使った方が ログ出力はlog4j2/logbackのどちらにも対応するので、無理にlog4j2に切り替えなくても良い気がするのが最近のお気持ち
:::

## log4j2 の設定ファイル (参考)

Spring における log4j2 のロギング設定は、 `./src/main/resources/log4j2-spring.xml` に記述することでカスタマイズできる。

log4j2 の設定ファイルは YAML や JSON でも書けるが、Spring からは、XML 形式でないと参照しない模様。
また、ファイル名は `log4j2-spring.xml` と、'-spring' をつけないと起動時のロード順によって 設定ファイルから Springのプロパティが読み込めないので注意する。

> 「[4.8.カスタムログ設定](https://spring.pleiades.io/spring-boot/docs/current/reference/html/features.html#features.logging)」 より
> 可能な場合、ロギング構成に -spring バリアントを使用することをお勧めします（たとえば、logback.xml ではなく logback-spring.xml）。標準の構成場所を使用する場合、Spring はログの初期化を完全に制御できません。

以下は、設定例

```log4j2-spring.xml
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
    <Appenders>
        <Console name="Console" target="SYSTEM_OUT">
            <PatternLayout
                    pattern="%d{yyyy-MM-dd HH:mm:ss.SSS} %highlight{%-5level}[%style{%t}{bright,blue}] %style{%C}{bright,yellow}: %m%n"/>
        </Console>
        <RollingFile name="RollingFile"
                     fileName="./logs/spring-boot-logger-log4j2.log"
                     filePattern="./logs/$${date:yyyy-MM}/spring-boot-log4j2-%d{-dd-MMMM-yyyy}-%i.log.gz">
            <PatternLayout>
                <pattern>%d %p %C [%t] %m%n</pattern>
            </PatternLayout>
            <Policies>
                <OnStartupTriggeringPolicy/>
                <SizeBasedTriggeringPolicy size="10 MB"/>
                <TimeBasedTriggeringPolicy/>
            </Policies>
        </RollingFile
    </Appenders>
    <Loggers>
        <Root level="info">
            <AppenderRef ref="Console"/>
            <AppenderRef ref="RollingFile"/>
        </Root>
    </Loggers>
</configuration>
```

::: note info

YAMLなど XML以外のフォーマットで読み込むためには 必要なDependencyを追加すれば良さそう（動作は未確認）

https://docs.spring.io/spring-boot/docs/3.0.3/reference/htmlsingle/#howto.logging.log4j.yaml-or-json-config

:::

## 参考リンク

[Spring Boot の機能 4.ログ](https://spring.pleiades.io/spring-boot/docs/current/reference/html/features.html#features.logging)
[Log4j 2 - configuration](https://logging.apache.org/log4j/2.x/manual/configuration.html)
[(Baeldung) Logging in Spring Boot](https://www.baeldung.com/spring-boot-logging)
[(stack overflow) Caused by: org.apache.logging.log4j.LoggingException: log4j-slf4j-impl cannot be present with log4j-to-slf4j
](https://stackoverflow.com/questions/59629214/caused-by-org-apache-logging-log4j-loggingexception-log4j-slf4j-impl-cannot-be)