Spring Boot × Logback で用途ごとに別ファイルにログ出力 & 環境ごとに設定を変える

Java
JavaSpringバックエンド

結論としては複数のファイルに出力したい場合は複数の Appender と Logger を用意すればよいです。

この記事ではログを出力するまでの準備とログの設定、環境別の設定値の切り変え方についてサンプル交えて解説しています。

Spring Boot でのログ出力

Spring Boot は spring-boot-starter にデフォルトで Logback が含まれており、また、ログ出力に関しても logback.xml を用意しなくても、application.properties / application.yml である程度の制御ができます。

ただ、その範囲ではできないものについては logback.xml を用意する必要があります。今回はパッケージ単位でファイル出力やローテーションを分けたかったのですが、複数の Logger/Appender の設定と使い分けは、上の仕組みではできそうにない(はず…)ので、logback の設定ファイルを用意します。

なお、Spring Boot では、logback-spring.xml というものもあります。違いは、Logback拡張 を使う場合にはこの名前にする必要があるようです。今使える拡張機能はプロファイルごとに設定を変える、環境プロパティごとに設定を変える、の2つのようです。

この機能も使ってみます。

環境

  • Java 17
  • Spring boot 2.7.5

準備

プロジェクト

いつもどおり、Spring initializr です。

https://start.spring.io/

ログ出力コード

冒頭の通り spring-boot-starterSlf4j / Logback がで含まれています。
そのため、アノテーションを付与したあと、log.xxx とするだけでOKです。

@Slf4j
@SpringBootApplication
public class SpringLogbackApplication {

	public static void main(String[] args) {
		SpringApplication app = new SpringApplication(SpringLogbackApplication.class);
		int exitCode = SpringApplication.exit(app.run(args));
		String msg = "exitCode: " + exitCode;
		log.trace("trace: " + msg);
		log.debug("debug: " + msg);
		log.info("info: " + msg);
		log.warn("warn: " + msg);
		log.error("error: " + msg);
		ExampleClass1.hello();
		System.exit(exitCode);
	}
}

また、上記と別のパッケージにもログを出力するだけのクラスを用意しておきます。

@Slf4j
public class ExampleClass1 {
    static public void hello() {
      log.debug("exampleClass1 hello テスト");
    }
}

実行するとちゃんとログが出力されました。デフォルトでは、INFO なので trace, debug は出力されません。

2022-11-10 22:58:29.732  INFO 79567 --- [  restartedMain] c.e.s.SpringLogbackApplication           : info: exitCode: 0
2022-11-10 22:58:29.733  WARN 79567 --- [  restartedMain] c.e.s.SpringLogbackApplication           : warn: exitCode: 0
2022-11-10 22:58:29.733 ERROR 79567 --- [  restartedMain] c.e.s.SpringLogbackApplication           : error: exitCode: 0

ログの設定

いくつか順を追って試していきます。

その1: ファイル/コンソール出力

logback-spring の設定です。今回はファイル出力とコンソール出力をさせます。
利用しているタグの概要は以下です。正確なところは公式にしっかり書いてあるのでそちらを読むことをおすすめします。

