あるSEのつぶやき・改

ITやシステム開発などの技術に関する話題を、取り上げたりしています。

Spring BootでSLF4J+logback+Lombokを使いログ出力を行う

はじめに

Spring Boot のプロジェクトで簡単にログ出力を行いたい場合は、SLF4J + logback + Lombok を使うとよさそうです。

簡単にログ出力ができますし、高速で多機能、書籍『Java本格入門』でもよく使われていると書かれていました。

この記事では、SLF4J + logback + Lombok の組み合わせでコンソールにログを出力する方法を詳しくご紹介します。

SLF4J とは

SLF4J は、logback や log4j などのような、さまざまなロギングフレームワークを抽象化し、開発者がシンプルにログを扱えるようにしたログのための仕組みです。

開発者から見ると、開発者 -> SLF4J -> logback開発者 -> SLF4J -> log4j という形になり、実装としてのロギングフレームワークを分離しています。例えば log4j から logback にロギングフレームワークを切り替えても軽い負担で済むようになっています。

注意が必要なのは、SLF4J 単体では動作せず、logback などのロギングフレームワークが必要になるということです。

公式サイトは以下になります。

logback とは

logback は、ロギングフレームワークの中でも後発であることもあり、よくできているようです。

log4j の開発者が log4j の後継として開発したのが logback と SLF4J とのことなので、本命と言えるのかもしれません。

log4j 1.x(すでに EOL で、現在は log4j2) と比較し、logback は以下のようなメリットがあります。

  • log4j 1.x より10倍高速化している
  • 省メモリである
  • logback-classic の Log ライブラリはオーバーヘッドなしで動作する
  • logback-classic は設定ファイルを変更すると自動的にリロードする

詳しくは下記ドキュメントをご参照ください。

logback の設定ファイルは、logback.xml より、拡張機能を扱える logback-spring.xml の方が推奨になります。

公式サイトは以下になります。

SLF4J + logback + Lombok のメリット

SLF4J と logback だけだと、以下のように全てのクラスで Logger の宣言をする必要があります。

public class LogSampleClass {
  private final Logger logger = (Logger) LoggerFactory.getLogger(LogSampleClass.class);

  public void log() {
    logger.info("ログを出力");
  }
}

Lombok を使用すると、@Slf4j のアノテーションを付けるだけで簡単にログを出力できるようになります。

@Slf4j
public class LogSampleClass {

  public void log() {
    log.info("infoログを出力しました");
  }
}

ですので、Lombok を使用しているプロジェクトでは、@Slf4j を使用するとよいかと思います。

ログの設定

build.gradle

logback は logback-classic を build.gradle で指定すると、slf4j-api と logback-core を自動的に読み込みます。

Note that in addition to logback-classic.jar, the above declaration will automatically pull-in slf4j-api.jar and logback-core.jar into your project by virtue of Maven's transitivity rules.

https://logback.qos.ch/setup.html

そのため、build.gradle では logback-classic のみを指定します。

logback-classic は、以下の Maven Repository から安定版の最新バージョンを使用します。2022/07/09時点であれば、1.2.11 になります。

logback-classic 1.2.11 が使用する logback-core は 1.2.11 になります。

もし、logback のバージョンが 1.3.x になると、Java 9以上が必要になるためご注意ください。

Version 1.3.x requires Java 9 to compile and build.

GitHub - qos-ch/logback: The reliable, generic, fast and flexible logging framework for Java.

SLF4J は、slf4j-api の安定版である 1.7.x を使用します。とは言っても、logback-classic で自動で読み込まれますが。

という訳で、build.gradle には以下のように設定を追加します。

dependencies {
    implementation 'ch.qos.logback:logback-classic:1.2.11'
}

./gradlew dependenciesで依存関係を調べてみても問題ないですね。

+--- org.springframework.boot:spring-boot-starter-thymeleaf -> 2.7.0
|    +--- org.springframework.boot:spring-boot-starter:2.7.0
|    |    +--- org.springframework.boot:spring-boot-starter-logging:2.7.0
|    |    |    +--- ch.qos.logback:logback-classic:1.2.11
|    |    |    |    +--- ch.qos.logback:logback-core:1.2.11
|    |    |    |    \--- org.slf4j:slf4j-api:1.7.32 -> 1.7.36
+--- ch.qos.logback:logback-classic:1.2.11 (*)

logback.xml

logback.xml という XML ファイルを resouces の直下に作成します。

  • src/main/resources/logback.xml

logback.xml の内容は以下のようにします。 今回はコンソールにログを出力します。

<?xml version="1.0" encoding="UTF-8"?>
<configuration>
    <!-- コンソールにログを出力する設定 -->
    <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
        <encoder>
            <pattern>%date %level [%thread] %logger %msg%n</pattern>
        </encoder>
    </appender>

    <!-- INFOレベル以上のログを出力-->
    <root level="INFO">
        <appender-ref ref="STDOUT" />
    </root>
</configuration>

実行してみる

LogSampleClass を作成します。

@Slf4j
public class LogSampleClass {

  public void log() {
    log.trace("traceログを出力しました");
    log.debug("debugログを出力しました");
    log.info("infoログを出力しました");
    log.warn("warnログを出力しました");
    log.error("errorログを出力しました");
  }
}

テストクラスの LogSampleClassTest を作成します。

