kagamihogeの日記

kagamihogeの日記です。

spring-bootでlogstash-logback-encoderを使用してログをJSON形式にする

基本的な使い方のサンプルコード

plugins {
    id 'java'
    id 'org.springframework.boot' version '3.3.1'
    id 'io.spring.dependency-management' version '1.1.5'
}

group = 'com.example'
version = '0.0.1-SNAPSHOT'

java {
    toolchain {
        languageVersion = JavaLanguageVersion.of(17)
    }
}

configurations {
    compileOnly {
        extendsFrom annotationProcessor
    }
}

repositories {
    mavenCentral()
}

dependencies {
    implementation 'org.springframework.boot:spring-boot-starter-web'
    implementation 'net.logstash.logback:logstash-logback-encoder:7.4'

    compileOnly 'org.projectlombok:lombok'
    annotationProcessor 'org.projectlombok:lombok'
    testImplementation 'org.springframework.boot:spring-boot-starter-test'
    testRuntimeOnly 'org.junit.platform:junit-platform-launcher'
}

tasks.named('test') {
    useJUnitPlatform()
}

logstash-logback-encoderのバージョンはspring-boot、というより、これに含まれるlogbackに合わせる必要がある……らしい。詳細は https://github.com/logfellow/logstash-logback-encoder?tab=readme-ov-file#including-it-in-your-project にある。問題があると実行時にClassNotFoundExceptionなどが発生したりするらしい。

src/main/resources/logback-spring.xml を作成する。

<?xml version="1.0" encoding="UTF-8"?>
<configuration>
  <appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
    <encoder class="net.logstash.logback.encoder.LogstashEncoder">
    </encoder>
  </appender>

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

動作確認用の適当なRestControllerを作成する。

@SpringBootApplication
@RestController
@Slf4j
public class App {

  @GetMapping("/test")
  public String ho() {
    log.info("info");
    log.error("", new NullPointerException("error"));
    return "sad";
  }

  public static void main(String[] args) {
    SpringApplication.run(App.class, args);
  }

}

これでログがjsonになるが、開発中は見やすく整形して表示する設定を追加する。

<encoder class="net.logstash.logback.encoder.LogstashEncoder">
    <jsonGeneratorDecorator class="net.logstash.logback.decorate.PrettyPrintingJsonGeneratorDecorator"/>
       ...(略)

実行例。

{
  "@timestamp" : "2024-07-14T19:58:24.6156441+09:00",
  "@version" : "1",
  "message" : "info",
  "logger_name" : "org.example.app.App",
  "thread_name" : "http-nio-8080-exec-6",
  "level" : "INFO",
  "level_value" : 20000
}
{
  "@timestamp" : "2024-07-14T19:58:24.6156441+09:00",
  "@version" : "1",
  "message" : "",
  "logger_name" : "org.example.app.App",
  "thread_name" : "http-nio-8080-exec-6",
  "level" : "ERROR",
  "level_value" : 40000,
  "stack_trace" : "java.lang.NullPointerException: error\r\n\t(略)"
}

LoggingEvent

上記を基本形にドキュメントみながらあれこれ修正を加えていく。ここでは幾つかのみ触れる。

https://github.com/logfellow/logstash-logback-encoder?tab=readme-ov-file#loggingevent-fields

フィールド名の変更

https://github.com/logfellow/logstash-logback-encoder?tab=readme-ov-file#customizing-standard-field-names

  <appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
    <encoder class="net.logstash.logback.encoder.LogstashEncoder">
      <jsonGeneratorDecorator class="net.logstash.logback.decorate.PrettyPrintingJsonGeneratorDecorator"/>
      <fieldNames>
        <timestamp>時間</timestamp>
        <stackTrace>stacktrace</stackTrace>
      </fieldNames>
    </encoder>
  </appender>

試しに日本語にしてみたが特に意味はない。

{
  "時間" : "2024-07-13T19:12:05.2237172+09:00",
  (略)
  "stacktrace" : "java.lang.NullPointerException: (略)"
}

key-valueでフィールド追加

https://github.com/logfellow/logstash-logback-encoder?tab=readme-ov-file#key-value-pair-fields

https://www.slf4j.org/manual.html#fluent とかいうのでkey/valueペアを仕込める。以下のようにすると、

  log.atInfo().addKeyValue("fluent-key", "fluent-value").setMessage("fluent-msg").log();

以下のようなフィールドが追加される。

  "fluent-key" : "fluent-value"

以下のようにしてinclude/excludeを切り替えられる。たいていの項目は<exclude...>, <include...>という形式で切り替えられる。

<excludeKeyValueKeyName>fluent-key</excludeKeyValueKeyName>

この設定項目に関しては以下で全部出なくなる。

<includeKeyValuePairs>false</includeKeyValuePairs>

StructuredArguments/Markers

https://github.com/logfellow/logstash-logback-encoder?tab=readme-ov-file#event-specific-custom-fields

StructuredArguments, Markers でフィールド追加できる。細かい違いについては上記ドキュメントを参照。

StructuredArgumentsの例。

log.info("log message {}", StructuredArguments.value("name", "value"));
  "message" : "log message value",
  "name" : "value",

Markersの例。

log.info(Markers.append("marker-name", "v"), "log");
"message" : "log",
"marker-name" : "v",

LoggingEventCompositeJsonEncoder でより細かくJSONフォーマットを指定

https://github.com/logfellow/logstash-logback-encoder?tab=readme-ov-file#composite-encoderlayout

以下は使用例。%..https://logback.qos.ch/manual/layouts.html#ClassicPatternLayout を参照。

    <encoder class="net.logstash.logback.encoder.LoggingEventCompositeJsonEncoder">
      <providers>
        <timestamp/>
        <pattern>
          <pattern>
            {
              "message": "%msg",
              "exception-message": "%ex{0}"
            }
          </pattern>
        </pattern>
      </providers>
    </encoder>

実行例。

{"@timestamp":"2024-07-14T20:33:21.110684+09:00","message":"info","exception-message":""}
{"@timestamp":"2024-07-14T20:33:21.110684+09:00","message":"","exception-message":"java.lang.NullPointerException: error\r\n"}

参考URL