はじめに
現在、Spring Boot3 x Google Cloudでアプリケーション開発を行っています。
Cloud Logging、便利ですよね。
アプリケーションで標準出力したログを拾ってよしなに表示してくれる。
ただ、そのままだと上記のようにErrorログも標準出力として表示されてしまっていたり、
タイムスタンプがログのPayloadにもそのまま出力されていたり、
いろいろと痒いところに手が届かないので、この辺をいい感じに整備したい。
構造化ロギング
Cloud Loggingは、特定のフィールドを含むJSONログを自動的にマッピングして読み込んでくれます。
https://cloud.google.com/logging/docs/structured-logging?hl=ja#special-payload-fields
例えば、JSONログ中のseverity
フィールドにログの重要度(INFO、ERRORなど)を含めると、その重要度に応じてログを振り分けて表示してくれるようになります。
それ以外にも同一リクエストのログをグルーピングするのに利用されるtrace
フィールドなど、含めておいた方が後々嬉しいフィールドがあります。
spring-cloud-gcp-starter-logging
Google CloudはSpring環境向けに様々なモジュールを提供しています。
https://spring.io/projects/spring-cloud-gcp
Cloud Logging向けのstarterモジュールもあるので、これを使っていきます。
https://mvnrepository.com/artifact/com.google.cloud/spring-cloud-gcp-starter-logging
build.gradle
依存を追加します。
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-web'
implementation 'com.google.cloud:spring-cloud-gcp-starter-logging:4.5.0' // 追加
}
logback-spring.xml
spring-cloud-gcp-starter-logging
にlogback-json-appender.xml
が定義されているので、それを読み込みます。基本的にこれだけでOK
<configuration>
<include resource="org/springframework/boot/logging/logback/defaults.xml"/>
<include resource="com/google/cloud/spring/logging/logback-json-appender.xml"/>
<root level="INFO">
<appender-ref ref="CONSOLE_JSON"/>
</root>
</configuration>
こいつをresources配下に置いてやるだけ
DemoApplication
package com.example.demo;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@SpringBootApplication
public class DemoApplication {
@Autowired
private DemoService demoService;
@GetMapping("/")
public void demo() {
demoService.exec();
}
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
}
package com.example.demo;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;
@Service
public class DemoService {
private static final Logger LOGGER = LoggerFactory.getLogger(DemoService.class);
public void exec() {
LOGGER.info("This is INFO.");
LOGGER.warn("This is WARNING.");
LOGGER.error("This is ERROR.");
}
}
デプロイしてログ確認
起動時のロゴも消したい
application.properties
(もしくはyaml
)に以下を記述すればOK
spring.main.banner-mode=off
traceを含める
Google Cloudでは、同一リクエストをグルーピングできるtrace
を含めることができます。
spring-cloud-gcp-starter-logging
を使えば、こいつも自動的にログに含まれるようになります。便利。
「一致エントリを表示」を選択すると
同一リクエストで出力されたログを絞り込めます。
Asyncを使った場合のtrace
通常、Spring BootのAsync機能を使うと、traceが欠落してしまいます。
これを解決するには、micrometer-tracing-bridge-brave
を利用して、AsyncExecutorをwrapしてあげます。
dependencies {
implementation 'io.micrometer:micrometer-tracing-bridge-brave:1.1.2' // 追加
}
package com.example.demo;
import io.micrometer.context.ContextExecutorService;
import io.micrometer.context.ContextSnapshotFactory;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.AsyncConfigurer;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import java.util.concurrent.Executor;
@EnableAsync
@Configuration(proxyBeanMethods = false)
public class AsyncTraceContextConfig implements AsyncConfigurer {
private final ThreadPoolTaskExecutor taskExecutor;
public AsyncTraceContextConfig(ThreadPoolTaskExecutor taskExecutor) {
this.taskExecutor = taskExecutor;
}
@Override
public Executor getAsyncExecutor() {
return ContextExecutorService.wrap(taskExecutor.getThreadPoolExecutor(),
() -> ContextSnapshotFactory.builder().build().captureAll());
}
}
その他
以前は
- logback-jackson (https://mvnrepository.com/artifact/ch.qos.logback.contrib/logback-jackson)
- logstash-logback-encoder (https://github.com/logfellow/logstash-logback-encoder)
この子たちを使っていたりもしたのですが、Google Cloud x Spring Bootに関してはspring-cloud-gcp-starter-logging
を使ってあげれば、あまり難しい設定も要らずに簡単にログ周りを整理できるので良いなと思いました。