Spring Boot で log4j2 を利用して環境ごとに異なる設定でログ出力する

Java
JavaSpringバックエンド

別記事で Logback を使った方法を書きましたが今度は log4j2 でログ出力や環境ごとの切り替えを行う方法の紹介です。

Spring Boot のデフォルトは Logback ですが、 log4j2 を選択することもあると思います。
両方試した結果、やはり簡単にできるのはデフォルトで統合されている Logback だとは感じました。一度セットアップさえしてしまえば後はどちらでもよさそうですが。

また、今回は qos-ch の OSS である Logback 似合わせて Slf4j も使用していません。

バージョン

  • Java 17
  • Spring Boot 2.7.7

インストール

いつも通りです。

https://start.spring.io/
https://start.spring.io/

標準のログ出力

前の記事と同じものです。

@Slf4j
@SpringBootApplication
public class SpringLog4j2Application {

	public static void main(String[] args) {
		SpringApplication app = new SpringApplication(SpringLog4j2Application.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);

		System.exit(exitCode);	
  }

}

デフォルトは logback かつ info なので info 以上のログが出力されます。

2023-01-05 16:16:20.153  INFO 35812 --- [  restartedMain] c.e.s.SpringLog4j2Application            : info: exitCode: 0
2023-01-05 16:16:20.153  WARN 35812 --- [  restartedMain] c.e.s.SpringLog4j2Application            : warn: exitCode: 0
2023-01-05 16:16:20.153 ERROR 35812 --- [  restartedMain] c.e.s.SpringLog4j2Application            : error: exitCode: 0

ここから log4j2 に変更しています。

log4j2 に変更

pom.xml の変更だけでOKです。

spring boot から logback を除外

		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter</artifactId>
			<exclusions>
				<exclusion>
					<groupId>org.springframework.boot</groupId>
					<artifactId>spring-boot-starter-logging</artifactId>
				</exclusion>
			</exclusions>
		</dependency>

log4j2 の追加

		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-log4j2</artifactId>
		</dependency>

log4j2 の設定

Spring では、logback 同様、log4j2-spring.xml という -spring をファイル名につけた設定ファイルを resources 配下におくのが基本です。

が、log4j2 には logback でやったときの

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

の例のように、複数の property を外だしにしたファイルを読み込みつつ、SpringProfile を使って環境変数ごとに読む対象を変える、と言ったことがぱっとできなかった(${bundle:BundleName:key} などを使えばできるかもしれません。あと SpringProfile 自体は使えるはずなのでやり方が悪かっただけかも・・・)ので、設定ファイル自体を環境変数の数だけ用意する形にしました。

設定した内容自体は、以下で Logback で設定したのとほぼ同じで、

以下ができるパターンを作成。

  • コンソール出力
  • ファイル出力1(日付ベースで指定世代まで残す)
  • ファイル出力2(同じく日付ベース。上とは別の定義を用意して、指定パッケージ配下に適用)

application.yml

resources/application.yml です。Spring では application.properties がデフォルトですが、yml の方が構造的にわかりやすいのでこちらで。内容はシンプルに以下のみ。

これで、アプリケーション起動時に設定した環境変数に応じた log4j2-<環境>.xml が読まれます。

# log4j2.xml の設定ファイルの場所
# log4j2-spring.xml を用意するか、以下のように logging.config でパスを指定する
# ${profile:dev} の :dev はデフォルト値
logging:
  config: ./src/main/resources/log4j2-${profile:dev}.xml

# 環境変数から active な profile の設定
spring:
  profiles:
    active: ${profile:dev}

log4j2-dev.xml  ※ dev 部分は環境

環境別の設定。log4j2 の設定自体は公式や他の方が丁寧に書いてくださってますので割愛。
以下は ルート含め 3 つのロガーに対して、それぞれ appender を設定した例です。
長いですが、やっていることは、

  • 上段で Property 定義(変数の定義)を行い、
  • 中段で Appender (コンソール出力やファイル出力といった出力方法と、それぞれの出力フォーマットや、ファイルの場合はファイルの分割基準や削除タイミング)の定義をして、
  • 下段で Logger 設定(どのパッケージにどの Appender を適用するか、またログの出力レベル)を行う

とシンプルです。それぞれ簡単ですがコメントも記載しています。

