Spring Boot のログを出力する

# Spring Boot のログを出力する

こんにちは、@studio_meowtoon です。今回は、WSL Ubuntu で Spring Boot Web アプリケーションのログを出力する方法を紹介します。
![spring-boot_on_ubuntu.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3206449/2cc95772-f91c-63f1-44ad-d4d6f785844e.png)

## 目的

Windows 11 の Linux でクラウド開発します。

こちらから記事の一覧がご覧いただけます。

https://qiita.com/studio_meowtoon/items/6119ab05d8b13527efd8

## 実現すること

ローカル環境の Spring Boot アプリにログを出力する設定を組み込み、標準出力とファイルにログを出力します。 

:::note info
ログを出力する SLF4J 実装ライブラリには [Logback](https://logback.qos.ch/) を使用します。  
:::

## 技術トピック

### SLF4J (Simple Logging Facade for Java) とは？  

<details>
<summary>こちらを展開してご覧いただけます。</summary>

### [SLF4J](https://www.slf4j.org/) (Simple Logging Facade for Java)

:::note info

|[SLF4J](https://www.slf4j.org/)|
|:--|
|SLF4J は、Java アプリケーションで使用されるオープンソースのロギングフレームワークです。SLF4J は、ログ記述に関する Java のAPI を抽象化して、アプリケーションのコードをロギングフレームワークに依存しない形で書くことを可能にします。つまり、アプリケーションが複数のロギングフレームワークに依存することなく、ロギング機能を使用できるようになります。|  

|[Logback](https://logback.qos.ch/)|
|:--|
|Logback は、SLF4J API を実装するバックエンドの1つです。つまり、SLF4J API を使用してログを出力するアプリケーションで、Logback をバックエンドとして使用できます。Logback は、高速で柔軟性があり、多くの機能を提供するロギングフレームワークであり、SLF4J と組み合わせて使用することで、一貫性のあるログ記述を維持できます。 |

したがって、SLF4J と Logback は、アプリケーションのロギング機能を改善するため一緒に使用されることが一般的です。組み合わせて使用することで、柔軟性、パフォーマンス、一貫性のあるログ出力を提供できます。
:::

</details>

## 開発環境

+ Windows 11 Home 23H2 を使用しています。  

:::note info
[WSL](https://learn.microsoft.com/ja-jp/windows/wsl/about) の Ubuntu を操作しますので macOS の方も参考にして頂けます。  
:::

<details>

WSL (Microsoft Store アプリ版) [※ こちらの関連記事からインストール方法をご確認いただけます](https://qiita.com/studio_meowtoon/items/cd0c4a607d451dbf5430)

```sh
> wsl --version
WSL バージョン: 2.2.4.0
カーネル バージョン: 5.15.153.1-2
WSLg バージョン: 1.0.61
```

Ubuntu [※ こちらの関連記事からインストール方法をご確認いただけます](https://qiita.com/studio_meowtoon/items/cd0c4a607d451dbf5430)

```sh
$ lsb_release -a
No LSB modules are available.
Distributor ID: Ubuntu
Description:    Ubuntu 24.04 LTS
Release:        24.04
Codename:       noble
```

Java JDK [※ こちらの関連記事からインストール方法をご確認いただけます](https://qiita.com/studio_meowtoon/items/4d11e94a2389758759cd)

```sh
$ java -version
openjdk version "11.0.24" 2024-07-16
OpenJDK Runtime Environment (build 11.0.24+8-post-Ubuntu-1ubuntu324.04.1)
OpenJDK 64-Bit Server VM (build 11.0.24+8-post-Ubuntu-1ubuntu324.04.1, mixed mode, sharing)
```

Maven [※ こちらの関連記事からインストール方法をご確認いただけます](https://qiita.com/studio_meowtoon/items/f4cdc4039158153365fb)

```sh
$ mvn -version
Apache Maven 3.8.7
Maven home: /usr/share/maven
Java version: 11.0.24, vendor: Ubuntu, runtime: /usr/lib/jvm/java-11-openjdk-amd64
```

</details>

:::note warn
この記事では基本的に Ubuntu のターミナルで操作を行います。**Vim を使用してコピペする方法**をはじめて学ぶ人のために、以下の記事で手順を紹介しています。ぜひ挑戦してみてください。
:::

https://qiita.com/studio_meowtoon/items/7771ad48676926e41463

## 作成する Web アプリケーションの仕様

|No|エンドポイント|HTTPメソッド|MIME タイプ|
|:--|:--|:--|:--|
|1|/api/data|GET|application/json|

<details>
<summary>説明を開きます。</summary>

:::note info
**/api/data** という**エンドポイント**に対して **HTTP GET** リクエストを送信すると、**JSON データ**がレスポンスされるシンプルな Web サービスを実装します。
:::

</details>

## ログを出力する手順

### Spring Boot アプリの作成

こちらの関連記事からご確認いただけます。

https://qiita.com/studio_meowtoon/items/37ac0082a3228e4de570

### プロジェクトフォルダーに移動

プロジェクトフォルダーに移動します。
※ ~/tmp/hello-spring-boot をプロジェクトフォルダーとします。  

```sh
$ cd ~/tmp/hello-spring-boot
```

### ※参考: Spring MCV などの場合

<details>
<summary>Spring Boot では、pom.xml に依存を追加する必要はありません。</summary>

### ログを出力するライブラリを追加 (Spring MVC など)

#### pom.xml の修正

pom.xml を編集します。

```sh
$ vim pom.xml
```

ファイルの内容

```xml:pom.xml ※一部抜粋
<!-- Log -->
<dependency>
    <groupId>ch.qos.logback</groupId>
    <artifactId>logback-core</artifactId>
    <version>1.2.6</version>
</dependency>
<dependency>
    <groupId>ch.qos.logback</groupId>
    <artifactId>logback-classic</artifactId>
    <version>1.2.6</version>
</dependency>
<dependency>
    <groupId>org.slf4j</groupId>
    <artifactId>slf4j-api</artifactId>
    <version>1.7.30</version>
</dependency>
```

:::note info
LogBack を使用する場合、通常 **logback-core** と **logback-classic** の2つの依存関係を含める必要があります。これらの依存関係には、ログ記録機能と SLF4J (Simple Logging Facade for Java) の実装が含まれています。したがって **logback-classic は SLF4J に依存**し、**logback-core は logback-classic に依存**します。
:::

</details>

### Spring Boot の設定ファイルを追加

**application.properties** ファイルを作成します。

```sh
$ mkdir -p src/main/resources
$ vim src/main/resources/application.properties
```

ファイルの内容

```sh:application.properties
spring.profiles.active=develop
```

<details>
<summary>説明を開きます。</summary>

:::note info
**spring.profiles.active** は、Spring Boot アプリのプロファイルを設定するためのプロパティの1つです。このプロパティは、アプリの起動時にアクティブ化するプロファイルを指定するために使用されます。  

|値|内容|
|:--|:--|
|develop|たとえば develop プロファイルには、開発時に便利な機能やロギングレベルが DEBUG に設定されるような設定が含まれる場合があります。|
|production|一方、本番環境での動作に適した設定が含まれる production プロファイルを指定することもできます。|

:::

</details>

### ログ設定ファイルを追加

:::note warn
Spring Boot アプリケーションでは、デフォルトで **src/main/resources** ディレクトリにある **logback-spring.xml** ファイルが自動的に読み込まれます。  
:::

logback-spring.xml ファイルを作成します。

```sh
$ vim src/main/resources/logback-spring.xml
```

ファイルの内容

```xml:logback-spring.xml
<?xml version="1.0" encoding="UTF-8"?>
<configuration>

    <springProfile name="develop">
        <include resource="org/springframework/boot/logging/logback/defaults.xml" />
        <include resource="org/springframework/boot/logging/logback/console-appender.xml" />

        <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
            <encoder>
                <pattern>[%-5le] %lo{0}.%M:%L - %msg%n</pattern>
            </encoder>
        </appender>

        <appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
            <file>./log/app.log</file>
            <append>true</append>
            <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
                <fileNamePattern>./log/app.%d{yyyy-MM-dd}_%i.log.gz</fileNamePattern>
                <timeBasedFileNamingAndTriggeringPolicy
                    class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
                    <maxFileSize>5MB</maxFileSize>
                </timeBasedFileNamingAndTriggeringPolicy>
                <maxHistory>31</maxHistory>
                <cleanHistoryOnStart>true</cleanHistoryOnStart>
            </rollingPolicy>
            <encoder>
                <pattern>%date{yyyy-MM-dd HH:mm:ss.SSS} [%-5le] [%t] %C.%M:%L - %m%n</pattern>
            </encoder>
        </appender>

        <logger name="org.springframework" level="INFO" />
        <logger name="com.example" level="DEBUG" />

        <root level="WARN">
            <appender-ref ref="STDOUT" />
            <appender-ref ref="FILE" />
        </root>
    </springProfile>

</configuration>
```

<details>
<summary>説明を開きます。</summary>

```xml:logback-spring.xml ※一部抜粋
<include resource="org/springframework/boot/logging/logback/defaults.xml" />
<include resource="org/springframework/boot/logging/logback/console-appender.xml" />
```

:::note info
この設定は Spring Boot ロギングのデフォルト設定ファイルをインクルードしています。[※参考: 公式 Spring Boot ロギングのデフォルト設定](https://github.com/spring-projects/spring-boot/tree/main/spring-boot-project/spring-boot/src/main/resources/org/springframework/boot/logging/logback)  
:::

```xml:logback-spring.xml ※一部抜粋
<pattern>[%-5le] %lo{0}.%M:%L - %msg%n</pattern>
```

:::note info
この設定はログ出力のフォーマットを指定しています。  

|表記|意味|
|:--|:--|
|%le |ログイベントのレベル|
|%lo |ロガー名            |
|%M  |メソッド名          |
|%L  |行番号              |
|%msg|ログメッセージ本体  |
|%n  |改行                |

:::

```xml:logback-spring.xml 
<pattern>%date{yyyy-MM-dd HH:mm:ss.SSS} [%-5le] [%t] %C.%M:%L - %m%n</pattern>
```

:::note info
この設定はログ出力のフォーマットを指定しています。  

|表記|意味|
|:--|:--|
|%date{yyyy-MM-dd HH:mm:ss.SSS}|ログが出力された日時を指定されたフォーマットで表示します。|
|%-5le                         |ログレベルを5文字の幅で表示します。%-5は左揃えを指定するためのフラグです。|
|%t                            |ログを出力したスレッド名を表示します。|
|%C.%M:%L                      |ログを出力したクラス、メソッド、行番号を表示します。|
|%m                            |ログメッセージを表示します。|
|%n                            |改行を挿入します。|

:::

```xml:logback-spring.xml ※一部抜粋
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
    <fileNamePattern>./log/app.%d{yyyy-MM-dd}_%i.log.gz</fileNamePattern>
    <timeBasedFileNamingAndTriggeringPolicy
        class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
        <maxFileSize>5MB</maxFileSize>
    </timeBasedFileNamingAndTriggeringPolicy>
    <maxHistory>31</maxHistory>
    <cleanHistoryOnStart>true</cleanHistoryOnStart>
</rollingPolicy>
```

:::note info
この設定は、時間単位でログファイルをローテーションする方法を指定しています。

|内容|
|:--|
|ログファイル名は ./log/app.%d{yyyy-MM-dd}_%i.log.gz ファイル名パターンにしたがって命名されます。|
|また SizeAndTimeBasedFNATP クラスを使用して、ファイルサイズと時間に基づくローテーションを設定しています。|
|ログファイルの最大サイズは 5MB に設定されており、maxHistory プロパティを使用してログファイルの最大保存日数を 31 日に設定しています。|
|cleanHistoryOnStart プロパティはアプリケーションの起動時に古いログファイルを自動的に削除するかどうかを設定します。|

:::

</details>

### Java コードにログを出力するコードを追加

Java ソースコードを編集します。

```sh
$ vim src/main/java/com/example/springboot/controller/HelloController.java
```

ファイルの内容

```java:HelloController.java
package com.example.springboot.controller;

import java.util.Map;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/api")
public class HelloController {

    private static final Logger log = LoggerFactory.getLogger(HelloController.class);

    @GetMapping("/data")
    public Map<String, String> getData() {
        Map<String, String> map = Map.of("message", "Hello World!");
        log.info("Hello World!");
        return map;
    }
}
```

<details>
<summary>説明を開きます。</summary>

```java:HelloController.java ※一部抜粋
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
```

:::note info
SLF4J を使用して Java のログを出力するために必要なクラス、インタフェースをインポートしています。
:::

```java:HelloController.java ※一部抜粋
private static final Logger log = LoggerFactory.getLogger(HelloController.class);
```

:::note info
LoggerFactory.getLogger() メソッドには、ロギング機能を使用するクラスの名前を渡します。通常、クラス単位でロガーを定義するためクラスの完全修飾名を指定します。このロガーオブジェクトは、SLF4J を通じてログ出力のために使用されます。  
:::

```java:HelloController.java ※一部抜粋
log.info("Hello World!");
```

:::note info
SLF4J の Logger インタフェース info() メソッドを呼び出してログにメッセージ "Hello World!" を出力しています。  
:::

#### SLF4J のログレベル

|レベル|概要|
|:--|:--|
|TRACE|デバッグに使用される非常に詳細な情報|
|DEBUG|デバッグ情報|
|INFO |一般的な情報|
|WARN |警告情報|
|ERROR|エラー情報|
|FATAL|致命的なエラー|

</details>

### プロジェクトの構成

プロジェクトのファイル構成を表示してみます。

```sh
$ tree -I log -I target
.
├── pom.xml
├── src
    └── main
        ├── java
        │   └── com
        │       └── example
        │           └── springboot
        │               ├── Application.java
        │               └── controller
        │                   └── HelloController.java
        └── resources
            ├── application.properties
            └── logback-spring.xml
```

#### アプリのビルド

Java アプリをビルドします。(※ 参考)
※ target/app.jar が作成されます。

:::note warn
package フェーズで作成される app.jar がなくても、**spring-boot:run フェーズを使用してアプリケーションを起動できます**。したがって、package フェーズは必須でないことに注意してください。
:::

```sh
$ mvn clean package
```

#### アプリを起動

アプリを起動します。  
※ アプリを停止するときは ctrl + C を押します。

```sh
$ mvn spring-boot:run
```

:::note info
ここまでの手順で、**Ubuntu** でアプリの **JAR ファイル**を起動することができました。
:::

### アプリの動作確認

別ターミナルから curl コマンドで確認します。

```sh
$ curl http://localhost:8080/api/data -w '\n'
{"message":"Hello World!"}
```

ターミナルに以下のようにログが表示されました。

```sh
[INFO ] HelloController.getData:20 - Hello World!
```

また、/log/app.log に以下のように追記されました。

```sh
2023-03-15 10:35:44.331 [INFO ] [http-nio-8080-exec-3] com.example.springboot.controller.HelloController.getData:20 - Hello World!
```

:::note info
ここまでの手順で、**標準出力**と**ファイル**に**ログ**を出力することができました。
:::

### 非同期でログを出力する設定

<details>
<summary>こちらを展開してご覧いただけます。</summary>

#### 非同期バージョン logback-spring.xml

logback-spring.xml を修正します。

```sh
$ vim src/main/resources/logback-spring.xml
```

ファイルの内容

```xml:logback-spring.xml
<?xml version="1.0" encoding="UTF-8"?>
<configuration>

    <springProfile name="develop">
        <include resource="org/springframework/boot/logging/logback/defaults.xml" />
        <include resource="org/springframework/boot/logging/logback/console-appender.xml" />

        <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
            <encoder>
                <pattern>[%-5le] %lo{0}.%M:%L - %msg%n</pattern>
            </encoder>
        </appender>

        <appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
            <file>./log/app.log</file>
            <append>true</append>
            <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
                <fileNamePattern>./log/app.%d{yyyy-MM-dd}_%i.log.gz</fileNamePattern>
                <timeBasedFileNamingAndTriggeringPolicy
                    class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
                    <maxFileSize>5MB</maxFileSize>
                </timeBasedFileNamingAndTriggeringPolicy>
                <maxHistory>31</maxHistory>
                <cleanHistoryOnStart>true</cleanHistoryOnStart>
            </rollingPolicy>
            <encoder>
                <pattern>%date{yyyy-MM-dd HH:mm:ss.SSS} [%-5le] [%t] %C.%M:%L - %m%n</pattern>
            </encoder>
        </appender>

        <logger name="org.springframework" level="INFO" />
        <logger name="com.example" level="DEBUG" />

        <appender name="ASYNC_STDOUT" class="ch.qos.logback.classic.AsyncAppender">
            <appender-ref ref="STDOUT" />
            <includeCallerData>true</includeCallerData>
        </appender>

        <appender name="ASYNC_FILE" class="ch.qos.logback.classic.AsyncAppender">
            <queueSize>512</queueSize>
            <neverBlock>false</neverBlock>
            <appender-ref ref="FILE" />
            <includeCallerData>true</includeCallerData>
        </appender>

        <root level="WARN">
            <appender-ref ref="ASYNC_STDOUT" />
            <appender-ref ref="ASYNC_FILE" />
        </root>
    </springProfile>

</configuration>
```

:::note info
この例では、AsyncAppender を定義して、FILE アペンダーと STDOUT アペンダーを非同期に呼び出すように設定しています。

|パラメーター|値|内容|
|:--|:--|:--|
|queueSize|512|AsyncAppender が使用するキューのサイズを指定します。キューはログイベントを一時的に保持するために使用されます。設定値は必要に応じて調整できますが、設定値を大きくしすぎるとメモリ消費量が増加することがあります。|
|neverBlock|false|キューがいっぱいの場合にログイベントをブロックするかどうかを制御します。この値が true の場合、キューがいっぱいでもログイベントをブロックせずに破棄します。false の場合、キューがいっぱいの場合にはログイベントをブロックします。|
|includeCallerData|true|Logback のロギングイベントに呼び出し元情報（caller data）を含めるように設定するための設定です。呼び出し元情報とは、ログイベントが発生した場所（クラス名、メソッド名、行番号）を示す情報です。この設定を有効にすると、呼び出し元情報をロギングイベントに含めることができます。ただし、この設定は、パフォーマンスの観点から、非同期アペンダーを使用する場合には注意が必要です。|

:::

</details>

## まとめ

Ubuntu に構築したシンプルな Java 開発環境で、Spring Boot Web サービスからログを出力することができました。

:::note warn
実際の業務での Spring Boot の使用ケースでは、構成ファイルなどが追加されるため、ここで示したようなシンプルな構成とは異なる場合があります。しかしながら、最小構成の例を学ぶことで、Spring Boot がどのような構成が必要なのかを理解できます。
:::

どうでしたか？　WSL Ubuntu で、Spring Boot Web アプリケーションのログ出力の設定を、手軽に行うことができます。ぜひお試しください。今後も Java の開発環境などを紹介しますので、ぜひお楽しみにしてください。