Spring Boot 3.4 以降は、logging.structured.format.console に ecs・gelf・logstash のどれかを書くだけで、ログが1行1件の JSON になります。 logback.xml の編集も、追加のライブラリも使いません。

logging:
  structured:
    format:
      console: ecs      # ecs / gelf / logstash / 自作クラスの完全修飾名
{"@timestamp":"2024-01-01T10:15:00.067462556Z","log":{"level":"INFO","logger":"org.example.Application"},"process":{"pid":39599,"thread":{"name":"main"}},"service":{"name":"simple"},"message":"No active profile set, falling back to 1 default profile: \"default\"","ecs":{"version":"8.11"}}

(上は Spring Boot リファレンスに載っている ECS 形式の出力例です。)

値 形式 主な送り先 時刻の形
ecs Elastic Common Schema 8.11 Elasticsearch・Kibana・OpenTelemetry 系 @timestamp(UTC の ISO 8601)
gelf Graylog Extended Log Format 1.1 Graylog timestamp(エポック秒、小数3桁)
logstash logstash-logback-encoder 互換 Logstash・Fluentd など @timestamp(実行環境のタイムゾーン付き)

確認環境:Spring Boot 4.1.1・3.5.0・3.4.0 のソース(GitHub のタグ)を clone し、Logback 用の3つのフォーマッター(ElasticCommonSchemaStructuredLogFormatter など)とそのテストコードで、出力されるフィールドと順番を確かめました。設定はリファレンス「Logging」の Structured Logging の章で確認しています(確認日 2026年10月13日)。

本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。

(記載例)出典:株式会社RJC「Spring Boot構造化ログ|logging.structuredでJSON出力」

設定:コンソールとファイル

コンソールは logging.structured.format.console、ファイルは logging.structured.format.file で、別々に決められます。 ファイルに出すには、いつもどおり logging.file.name(または logging.file.path)も要ります。

spring:
  application:
    name: order-api          # ECS の service.name、GELF の host に使われる
    version: 1.4.0           # ECS の service.version に使われる

logging:
  structured:
    format:
      console: ecs
      file: logstash
    ecs:
      service:
        environment: production
        node-name: ap-1
  file:
    name: logs/app.json

手元では普通の1行ログ、本番だけ JSON にしたいときは、プロファイルで分けます。

# application.yml(共通。何も書かなければ従来のテキスト形式)
---
spring:
  config:
    activate:
      on-profile: prod
logging:
  structured:
    format:
      console: ecs

プロファイルの切り替えは、関連記事「Spring Bootプロファイル切り替え|application.ymlと優先順位」で扱っています。

構造化ログを有効にすると、起動時のバナー(:: Spring Boot ::)は出ません。 Spring Boot のスモークテスト(SampleStructuredLoggingApplicationTests)が、バナーが出ないことを確かめています。

出力フィールド一覧(3形式の比較)

Spring Boot 4.1.1 の Logback 用フォーマッターのソースから、出力される項目を書き出し順に並べました。

内容 ecs gelf logstash
時刻 @timestamp timestamp @timestamp
形式の版 ecs.version("8.11") version("1.1") @version("1")
ログレベル log.level("INFO") level(6)・_level_name level・level_value(20000)
ロガー名 log.logger _log_logger logger_name
スレッド名 process.thread.name _process_thread_name thread_name
プロセス ID process.pid _process_pid (なし)
メッセージ message short_message message
サービス名 service.name host (なし)
サービスの版 service.version _service_version (なし)
環境・ノード service.environment・service.node.name (なし) (なし)
MDC・キーと値 そのままの名前(. で入れ子) 先頭に _ を付ける そのままの名前
マーカー tags(配列) (なし) tags(配列)
例外 error.type・error.message・error.stack_trace full_message・_error_type・_error_message・_error_stack_trace stack_trace

表の log.level などは、JSON の入れ子を . でつないだ書き方です(ECS では {"log":{"level":"INFO"}})。

