Spring Boot + Logbackでログを出力する

こんにちは、@seitです。 今回は、社内のWebシステムでログ出力周りをいじることがあったので、その時の備忘録です。

サンプルアプリ

やりたいこと

Spring Bootで、ログの出力先(標準出力、ファイル出力)を細かく切り分けたい。
・全てのログを標準出力とファイルに出力する
・一部のログは別のファイルにも切り出して出力する
・ログファイルはローテーションする
・ローテーションルールを指定する

Log出力で使用するライブラリ

Logbak+SLF4J

最初、過去に使ったことのあるlog4jを考えていましたが、公式によると"Logback は log4j プロジェクトの後継プロジェクトです。log4j の創始者であるCekiGülcü によって設計された"とのことなので、今回はLogback+SLF4Jにしました。

実装

Logbackは"spring-boot-starter-web"に含まれるため、Spring bootを利用している場合は新たに依存性を追加する必要はありません。
Logbackの設定ファイルである"logback-spring.xml"を"resources"フォルダの下に作成します。
今回は以下のようにします。

logback-spring.xml

<configuration>

    <!-- 標準出力用設定 --> ・・・① 
    <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
        <encoder>
            <charset>UTF-8</charset>
            <pattern>%d{yyyy/MM/dd HH:mm:ss} %-5level [%thread] - %msg%n</pattern>
        </encoder>
    </appender>

    <!-- ファイルA出力用設定 --> ・・・②
    <appender name="FILE_A" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <!-- 出力先ファイルパス -->
        <file>/<任意のパス>/alog.log</file>

        <!-- ログのローテーション設定 -->
        <rollingPolicy class="ch.qos.logback.core.rolling.FixedWindowRollingPolicy">
            <!-- 世代管理されるファイルの命名パターン -->
            <fileNamePattern>/<任意のパス>/alog.log.%i</fileNamePattern>    <!-- %iはインデックス。minIndexからMaxIndexまでの連番 -->
            <minIndex>1</minIndex>
            <maxIndex>7</maxIndex>      <!-- 最大7ファイルまで世代管理 -->
        </rollingPolicy>

        <!-- ローテーションのトリガ。10MBに達した時点でローテーション。 -->
        <triggeringPolicy class="ch.qos.logback.core.rolling.SizeBasedTriggeringPolicy">
            <maxFileSize>10MB</maxFileSize>
        </triggeringPolicy>

        <encoder>
            <charset>UTF-8</charset>
            <pattern>%d{yyyy/MM/dd HH:mm:ss} %-5level [%thread] - %msg%n</pattern>
        </encoder>
    </appender>

    <!-- ファイルB出力用設定 --> ・・・③
    <appender name="FILE_B" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <file>/<任意のパス>/blog.log</file>
        
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <fileNamePattern>/<任意のパス>/blog.log.%d{yyyy-MM-dd}</fileNamePattern>    
            <!-- %d{yyyy-MM-dd}の設定によって、自動でローテーションがトリガされる。日まで指定すると、毎日ローテーション。 -->

            <!-- 最大30日間保存 -->
            <maxHistory>30</maxHistory>

        </rollingPolicy>
        <encoder>
            <pattern>%d{yyyy/MM/dd HH:mm:ss} %-5level [%thread] - %msg%n</pattern>
        </encoder>
    </appender>

    <!-- rootロガーを継承したサブロガー。rootロガーの設定を引き継ぎつつ、追加でファイルBにもログを出す --> ・・・④
    <logger name="com.example" level="INFO">
        <appender-ref ref="FILE_B" />
    </logger>

    <!-- rootロガー。ログは原則ファイルAと標準出力に出力する -->  ・・・⑤
    <root level="INFO">
        <appender-ref ref="STDOUT" />
        <appender-ref ref="FILE_A" />
    </root>
</configuration>

⑤で、rootロガーは標準出力用のappender①と、ファイル出力用のappender②を参照しているため、rootロガーを使ったログは標準出力とファイルAに出力されます。
④の"com.piyo"ロガーはファイルBに出力するロガーを参照しているため、"com.piyo"ロガーを使うと、ファイルBに出力されます。
ここで重要なのは、ロガーは継承関係を持つことができるということです。
で作成したロガーは全てで定義したrootロガーを継承するため、"com.piyo"ロガーを使うと標準出力とファイルAにも同時に出力されます。
(タグを使用しない場合はデフォルトのrootロガーの設定が継承されます。)
ロガーは、"com.piyo.hoge.fuga..."のように、ロガーの名称を"."で繋ぐことで設定を継承させていくことができます。

root

↑(継承)

com.example

↑(継承)

com.example.hoge

↑(継承)

com.exampleexample.hoge.fuga

この例だと、com.piyo.hoge.fugaロガーを使用する場合、com.piyoの設定を引き継ぐため、ログがファイルBにも出力されることになります。
このloggerタグのname属性にパッケージ名(今回はcom.piyo)を指定しておくことで、com.piyo以下のパっケージのクラスで継承元のロガーを利用することができたりします(以下で説明)。

ログ出力コード

ログを出力するには、以下のように実装します。

com.example.logback.demo;

import org.slf4j.Logger;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;

@Controller
public class SampleController {

    Logger loggerA = org.slf4j.LoggerFactory.getLogger("com.hoge"); ・・・①
    Logger loggerB = org.slf4j.LoggerFactory.getLogger(getClass().getPackage().getName()); ・・・②

    @GetMapping("/")       // URLのパスの指定
    private String top() { // リクエストを受け付けるメソッド

        loggerA.info("これはrootロガーのみ継承されているためFILE_Aのみに出力されます。");
        loggerB.info("これはcom.exampleロガーを継承しているのでFILE_AとFILE_Bに出力されます。");

        return "/index";
    }
}

getLoggerの引数に、取得するロガー名称を指定します。
①ではrootロガーのみ継承されることになるため、ファイルBには出力されず、標準出力とファイルAにのみ出力されます。
②では"com.example"ロガーを継承した"com.example.logback.demo.SampleController"ロガーが生成され、ログがファイルBにも出力されます。

以上です。

www.wantedly.com