はじめに
新卒1年目エンジニアのhirotoです。研修中でWebアプリケーションを作成する課題があり、アクセスログの出力機能を追加しようとしたときに発生したエラーとその解決手順についてこの記事でまとめたいと思います。
本文
動作環境
- 言語:Java(JDK 21)
- ビルドツール:Gradle 9.6.1
- フレームワーク:Spring Boot 4.1.0
- テンプレートエンジン:Thymeleaf
- DB:Postgres 15.18
発生したエラー
SLF4J: No SLF4J providers were found.
SLF4J: Defaulting to no-operation (NOP) logger implementation
SLF4J: See http://www.slf4j.org/codes.html#noProviders for further details.
昨日までちゃんと動いてて、SLF4Jのimplementちゃんとgradleに書いてるのに急になんでNoと言われるんだ?という感じでした
原因
| 要素 | 詳細 |
|---|---|
| 原因 |
logstash-logback-encoder が推移依存で古い slf4j-api と logback-classic を引き込み、Spring Bootが管理する slf4j-api / logback-classic とクラスパス上で競合
|
| SLF4J の仕組み | SLF4J 2.xは ServiceLoader で どこのが提供したものか(Provider)を検出する。APIのバージョンと providerのバージョンが不一致だと providerを発見できず NOP logger にフォールバックする |
| なぜ急に発生したか | Gradle のキャッシュ更新や依存解決順序の変動で、先に古い slf4j-api が読み込まれるようになった |
依存の衝突イメージ:
spring-boot-starter-web
└─ spring-boot-starter-logging
├─ logback-classic:1.5.x ← Spring Boot 管理(正)
└─ slf4j-api:2.1.x ← Spring Boot 管理(正)
logstash-logback-encoder:7.4
├─ logback-classic:1.4.x ← 競合!
└─ slf4j-api:2.0.x ← 競合!
アクセスログの実装はこちらの記事を参考にしつつ、Copilotとも相談しつつ実装しました。その際、提案されたlogstash-logback-encoder:7.4を追加したのですがこれが原因で競合が発生してしまっていたようです。(Copilotに提案されたコードを仕組みを理解しないまま承認してしまったのが反省点ですね)
解決方法
手順1: 競合ライブラリのバージョンアップ
logstash-logback-encoder を 8.0(Spring Boot 4.x 互換バージョン)に上げる。
// build.gradle
implementation('net.logstash.logback:logstash-logback-encoder:8.0')
手順2: 推移依存をexcludeで除外して固定化する
念のため、SLF4J / Logback の推移依存は Spring Boot の dependency management に統一する
implementation('net.logstash.logback:logstash-logback-encoder:8.0') {
exclude group: 'ch.qos.logback', module: 'logback-classic'
exclude group: 'ch.qos.logback', module: 'logback-core'
exclude group: 'org.slf4j', module: 'slf4j-api'
}
手順3: 確認
SLF4jの依存関連を調査する
.\gradlew.bat dependencyInsight --configuration runtimeClasspath --dependency org.slf4j
再発防止のためには
-
SLF4J / Logback を直接
implementationに書かない(Spring Boot の BOM 管理に任せてバージョンは書かない) -
サードパーティのロギング系ライブラリ追加時は
excludeで推移依存を除外する -
依存追加後に
dependencies --configuration runtimeClasspathでslf4j-api/logback-classicが 1バージョンだけ 存在するか確認する
付録:調査に使えるコマンド集
# 特定の依存がどこから来ているか調べる
.\gradlew.bat dependencyInsight --dependency slf4j-api --configuration runtimeClasspath
# logback の重複を調べる
.\gradlew.bat dependencyInsight --dependency logback-classic --configuration runtimeClasspath
# テスト用クラスパスの確認
.\gradlew.bat dependencies --configuration testRuntimeClasspath | Select-String "slf4j|logback"
まとめ
今回はアクセスログの実装しながら発生したエラーについてとその解決方法について紹介しました。
これまでの知識ではSLF4J+logbackでログを外部ファイルに出力することしか知らなかったため、Spring Bootが管理するSLF4Jがあることを知らずクラスパスの競合に気づけませんでした。
CLIでgradleの依存関係の調査なども初めてやってみたので今後も似たような事象があれば活用していきたいと思います