<?xml version="1.0" encoding="UTF-8"?>
<configuration>
    <!-- プロパティ定義
        name, value で定義していく。
        %d など出力レイアウトで使えるものは https://qiita.com/pica/items/f801c74848f748f76b58#patternlayout などで紹介されています。
        ${sys:xxxx} はシステムプロパティ。
    -->
    <Properties>
        <Property name="logFilePath">./</Property>
        <Property name="logFormat">%d{yyyy/MM/dd HH:mm:ss} %-5level [%thread] - %msg%n</Property>
        <Property name="logMaxHistory">3</Property>
        <Property name="logFileName1">app1</Property>
        <Property name="logFilePathName1">${sys:logFilePath}${sys:logFileName1}.log</Property>
        <Property name="logRollingFilePathName1">${sys:logFilePathName1}-%d{yyyy-MM-dd}.log.zip</Property>
        <Property name="logFileName2">app2</Property>
        <Property name="logFilePathName2">${sys:logFilePath}${sys:logFileName2}.log</Property>
        <Property name="logRollingFilePathName2">${sys:logFilePathName2}-%d{yyyy-MM-dd}.log.zip</Property>
        <Property name="logFormat2">test %d{yyyy/MM/dd HH:mm:ss} %-5level [%thread] - %msg%n</Property>
    </Properties>

    <!-- 出力の定義 -->
    <Appenders>
        <Console name="Console" target="SYSTEM_OUT" follow="true">
            <PatternLayout pattern="${sys:logFormat}" charset="UTF-8"/>
        </Console>
        <!-- ファイル出力1 -->
        <RollingFile name="File_App1" fileName="${sys:logFilePathName1}" filePattern="${sys:logRollingFilePathName1}">
            <!-- フォーマット -->
            <PatternLayout pattern="${sys:logFormat}" charset="UTF-8"/>
            <Policies>
                <!-- 時間ベース(間隔はパターンの設定次第) -->
                <TimeBasedTriggeringPolicy />
            </Policies>
            <!-- 10世代分まで保持。超えたものは削除する -->
            <DefaultRolloverStrategy>
                <Delete basePath="${sys:logFilePath}" maxDepth="1">
                    <IfFileName glob="${sys:logFileName1}*.log.gz" />
                    <IfAccumulatedFileCount exceeds="10" />
                </Delete>
            </DefaultRolloverStrategy>
        </RollingFile>

        <!-- ファイル出力2 -->
        <RollingFile name="File_App2" fileName="${sys:logFilePathName2}" filePattern="${sys:logRollingFilePathName2}">
            <PatternLayout pattern="${sys:logFormat2}" charset="UTF-8"/>
            <Policies>
                <TimeBasedTriggeringPolicy />
            </Policies>
            <DefaultRolloverStrategy>
                <Delete basePath="${sys:logFilePath}" maxDepth="1">
                    <IfFileName glob="${sys:logFileName2}*.log.gz" />
                    <IfAccumulatedFileCount exceeds="10" />
                </Delete>
            </DefaultRolloverStrategy>
        </RollingFile>
    </Appenders>

    <!--ロガー
        コンソール出力 & ファイル出力など複数の appender を利用する場合、その分書く。
     com.example.springlog4j2 などは、コンソール出力もファイル出力もする。
        なお、上位 / 下位の関係があるロガーは下位の後に上位が実行される。
        そのため、Console を両方のロガーで appenderRef していると2回出力される。
        additivity=false は上位は動かないようにする設定。
    -->
    <Loggers>
        <!-- ルート -->
        <Root level="ERROR">
            <AppenderRef ref="Console"/>
        </Root>
        <Logger name="com.example.springlog4j2" level="INFO" additivity="false">
            <AppenderRef ref="Console"/>
            <AppenderRef ref="File_App1"/>
        </Logger>
        <Logger name="com.example.springlog4j2.hoge" level="DEBUG" additivity="false">
            <AppenderRef ref="Console"/>
            <AppenderRef ref="File_App2"/>
        </Logger>
    </Loggers>
</configuration>

上記の場合の出力結果は以下のようになります。
指定したとおり、app1 は INFO 以上のログが出ていて、app2 は DEBUG でもでています。
また、コンソールには両方でていますね。フォーマットも指定した通りです。

2023/01/07 23:31:03 INFO  [restartedMain] - The following 1 profile is active: "dev"
2023/01/07 23:31:04 INFO  [restartedMain] - Started SpringLog4j2Application in 2.098 seconds (JVM running for 3.818)
2023/01/07 23:31:04 INFO  [restartedMain] - info: exitCode: 0
2023/01/07 23:31:04 WARN  [restartedMain] - warn: exitCode: 0
2023/01/07 23:31:04 ERROR [restartedMain] - error: exitCode: 0
2023/01/07 23:31:04 DEBUG [restartedMain] - exampleClass1 hello テスト
023/01/07 23:31:03 INFO  [restartedMain] - The following 1 profile is active: "dev"
2023/01/07 23:31:04 INFO  [restartedMain] - Started SpringLog4j2Application in 2.098 seconds (JVM running for 3.818)
2023/01/07 23:31:04 INFO  [restartedMain] - info: exitCode: 0
2023/01/07 23:31:04 WARN  [restartedMain] - warn: exitCode: 0
2023/01/07 23:31:04 ERROR [restartedMain] - error: exitCode: 0
test 2023/01/07 23:31:04 DEBUG [restartedMain] - exampleClass1 hello テスト

ちなみに、環境変数で profile=prd にして、log4j2-prd.xml を用意すると、ちゃんとこちらの設定ファイルを読んで動いてくれます。