Kengo's blog

Technical articles about original projects, JVM, Static Analysis and TypeScript.

Javaライブラリを配布する際のログ周りにおける配慮と実践 2020

この記事は、2013年に書いた記事を現状に合わせてアップデートするものです。結論から言うと、当時から id:miyakawa_taku さんがおっしゃっていた「APIは依存に含めて良い」を支持するものです。あるいは無難にバージョン 1.7.30 を使っておきましょう。

blog.kengo-toda.jp

slf4j-apiに1.7, 1.8, 2.0の3バージョンが生まれた

現在slf4j-apiには3つのバージョンが存在します。現在のSLF4Jエコシステムを考える上では、これらの違いを抑える必要があります:

1.7.x
Java 1.5から利用できるバージョンです。安定版にこだわるなら未だこのバージョンを使う必要があります。
1.8.x
JPMS(a.k.a. jigsaw)を採用しServiceLoaderを使ってBindingを呼び出すようになったバージョンです。Java 1.6が必要です。alpha0が出てから3年以上経ちますが、未だbeta4です*1。作業用ブランチも残っていないので、1.8は安定しないまま2.0開発に移ったものと思われます。
2.0.x
このバージョンからはJava 8が必要です。Fluent Logging APIが実装され、ログの書き方の選択肢が増えました。新しい実装はinterfaceのdefault methodを使って実装されているため、slf4j-api単体で変更が完結しており、binding側は変更なく利用できます。

複数バージョンの存在を踏まえ、ライブラリ配布者は何を気にするべきか

前出の情報を紐解くと、3バージョン間に眠る2つの差が見えてきます:

  1. APIの差。slf4j-api 2.0.x には従来存在しなかったメソッドが増えている。これに依存したライブラリのユーザは、slf4j-api 2.0.xを利用しなければ実行時に NoSuchMethodError が出るかもしれない。
  2. binding選択手法の差。slf4j-api 1.7.x と1.8.x以降では実装を決定するための手法が異なるため、apiとbindingのバージョンは揃える必要がある。

このうち2については、アプリケーションをビルドする開発者に責任があります。実行時にCLASSPATHにどのバージョンのAPIとbindingを含めるか、彼らに決定権があるからです。のでライブラリ提供者としては、彼らに選択肢と必要な情報を提供しつつ、1に取り組む必要があります。

考慮すべきはライブラリでFluent Logging APIを使っているかどうか、です。このAPIに依存していないなら、実行時にslf4j-apiのどのバージョンを利用しても問題ありません。

もし使っているなら、その旨をユーザに伝えて実行時にもslf4j-api 2.0.xを使ってもらう必要があります。そしてそのための最もsemanticな方法が、compileスコープで依存することです。ライブラリが2.0.xに依存しているにも関わらずに古いバージョンで依存が上書きされてしまった場合、ビルドツールによっては警告を発してくれます。Gradleの場合はもっと細かな依存指定も可能です。

よって、現時点では slf4j-api には compile スコープで依存することが望ましいと思います。bindingは引き続きprovidedあるいはtestスコープが望ましいでしょう。

どのバージョンを使うべきか

安定版にこだわるなら1.7.xを、JPMSを使いたいなら1.8.xか2.0.xを、Fluent Logging APIを使いたいなら2.0.xを選ぶことになるでしょう。

1.8.xと2.0.xはJPMSに準拠しており、モジュールを利用したビルドを行っているプロジェクトでも安心して使うことができます。ただどちらも安定版が出ていない点には注意が必要です。

2020-08-07 追記: 1.8を選択する積極的な理由がないことを以下の記事に記載しました。 SLF4Jの1.8は安定版が出ないので2.0を使おうという話 - Kengo's blog

マイナーケース:ライブラリとしても実行可能ファイルとしても配布する場合

私がメンテに参加しているSpotBugsは、spotbugs-gradle-pluginやspotbugs-maven-pluginなどから利用されるライブラリとしての性質と、CUIGUIで実行される実行ファイルとしての性質とを持ち合わせています。内部的にはDistribution Pluginで配布用パッケージを作成しています。

この場合、Maven Centralにアップロードする pom.xml にはSLF4J bindingを依存として追加しない一方で、distribution pluginの対象にはSLF4J bindingを追加する必要があります。

runtimeOnly 依存を使うことが素直な解決になるでしょう。configurations.runtimeClasspath を配布用ファイルに入れる形になります。

ただSpotBugsの場合はEclipse用依存を別に管理している(Eclipse用のruntimeClasspathにはSLF4J bindingを入れたくない)ので、以下のようにconfigurationを自前で定義しています

plugins {
    id 'distribution'
}
configurations {
  slf4jBinding
}
dependencies {
  implementation 'org.slf4j:slf4j-api:1.8.0-beta4'
  logBinding ('org.apache.logging.log4j:log4j-slf4j18-impl:2.13.3') {
    exclude group: 'org.slf4j'
  }
}
distributions {
  main {
    contents {
      from ([configurations.compileClasspath, configurations.slf4jBinding]) {
        into 'lib'
        include '**/*.jar'
      }
    }
  }
}

*1:newsページにはbeta5が公開された旨が記載されていますが、Maven Centralには来ていませんし、タグも残っていません