ecs の出力(4.1.1)

フォーマッターは、@timestamp → log → process → service → message → MDC・キーと値 → error → tags → ecs の順に書き出します。テストコード(ElasticCommonSchemaStructuredLogFormatterTests)が使う値を、この順に並べると次のようになります。

{"@timestamp":"2024-07-02T08:49:53Z","log":{"level":"INFO","logger":"org.example.Test"},"process":{"pid":1,"thread":{"name":"main"}},"service":{"name":"name","version":"1.0.0","environment":"test","node":{"name":"node-1"}},"message":"message","mdc-1":"mdc-v-1","kv-1":"kv-v-1","ecs":{"version":"8.11"}}
設定 出力先 指定しないとき
logging.structured.ecs.service.name service.name spring.application.name
logging.structured.ecs.service.version service.version spring.application.version
logging.structured.ecs.service.environment service.environment 出ない
logging.structured.ecs.service.node-name service.node.name 出ない

ECS では、MDC のキーに . があると入れ子になります。 テストでは、MDC の a1.b1.c1 が {"a1":{"b1":{"c1":"A1B1C1"}}} になることを確かめています。

gelf の出力(4.1.1)

{"version":"1.1","short_message":"message","timestamp":1719910193.000,"level":6,"_level_name":"INFO","_process_pid":1,"_process_thread_name":"main","host":"name","_service_version":"1.0.0","_log_logger":"org.example.Test","_mdc-1":"mdc-v-1","_kv-1":"kv-v-1"}

(テストコード GraylogExtendedLogFormatStructuredLogFormatterTests の値を、ソースの書き出し順に並べたものです。)

GELF ならではの決まりが、ソースに入っています。

決まり ソースの動き
level は数値 syslog の重大度に変換(INFO は 6)
timestamp はエポック秒 ミリ秒を小数3桁で出す(1719910193.123 など)
追加の項目は _ 始まり MDC の mdc-1 は _mdc-1 になる
使えない名前 ^[\w.\-]*$ に合わない名前と、id・_id は出さずに WARN を出す
空のメッセージ short_message を (blank) にする(Graylog 6 で空がエラーになるため、とコメントあり)
host logging.structured.gelf.host。無ければ spring.application.name

logstash の出力(4.1.1)

{"@timestamp":"2024-01-01T10:15:00.111037681+02:00","@version":"1","message":"No active profile set, falling back to 1 default profile: \"default\"","logger_name":"org.example.Application","thread_name":"main","level":"INFO","level_value":20000}

(リファレンスの出力例です。)@timestamp は、実行環境のタイムゾーンのオフセット付きです(ソースでは ZoneId.systemDefault() を使用)。コンテナで UTC にそろえたいときは、TZ 環境変数や -Duser.timezone=UTC で JVM のタイムゾーンを決めます。ECS の @timestamp は UTC(末尾 Z)です。

3.4 と 3.5 以降で、ECS の JSON の形が違う

Spring Boot 3.4.0 の ECS は、"log.level" のように . を含む名前の平らな JSON でした。3.5.0 以降は {"log":{"level":...}} の入れ子です。 3.4.0 と 3.5.0 のソースを比べて確かめました。

// 3.4.x(平ら)
{"@timestamp":"…","log.level":"INFO","process.pid":39599,"process.thread.name":"main","service.name":"simple","log.logger":"org.example.Application","message":"…","ecs.version":"8.11"}

// 3.5.0 以降(入れ子)
{"@timestamp":"…","log":{"level":"INFO","logger":"org.example.Application"},"process":{"pid":39599,"thread":{"name":"main"}},"service":{"name":"simple"},"message":"…","ecs":{"version":"8.11"}}

(3.4.0 のソースの書き出し順と項目名から作った形です。値は省略しています。)

jq '.["log.level"]' のように平らな名前で読んでいるスクリプトやダッシュボードは、3.5 に上げると値が取れなくなります。 jq '.log.level' に書き換えます。

