Spring Boot ログの出力先とフォーマットを環境によって切り替える

Spring Boot アプリケーションでログの出力先やフォーマットを
環境によって切り替えたいことがよくあります。

自分の場合は、

  • ローカル開発時は標準出力にテキスト出力
  • 本番実行時は標準出力に JSON フォーマットで出力

とすることが多いです。

(ローカル開発時まで JSON 出力するのは読みにくいため)

環境

  • Java バージョン: 17
  • Spring Boot バージョン: 3.0 (2.x でも OK)
  • ビルドツール: Maven

方針

  • ここではローカル開発時, 本番実行時の 2 環境の切り替えを想定します
  • ローカル開発時以外の環境では Spring Profile に環境名を与えます (下表 A)
    • 📝 環境を増やしたい場合は、この値の種類を増やします
  • application(-{環境名}).yml に Spring Property app.log.appender を定義します (下表 B)
    • 📝 出力先/フォーマットを増やしたい場合は、この値の種類を増やします
  • Spring Property app.log.appender によって出力先/フォーマットを切り替えます (下表 C)
    • 📝 Spring Property 経由なので、環境変数 APP_LOG_APPENDER でも柔軟に切り替え可能です
    • 📝 標準出力のみ扱ってますが、実際はファイル出力やログサーバ送信等も想定できます
環境 Spring Profile (A) Spring Property (B) 出力先 (C) フォーマット (C)
ローカル開発時 なし console-text 標準出力 テキスト
本番実行時 prod console-json 標準出力 JSON

実装方法

依存関係の追加

JSON 出力する場合は logstash-logback-encoder を使うのが楽なので、依存関係に追加します。

<!-- pom.xml -->
<dependency>
  <groupId>net.logstash.logback</groupId>
  <artifactId>logstash-logback-encoder</artifactId>
  <version>7.3</version>
</dependency>

環境設定ファイルの作成

環境ごとの設定ファイル application(-{環境名}).yml をクラスパスルートに作成します。

# application.yml (デフォルト, ローカル開発時用)
app.log.appender: console-text
# application-prod.yml (本番実行時用)
app.log.appender: console-json

Logback 設定ファイルの作成

Logback の設定ファイル logback-spring.xml をクラスパスルートに作成します。
Spring Boot が自動的に読み込んでくれるので、ここで Logback 設定をカスタマイズできます。
Spring Property の取得には、 Spring Boot 提供の Logback 拡張 <springProperty> タグが便利です。

<!-- logback-spring.xml -->
<configuration>

  <!-- Spring Boot が提供しているデフォルト設定を読み込み -->
  <include resource="org/springframework/boot/logging/logback/defaults.xml"/>

  <!-- Spring Property (application.yml, 環境変数等で指定) から設定値を取得 -->
  <springProperty name="APP_LOG_APPENDER" source="app.log.appender" defaultValue="console-text"/>

  <!-- 標準出力向けテキスト出力 (Spring Boot が提供しているデフォルト設定から name だけ変えてます) -->
  <!--include resource="org/springframework/boot/logging/logback/console-appender.xml" /-->
  <appender name="console-text" class="ch.qos.logback.core.ConsoleAppender">
    <encoder>
      <pattern>${CONSOLE_LOG_PATTERN}</pattern>
      <charset>${CONSOLE_LOG_CHARSET}</charset>
    </encoder>
  </appender>

  <!-- 標準出力向け JSON 出力 -->
  <appender name="console-json" class="ch.qos.logback.core.ConsoleAppender">
    <encoder class="net.logstash.logback.encoder.LogstashEncoder"/>
  </appender>

  <!-- Spring Property によって Appender を切り替えて出力 -->
  <root level="INFO">
    <appender-ref ref="${APP_LOG_APPENDER}"/>
  </root>

</configuration>

サンプルコード全体

上記のサンプルコード全体はこちらに置いてます。

akkinoc/try-spring-boot-log-by-env - GitHub

実行イメージ

ローカル開発時

Spring Profile の指定なしで実行すると、テキストフォーマットで出力されます。

$ mvn spring-boot:run
... (中略)
2023-03-26T14:21:28.360+09:00  INFO 77469 --- [           main] sample.App                               : Running App!

本番実行時