@SpringBootTest
class LogSampleClassTest {

  @Test
  void log_test() {
    final LogSampleClass logSampleClass = new LogSampleClass();
    logSampleClass.log();
  }
}

テストを実行すると、以下のように INFO レベル以上のログが出力されているので想定通りですね。

2022-07-09 19:17:29,416 INFO [Test worker] com.example.demo.LogSampleClass infoログを出力しました
2022-07-09 19:17:29,417 WARN [Test worker] com.example.demo.LogSampleClass warnログを出力しました
2022-07-09 19:17:29,417 ERROR [Test worker] com.example.demo.LogSampleClass errorログを出力しました

実行環境ごとに設定を切り替える

実際のシステム開発では、ローカルの開発環境、開発環境、ステージング環境、本番環境でログの出力レベルを変えたいことがあります。

ローカルの開発環境と開発環境までは詳細なログを出力し、ステージング環境と本番環境では警告かエラーログのみ出力するようにします。

logback.xml の設定

アクティブなプロファイルが local または dev の場合は通常のログを出力して、stg または prod の場合はエラーログを出力するようにします。

<?xml version="1.0" encoding="UTF-8"?>
<configuration>

    <!-- WARN,ERRORはエラーログを主力する -->
    <appender name="STDERR" class="ch.qos.logback.core.ConsoleAppender">
        <target>System.err</target>
        <filter class="ch.qos.logback.classic.filter.ThresholdFilter">
            <level>WARN</level>
        </filter>
        <encoder>
            <pattern>%date %level [%thread] %logger %msg%n</pattern>
        </encoder>
    </appender>

    <!-- TRACE,DEBUG,INFOは通常ログを出力する -->
    <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
        <target>System.out</target>
        <filter class="ch.qos.logback.classic.filter.LevelFilter">
            <level>DEBUG</level>
            <onMatch>ACCEPT</onMatch>
        </filter>
        <filter class="ch.qos.logback.classic.filter.LevelFilter">
            <level>INFO</level>
            <onMatch>ACCEPT</onMatch>
        </filter>
        <filter class="ch.qos.logback.classic.filter.LevelFilter">
            <level>TRACE</level>
            <onMatch>ACCEPT</onMatch>
        </filter>
        <filter class="ch.qos.logback.classic.filter.LevelFilter">
            <level>WARN</level>
            <onMatch>DENY</onMatch>
        </filter>
        <filter class="ch.qos.logback.classic.filter.LevelFilter">
            <level>ERROR</level>
            <onMatch>DENY</onMatch>
        </filter>
        <encoder>
            <pattern>%date %level [%thread] %logger %msg%n</pattern>
        </encoder>
    </appender>

    <!-- プロファイルが local or dev の場合はINFOレベル以上を出力する-->
    <springProfile name="local | dev">
        <root level="INFO">
            <appender-ref ref="STDOUT" />
            <appender-ref ref="STDERR" />
        </root>
    </springProfile>

    <!-- プロファイルが stg or prod の場合はWARNレベル以上を出力する-->
    <springProfile name="stg | prod">
        <root level="WARN">
            <appender-ref ref="STDERR" />
        </root>
    </springProfile>
</configuration>

JUnit でアクティブプロファイルを指定して実行する

JUnit で以下のようにアクティブプロファイルを設定します。

@ActiveProfiles("dev")
@SpringBootTest
class LogSampleClassTest {

  @Test
  void log_test() {

    final LogSampleClass logSampleClass = new LogSampleClass();
    logSampleClass.log();
  }
}

アクティブプロファイルが local の時

想定通り、INFO 以上のログが出力されました。

2022-07-09 21:17:43,799 INFO [Test worker] com.example.demo.LogSampleClass infoログを出力しました
2022-07-09 21:17:43,799 WARN [Test worker] com.example.demo.LogSampleClass warnログを出力しました
2022-07-09 21:17:43,800 ERROR [Test worker] com.example.demo.LogSampleClass errorログを出力しました

なお、WARN 以上はエラーログのため、赤色で出力されています。

アクティブプロファイルが dev の時

想定通り、INFO 以上のログが出力されました。

2022-07-09 21:20:00,554 INFO [Test worker] com.example.demo.LogSampleClass infoログを出力しました
2022-07-09 21:20:00,554 WARN [Test worker] com.example.demo.LogSampleClass warnログを出力しました
2022-07-09 21:20:00,555 ERROR [Test worker] com.example.demo.LogSampleClass errorログを出力しました

アクティブプロファイルが stg の時

想定通り、WARN 以上のログが出力されました。

2022-07-09 21:20:48,855 WARN [Test worker] com.example.demo.LogSampleClass warnログを出力しました
2022-07-09 21:20:48,857 ERROR [Test worker] com.example.demo.LogSampleClass errorログを出力しました

アクティブプロファイルが prod の時

想定通り、WARN 以上のログが出力されました。

2022-07-09 21:21:27,171 WARN [Test worker] com.example.demo.LogSampleClass warnログを出力しました
2022-07-09 21:21:27,173 ERROR [Test worker] com.example.demo.LogSampleClass errorログを出力しました

おわりに

このように、SLF4J + logback + Lombok で Spring Boot のログ出力を行うと、簡単にログ出力をできるようになります。

ぜひ試していただければと思います。