版ごとに使えるプロパティも違います。

プロパティ 3.4 3.5 4.0・4.1
logging.structured.format.console・.file あり あり あり
logging.structured.ecs.service.*・gelf.* あり あり あり
logging.structured.json.include・exclude・rename・add あり あり あり
logging.structured.json.customizer 1クラス 複数クラス 複数クラス
logging.structured.json.stacktrace.* なし あり あり
logging.structured.json.context.include・prefix なし あり あり
ECS の例外の項目名 error.stack_trace(平ら) error の入れ子 error の入れ子

MDC とキー・値を JSON に足す

MDC(スレッドに結び付けた値)と、SLF4J の fluent API の addKeyValue は、3形式とも JSON の項目になります。

package com.example.orderapi;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.slf4j.MDC;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class OrderController {

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

    @PostMapping("/orders/{id}/ship")
    public void ship(@PathVariable String id) {
        MDC.put("order.id", id);             // ECS では {"order":{"id":"…"}}
        try {
            log.atInfo()
               .setMessage("出荷しました")
               .addKeyValue("carrier", "yamato")
               .addKeyValue("items", 3)
               .log();
        } finally {
            MDC.remove("order.id");
        }
    }
}

ECS では、MDC の order.id が入れ子になり、carrier と items がそのまま項目になります。GELF では _order.id・_carrier・_items です。

書き方 ecs gelf logstash
MDC.put("order.id", "A1") "order":{"id":"A1"} "_order.id":"A1" "order.id":"A1"
.addKeyValue("carrier", "yamato") "carrier":"yamato" "_carrier":"yamato" "carrier":"yamato"

Micrometer Tracing を入れていると、MDC の traceId・spanId も同じように出ます。トレースの仕組みは、関連記事「AWS X-Rayの使い方|Lambda・APIのトレースとサービスマップ」でも扱っています。

MDC をまとめて外す・前に名前を付ける(3.5 以降)

logging:
  structured:
    json:
      context:
        include: true      # false にすると MDC・キーと値を出さない(既定 true)
        prefix: ctx        # ECS は ctx.〜 の入れ子、logstash は ctx_〜 になる

ソースでは、ECS は . でつないだうえで入れ子に、logstash は _ でつないだ平らな名前にしています。

例外(スタックトレース)の出方

log.error("失敗しました", e) のように例外を渡すと、形式ごとに次の項目が付きます(4.1.1 のソースとテスト)。

形式 項目 中身
ecs error.type 例外のクラス名(java.lang.RuntimeException)
ecs error.message 例外のメッセージ
ecs error.stack_trace スタックトレース全文(1つの文字列。改行は \n)
gelf full_message メッセージ+空行+スタックトレース
gelf _error_type・_error_message・_error_stack_trace ecs と同じ中身
logstash stack_trace スタックトレース全文

スタックトレースは1つの JSON 文字列に入るので、ログが複数行に割れません。 CloudWatch Logs などで1件のログとして扱えます。CloudWatch Logs での検索は、関連記事「CloudWatch Logs Insightsのクエリ集|エラー・遅いリクエスト」で扱っています。

長さを抑えたいときは、3.5 以降で次のプロパティが使えます。

logging:
  structured:
    json:
      stacktrace:
        root: first              # 原因(root)を先に出す。既定は last(Java と同じ順)
        max-length: 2048         # 文字数の上限
        max-throwable-depth: 30  # 1つのトレースで出すフレーム数の上限
        include-common-frames: false
        include-hashes: true     # スタックトレースのハッシュを付ける

項目の除外・改名・追加

logging:
  structured:
    json:
      exclude: process.pid, log.logger   # この項目を出さない
      rename:
        log.logger: logger_name          # その階層での名前を変える
      add:
        team: payments                   # 固定の項目を足す
        app.region: ap-northeast-1       # ECS では {"app":{"region":…}} になる