タグ補足
include別ファイルで定義した内容を読み込む。
これを使って塊ごとにファイル分割ができる。
property変数の定義。
ここではファイル読み込みにしていますが、name, value で直接指定することも可。
patternログの出力フォーマット定義。
こちらで利用できるものが列挙されている(日本語はこちら)。
appenderlog4j でもおなじみ、出力先の設定。
コンソール出力は ch.qos.logback.core.ConsoleAppender、
ローテションしつつのファイル出力は ch.qos.logback.core.rolling.RollingFileAppender。
rollingPolicyローテションのポリシー定義。
ch.qos.logback.core.rolling.TimeBasedRollingPolicy は時間ベース。
ローテーションの期間は fileNamePattern の設定の仕方次第でかわる(yyyy-MM-dd なら日時、yyyy-MM-dd_HH なら毎時など)。
maxHistory は世代を指定。これを過ぎると自動でファイルを削除してくれる、便利。
rootルートのロガー設定。ここでの設定に従ってログが出力される。
logback-spring の設定の基本構成要素
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
    <!-- Spring Boot が用意しているデフォルト設定を読み込みます
    https://github.com/spring-projects/spring-boot/blob/main/spring-boot-project/spring-boot/src/main/resources/org/springframework/boot/logging/logback/defaults.xml
    -->
    <include resource="org/springframework/boot/logging/logback/defaults.xml" />

    <!-- 外出しにした設定値を読み込みます。プロパティは ${xx} で利用可能 -->
    <property file="src/main/resources/log-params-dev.properties" />

    <!-- コンソール出力 -->
    <appender name="console" class="ch.qos.logback.core.ConsoleAppender">
        <encoder>
            <pattern>${logFormat}</pattern>
        </encoder>
    </appender>

    <!-- ファイル出力 -->
    <appender name="app_log1" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <file>${logFilePathName1}</file>
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <fileNamePattern>${logRollingFilePathName1}</fileNamePattern>
            <maxHistory>${logMaxHistory}</maxHistory>
        </rollingPolicy>
        <encoder>
            <pattern>${logFormat}</pattern>
        </encoder>
    </appender>

    <!--ルートロガー -->
    <root level="INFO">
        <appender-ref ref="console" />
        <appender-ref ref="app_log1" />
    </root>
</configuration>

上で指定している log-config-dev.properties も用意します。このおかげで logback-spring.xml はほぼ定型みたいにできますね。何か変えたいときはこちらを変えればすみそうです。

logFilePath=./
logFormat=%d{yyyy/MM/dd HH:mm:ss} %-5level [%thread] - %msg%n
logMaxHistory=3

logFileName1=app1
logFilePathName1=${logFilePath}${logFileName1}.log
logRollingFilePathName1=${logFilePathName1}-%d{yyyy-MM-dd}.log.zip

これで実行すると、コンソール出力と同時に app1.log という名前のファイルにもログが出力されています。これだけ、簡単。

その2: 特定パッケージは別のファイルに出力

別のファイルに出力するには、もう1つ appender を作ってあげればよいです。
あとは、root は全体に効くので、適用させたいパッケージ用に logger を作ってあげます。

以下を追加します。appender は指定するプロパティが違うだけで先程と全く同じです。
logger は、name にパッケージを指定し、root と同じように appender-ref で適用する appender を指定します。

additivity は伝搬するか否かです。デフォルトだと、com.example.springlogback.hoge パッケージでログ出力されると、その後上位(今だと root)の設定も動きます。結果 コンソールに2回出力され、app1.log にも app2.log にも com.example.springlogback.hoge のログが出力されることになります。additivity=false はこの動きを抑制したい場合に使います。

    <!-- ファイル出力 その2 -->
    <appender name="app_log2" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <file>${logFilePathName2}</file>
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <fileNamePattern>${logRollingFilePathName2}</fileNamePattern>
            <maxHistory>${logMaxHistory}</maxHistory>
        </rollingPolicy>
        <encoder>
            <pattern>${logFormat2}</pattern>
        </encoder>
    </appender>

    <!-- additivity は伝搬を止める -->
    <logger name="com.example.springlogback.hoge" level="DEBUG" additivity="false">
        <appender-ref ref="console" />
        <appender-ref ref="app_log2" />
    </logger>

プロパティを追加します。違いがよりわかるよう logFormat も変えています。

logFileName2=app2
logFilePathName2=${logFilePath}${logFileName2}.log
logRollingFilePathName2=${logFilePathName2}-%d{yyyy-MM-dd}.log.zip
logFormat2=test %d{yyyy/MM/dd HH:mm:ss} %-5level [%thread] - %msg%n