Spring Profile に prod を指定して実行すると、 JSON フォーマットで出力されます。

$ SPRING_PROFILES_ACTIVE=prod mvn spring-boot:run
... (中略)
{"@timestamp":"2023-03-26T14:21:48.269814+09:00","@version":"1","message":"Running App!","logger_name":"sample.App","thread_name":"main","level":"INFO","level_value":20000}

環境変数で切り替え

環境変数を指定して柔軟に切り替えることも可能です (一時的に上書き変更したい場合等)。

$ APP_LOG_APPENDER=console-json mvn spring-boot:run
... (中略)
{"@timestamp":"2023-03-26T14:22:20.356888+09:00","@version":"1","message":"Running App!","logger_name":"sample.App","thread_name":"main","level":"INFO","level_value":20000}

Tips

ログレベルの設定

application.yml, logback-spring.xml, どちらでも設定できます。

# application.yml
logging.level.your.package=debug
logging.level.root=warn
<!-- logback-spring.xml -->
<logger name="your.package" level="DEBUG"/>
<root level="WARN">...</root>

片方に集約されていれば、どちらで設定しても良いと思います。
個人的には application.yml の方が、環境別に基本の値を定義できるので好きです。

どちらでも、環境変数 LOGGING_LEVEL_ROOT, LOGGING_LEVEL_YOUR_PACKAGE 等で
一時的な上書き変更も可能です。

テキストフォーマットのカスタマイズ

Spring Property logging.pattern.console が用意されてます。
指定可能なパターンは Logback Manual: PatternLayout が参考になります。

# application.yml
logging.pattern.console: "%d{yyyy-MM-dd HH:mm:ss.SSS} %-5p [%t] [%c{30}] %m - %C.%M \\(%F:%L\\)%n%ex"
$ mvn spring-boot:run
... (中略)
2023-03-26 15:23:28.421 INFO  [main] [sample.App] Running App! - sample.App.run (App.java:19)

JSON フォーマットのカスタマイズ

logstash-logback-encoder: Usage に詳細に記載されています。

JSON を整形して読みやすくしたい場合は jsonGeneratorDecorator が使えます。

<!-- logback-spring.xml -->
<appender name="console-json" class="ch.qos.logback.core.ConsoleAppender">
  <encoder class="net.logstash.logback.encoder.LogstashEncoder">
    <jsonGeneratorDecorator class="net.logstash.logback.decorate.PrettyPrintingJsonGeneratorDecorator"/>
  </encoder>
</appender>
$ APP_LOG_APPENDER=console-json mvn spring-boot:run
... (中略)
{
  "@timestamp" : "2023-03-26T16:04:04.654775+09:00",
  "@version" : "1",
  "message" : "Running App!",
  "logger_name" : "sample.App",
  "thread_name" : "main",
  "level" : "INFO",
  "level_value" : 20000
}

もし Logstash を無視したオリジナルのフォーマットにしたい場合は
LoggingEventCompositeJsonEncoder が使えます。

<!-- logback-spring.xml -->
<appender name="console-json" class="ch.qos.logback.core.ConsoleAppender">
  <encoder class="net.logstash.logback.encoder.LoggingEventCompositeJsonEncoder">
    <providers>
      <pattern>
        <pattern>
          {
            "timestamp": "%d{yyyy-MM-dd'T'HH:mm:ss.SSSZZ}",
            "level": "%p",
            "thread": "%t",
            "logger": "%c",
            "message": "%m",
            "class": "%C",
            "method": "%M",
            "file": "%F",
            "line": "%L",
            "exception": "%ex"
          }
        </pattern>
        <omitEmptyFields>true</omitEmptyFields>
      </pattern>
    </providers>
    <jsonGeneratorDecorator class="net.logstash.logback.decorate.PrettyPrintingJsonGeneratorDecorator"/>
  </encoder>
</appender>
$ APP_LOG_APPENDER=console-json mvn spring-boot:run
... (中略)
{
  "timestamp" : "2023-03-26T16:05:04.684+0900",
  "level" : "INFO",
  "thread" : "main",
  "logger" : "sample.App",
  "message" : "Running App!",
  "class" : "sample.App",
  "method" : "run",
  "file" : "App.java",
  "line" : "19"
}

参考リンク