プロパティ 動き(ソース)
exclude 指定したパス(. 区切り)の項目を出さない
include 指定したパスだけを出す
rename キーはパス、値はその階層での新しい名前。log.logger: logger_name は "log":{"level":…,"logger_name":…}
add 固定値の項目を足す。ECS では名前の . で入れ子になる
customizer StructuredLoggingJsonMembersCustomizer を実装したクラスで、細かく変える

rename の値は、パスではなく名前です。 上の位置から外へ出したいときは、customizer を使います。Spring Boot のスモークテストは、rename[process.pid]=process.procid と書いた結果、"process.procid" という名前で出ることを確かめています。

自作の形式(StructuredLogFormatter)

JSON 以外の形にもできます。リファレンスとスモークテストにある StructuredLogFormatter の実装です。

package com.example.orderapi.logging;

import ch.qos.logback.classic.pattern.ThrowableProxyConverter;
import ch.qos.logback.classic.spi.ILoggingEvent;
import org.springframework.boot.logging.structured.StructuredLogFormatter;
import org.springframework.core.env.Environment;

public class KeyValueLogFormatter implements StructuredLogFormatter<ILoggingEvent> {

    private final Long pid;
    private final ThrowableProxyConverter throwableProxyConverter;

    // コンストラクタの引数は Spring Boot が渡す(Environment・ThrowableProxyConverter など)
    public KeyValueLogFormatter(Environment environment, ThrowableProxyConverter throwableProxyConverter) {
        this.pid = environment.getProperty("spring.application.pid", Long.class);
        this.throwableProxyConverter = throwableProxyConverter;
    }

    @Override
    public String format(ILoggingEvent event) {
        StringBuilder sb = new StringBuilder();
        sb.append("epoch=").append(event.getInstant().toEpochMilli());
        if (pid != null) {
            sb.append(" pid=").append(pid);
        }
        sb.append(" level=").append(event.getLevel());
        sb.append(" msg=\"").append(event.getFormattedMessage()).append('"');
        if (event.getThrowableProxy() != null) {
            sb.append(" error=\"").append(throwableProxyConverter.convert(event)).append('"');
        }
        return sb.append('\n').toString();
    }
}
logging:
  structured:
    format:
      console: com.example.orderapi.logging.KeyValueLogFormatter

logback-spring.xml を自分で書いている場合

自前の Logback 設定があると、logging.structured.format.* を書いても、そのままでは JSON になりません。 encoder を StructuredLogEncoder に替え、Spring Boot が渡す CONSOLE_LOG_STRUCTURED_FORMAT を読みます(Spring Boot に入っている structured-console-appender.xml と同じ形)。

<?xml version="1.0" encoding="UTF-8"?>
<configuration>
  <include resource="org/springframework/boot/logging/logback/defaults.xml"/>

  <appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
    <encoder class="org.springframework.boot.logging.logback.StructuredLogEncoder">
      <format>${CONSOLE_LOG_STRUCTURED_FORMAT}</format>
      <charset>${CONSOLE_LOG_CHARSET}</charset>
    </encoder>
  </appender>

  <root level="INFO">
    <appender-ref ref="CONSOLE"/>
  </root>
</configuration>

<format>ecs</format> と直接書くこともできます。Log4j2 なら <StructuredLogLayout format="${sys:CONSOLE_LOG_STRUCTURED_FORMAT}" charset="${sys:CONSOLE_LOG_CHARSET}"/> です。logback-spring.xml 全体の書き方は、関連記事「Spring Bootログ設定|logback-spring.xml全文と設定対応表」で扱っています。

よくある質問

Q. logstash-logback-encoder(LogstashEncoder)とどちらを使えばいいですか。 A. Spring Boot の logstash 形式は、logstash-logback-encoder の標準フィールド(@timestamp・@version・message・logger_name・thread_name・level・level_value)に合わせた形です。依存を増やさずに済むのが Spring Boot 側、細かな出力設定(プロバイダーの組み合わせなど)が豊富なのが logstash-logback-encoder 側です。