実行すると、コンソールに以下がでます。level を DEBUG にしていたので、ExampleClass1 のログも出力されました。root は INFO のままなので traceやdebug は出ないままです。

2022/11/16 22:53:09 INFO  [restartedMain] - info: exitCode: 0
2022/11/16 22:53:09 WARN  [restartedMain] - warn: exitCode: 0
2022/11/16 22:53:09 ERROR [restartedMain] - error: exitCode: 0
2022/11/16 22:53:09 DEBUG [restartedMain] - exampleClass1 hello テスト

ファイルにはそれぞれ以下の内容が出力されています。期待通りです。

// app1.log
... Spring Boot の info ログ
2022/11/16 22:55:45 INFO  [restartedMain] - info: exitCode: 0
2022/11/16 22:55:45 WARN  [restartedMain] - warn: exitCode: 0
2022/11/16 22:55:45 ERROR [restartedMain] - error: exitCode: 0

// app2.log
test 2022/11/16 22:55:45 DEBUG [restartedMain] - exampleClass1 hello テスト

その3 プロファイルの利用

Spring Boot の Logback 拡張で利用できるプロファイルです。
これまで log-params-dev.properties で読み込んでいたものを、dev, stg, prd に分けます。

   <springProfile name="dev">
        <property file="src/main/resources/log-params-dev.properties" />
    </springProfile>
    <springProfile name="stg">
        <property file="src/main/resources/log-params-stg.properties" />
    </springProfile>
    <springProfile name="prd">
        <property file="src/main/resources/log-params-prd.properties" />
    </springProfile>

それぞれに対応する properties ファイルを作ります。今回は log-params-dev.properties をコピーして prd を作ります。とりあえずログファイル名だけ変えます。

...
logFileName1=app1-prd
...
logFileName2=app2-prd

あとは、どのプロファイルかを渡して実行すればOKです。プロファイルは次のようにすることで渡せるようです。

spring:
  profiles:
    active: prd

これでちゃんと app1-prd.log として出力されました。すごい。

その4 プロファイルを環境変数に

若干余談ですが、dev, stg, prd は環境変数として指定することが多いと思います。環境変数で設定するには以下の様にします(profile という環境変数)。${環境変数:デフォルト値} という形で指定できるようです。かゆいところに手が届いてる感ありますね。

spring:
  profiles:
    active: ${profile:dev}

その5 ログレベルの変更

logback-spring.xml でロガーを指定するときにログレベルを指定しましたが、application.yml に書くことで上書きすることもできます。

logging:
  level:
    root: debug
    com.example.springlogback.hoge: warn

すると、debug がでました。逆に com.example.springlogback.hogeは warn にしたのででなくなりました。

2022/11/16 23:16:42 DEBUG [restartedMain] - debug: exitCode: 0
2022/11/16 23:16:42 INFO  [restartedMain] - info: exitCode: 0
2022/11/16 23:16:42 WARN  [restartedMain] - warn: exitCode: 0
2022/11/16 23:16:42 ERROR [restartedMain] - error: exitCode: 0

まとめ

思った以上に簡単にできました。色々便利な仕組みがありますね。
一度仕組みを作ってしまえばそうそう xml も環境別のログ設定も触る必要がないので、変にログの設定を見る必要もなく、application.yml だけでログレベルなど制御できるのはありだなーと思います。

  1. logback-spring.xml でログの設定をする
  2. 設定値は外だしにしつつ、プロファイルで切り替えられるようにする
  3. プロファイルは環境変数から取得する
  4. ログレベルの変更程度であれば、上記いずれも触ることなく、application.yml で操作できる

Spring に関して、以下の書籍は出版されて年数が経っており最新ではない部分はありますが、それでもベースを理解する上では今でも有用でした(自分も何度も買うか悩んだ末 2023/1 に買いましたがやはり体系だったものごとが見れるのは助かります)。Spring は歴史があるぶん奥が深いので、迷っている方は読んで見ることをおすすめです。

コメント