Java + SLF4J + Logback によるログ出力のサンプルコード

## 概要

- Java + SLF4J + Logback によるログ出力のサンプルコードを示す
- 今回の環境: SLF4J 1.7.30 + Logback 1.2.3 + Java 15 (AdoptOpenJDK 15.0.1+9) + Gradle 6.7

## SLF4J とは

SLF4J は各種のロギングライブラリのラッパーとして使えるライブラリ。

[SLF4J](http://www.slf4j.org/)

> The Simple Logging Facade for Java (SLF4J) serves as a simple facade or abstraction for various logging frameworks (e.g. java.util.logging, logback, log4j) allowing the end user to plug in the desired logging framework at deployment time.

## Logback とは

Logback はロギングライブラリ。SLF4J と一緒に使用するよう想定されている。

[Logbackマニュアル - 第1章：はじめに](http://logback.qos.ch/manual/introduction_ja.html)

> logback-classic モジュールを動かす際は、クラスパス上に slf4j-api.jar と logback-core.jar と logback-classic.jar が配置されている必要があります。

[Logbackマニュアル - 第2章 アーキテクチャ](http://logback.qos.ch/manual/architecture_ja.html)

> coreモジュールは、他の二つのモジュールの足回りとして使用されています。classicモジュールは、 coreを拡張するものです。classic モジュールは、著しく改善されたバージョンのlog4jとも考えられます。logback-classic は SLF4J API を直接実装しているので、log4j や java.util.logging(JUL)などの他のロギング実装と切り替えることができます。

## サンプルコード

### ソースコード一覧

```
├── build.gradle
└── src
    └── main
        ├── java
        │   └── myapp
        │       ├── MyApp.java
        │       ├── hello
        │       │   └── Hello.java
        │       └── world
        │           └── World.java
        └── resources
            └── logback.xml
```

### build.gradle

Gradle 用ビルド設定ファイル。
公式ドキュメントに書かれている通り slf4j-api, logback-core, logback-classic を導入する。

[Logbackマニュアル - 第1章：はじめに](http://logback.qos.ch/manual/introduction_ja.html)

> logback-classic モジュールを動かす際は、クラスパス上に slf4j-api.jar と logback-core.jar と logback-classic.jar が配置されている必要があります。

```groovy
plugins {
  id 'application'
  id 'java'
}

repositories {
  mavenCentral()
}

dependencies {
  // SLF4J を使う
  implementation 'org.slf4j:slf4j-api:1.7.30'
  // Logback を使う
  implementation 'ch.qos.logback:logback-core:1.2.3'
  implementation 'ch.qos.logback:logback-classic:1.2.3'
}

mainClassName = "myapp.MyApp"
```

### src/main/resources/logback.xml

Logback の設定は logback.xml ファイルに記述する。

[Logbackマニュアル - 第3章 logbackの設定](http://logback.qos.ch/manual/configuration_ja.html)

> logback はクラスパス上でlogback-test.xmlやlogback.xmlを探して自分を設定しようとします。

```xml
<configuration>

  <!-- 標準出力用設定 -->
  <appender name="BAR-STDOUT" class="ch.qos.logback.core.ConsoleAppender">
    <encoder>
      <pattern>
        %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n
      </pattern>
    </encoder>
  </appender>

  <!-- ファイル出力用設定 -->
  <appender name="FOO-FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
    <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
      <!-- 日毎にログファイルを切り替える -->
      <fileNamePattern>foo-%d{yyyy-MM-dd}.log</fileNamePattern>
      <!-- 保持しておくログアーカイブファイルの最大数 -->
      <maxHistory>30</maxHistory>
    </rollingPolicy>
    <encoder>
      <pattern>%date %level [%thread] %logger{10} [%file : %line] %msg%n</pattern>
    </encoder>
  </appender>

  <!-- ルートロガー -->
  <!-- DEBUG, INFO, WARN, ERROR レベルのログを出力 -->
  <root level="DEBUG">
    <!-- 全てのロガーのメッセージを定義した BAR-STDOUT アペンダーに出力 -->
    <appender-ref ref="BAR-STDOUT"/>
  </root>

  <!-- INFO, WARN, ERROR レベルのログを出力 -->
  <logger name="myapp" level="INFO"/>

  <!-- WARN, ERROR レベルのログを出力 -->
  <logger name="myapp.hello.Hello" level="WARN"/>

  <!-- ERROR レベルのログを出力 -->
  <logger name="myapp.world.World" level="ERROR">
    <!-- 定義した FOO-FILE アペンダーに出力 -->
    <appender-ref ref="FOO-FILE"/>
  </logger>

</configuration>
```

今回は日毎にログファイルを切り替えるため RollingFileAppender を使用している。

[Logbackマニュアル - 第4章 アペンダー](http://logback.qos.ch/manual/appenders_ja.html)

> RollingFileAppenderはFileAppenderを拡張して、ログファイルを切り替えられるようにしたものです。たとえば、RollingFileAppenderでは、log.txtという名前のファイルにログを出力するようにした上で、一定の条件が満たされたら、出力先を別のファイルに変えることができます。

### src/main/java/myapp/hello/Hello.java

ログ出力を実行するサンプルクラス。

```java
package myapp.hello;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class Hello {

  private final Logger logger = LoggerFactory.getLogger(getClass());

  public void hello() {
    logger.trace("Hello Trace");
    logger.debug("Hello Debug");
    logger.info("Hello Info");
    logger.warn("Hello Warn");
    logger.error("Hello Error");
  }
}
```

### src/main/java/myapp/world/World.java

ログ出力を実行するサンプルクラス。

```java
package myapp.world;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class World {

  private final Logger logger = LoggerFactory.getLogger(getClass());

  public void world() {
    logger.trace("World Trace");
    logger.debug("World Debug");
    logger.info("World Info");
    logger.warn("World Warn");
    logger.error("World Error");
  }
}
```

### src/main/java/myapp/MyApp.java

ログ出力を実行するサンプルクラス。

```java
package myapp;

import myapp.hello.Hello;
import myapp.world.World;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class MyApp {

  public static void main(String[] args) {
    Logger logger = LoggerFactory.getLogger(MyApp.class);
    logger.trace("MyApp Trace");
    logger.debug("MyApp Debug");
    logger.info("MyApp Info");
    logger.warn("MyApp Warn");
    logger.error("MyApp Error");
    new Hello().hello();
    new World().world();
  }
}
```

## サンプルコードの実行例

設定した通りに、標準出力にログが出力される。

```
$ gradle run  

> Task :run
22:06:47.919 [main] INFO  myapp.MyApp - MyApp Info
22:06:47.921 [main] WARN  myapp.MyApp - MyApp Warn
22:06:47.921 [main] ERROR myapp.MyApp - MyApp Error
22:06:47.921 [main] WARN  myapp.hello.Hello - Hello Warn
22:06:47.921 [main] ERROR myapp.hello.Hello - Hello Error
22:06:47.921 [main] ERROR myapp.world.World - World Error

BUILD SUCCESSFUL in 1s
3 actionable tasks: 3 executed
```

設定した通りに、ファイルにもログが出力される。

```
$ cat foo-2020-11-02.log 
2020-11-02 22:07:20,532 ERROR [main] m.w.World [World.java : 15] World Error
```

## 参考資料

- [SLF4J Manual](http://www.slf4j.org/manual.html)
- [Logbackマニュアル](http://logback.qos.ch/manual/index_ja.html)
- [Logger \(SLF4J 2\.0\.0\-alpha0 API\)](http://www.slf4j.org/apidocs/org/slf4j/Logger.html)