Q. ローカルで JSON が読みにくいです。 A. プロファイルで本番だけ JSON にするか、jq で整形して読みます(./mvnw spring-boot:run | jq -R 'fromjson? // .')。fromjson? で JSON でない行もそのまま出せます。

Q. 日本語のメッセージは文字化けしませんか。 A. JSON は logging.charset.console の文字コードで書かれます。4.1.1 のソースでは、指定が無いとき、コンソールがあればその文字コード、無ければ JVM の既定の文字コードを使います。コンテナなどで化ける場合は、logging.charset.console: UTF-8 と明示し、受け取る側も UTF-8 にそろえます。

Q. どんな項目を出すべきかの設計は? A. 必須フィールドの考え方は、関連記事「構造化ログ(JSON)設計|必須フィールド一覧とPython・Node実装例」で扱っています。個人情報のマスクは、関連記事「ログ設計の出力項目一覧|ログ設計書テンプレートとマスク」で扱っています。

まとめ

  • logging.structured.format.console: ecs だけで JSON ログになる(Spring Boot 3.4 以降)
  • 形式は ecs・gelf・logstash の3つ。時刻・レベル・MDC の名前の付け方が違う
  • 3.4 の ECS は平らな名前、3.5 以降は入れ子。jq やダッシュボードの参照先に注意
  • MDC と addKeyValue は項目になる。ECS では . 入りの名前が入れ子になる
  • 除外・改名・追加は logging.structured.json.*、スタックトレースの長さは json.stacktrace.*(3.5 以降)

参考・出典

確認日はいずれも 2026年10月13日です。

  • Spring Boot Reference「Logging」(Structured Logging):https://docs.spring.io/spring-boot/reference/features/logging.html#features.logging.structured
  • Spring Blog「Structured logging in Spring Boot 3.4」:https://spring.io/blog/2024/08/23/structured-logging-in-spring-boot-3-4
  • Spring Boot ソース(タグ v4.1.1、logging/logback・logging/structured):https://github.com/spring-projects/spring-boot/tree/v4.1.1/core/spring-boot/src/main/java/org/springframework/boot/logging
  • Elastic Common Schema 8.11 Reference:https://www.elastic.co/guide/en/ecs/8.11/ecs-reference.html
  • Graylog「GELF」:https://go2docs.graylog.org/current/getting_in_log_data/gelf.html
  • logstash-logback-encoder「Standard Fields」:https://github.com/logfellow/logstash-logback-encoder#standard-fields

本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。

(記載例)出典:株式会社RJC「Spring Boot構造化ログ|logging.structuredでJSON出力」

株式会社RJC ― SI事業・SES事業・AI駆動開発。RJCは一緒に成長を楽しめる会社です。

WE ARE HIRING

RJCで一緒に開発しながら、
成長を楽しみませんか?

RJCは、Web・モバイル・AIを活用した開発プロジェクトで、テックリードやPM・PMOも活躍するシステム開発会社です。会社を知る、待遇を確かめる、話を聞いてみる。気になるところ見てみてください!

  • 127日年間休日
  • 12時間平均残業時間
  • 毎日ガチャ遊びココロも大切にする福利厚生。アマギフなどの賞品ラインナップ!

ほかにも、チケットレストラン、書籍読み放題、2年ごとの慰労報奨(休暇 or 金一封)、11期連続の黒字決算。

ABOUT RJC RJCがどんな会社か知る 考え方、研修、働き方、福利厚生、社員の前職まで。RJCのことが丸わかり! RJC丸わかりページへ JOB DESCRIPTION 仕事内容・待遇を見てみる 仕事内容、給与・待遇、選考の流れ。経験者も未経験も!応募前に知りたいこと、まとめました! 募集要項を見る ENTRY エントリーする エントリーは1〜2分・履歴書不要です。まずは話を聞いてみたい、という方でも歓迎です! エントリーフォームへ

RJCで一緒に開発しながら、 成長を楽しみませんか?