최근 안내된 Spring Boot 3.4 버전에서 새로 추가되는 구조화된 로깅 지원에 대한 소개글 입니다. 기본적으로 Spring Blog의 소개 글(https://spring.io/blog/2024/08/23/structured-logging-in-spring-boot-3-4)의 흐름을 따르고 있고 중간 중간 저의 의견 및 실행 결과 등을 추가했습니다.
Spring Boot 3.4에서는 구조화된 로깅(Structured logging)을 기본적으로 지원합니다. Elastic Common Schema (ECS)와 Logstash 형식을 지원하며, 사용자 정의 형식으로 확장하는 것도 가능합니다. 먼저 구조화된 로깅에 대해 알아봅시다.
구조화된 로깅이란?
로그 출력이 잘 정의된 형식으로 작성되는 기법입니다. 이 형식은 기계가 읽기 쉽도록 작성되며, ELK 스택 같은 로그 관리 시스템에서 효율적으로 처리될 수 있습니다. 대표적으로 JSON 형식이 많이 사용됩니다.
기본적인 설정 방법
Spring 3.4 버전 에서는 다음과 같은 설정이 가능합니다.
logging.structured.format.console=ecs
프로그램을 시작하면 다음과 같이 JSON 으로 로그가 포맷팅 됩니다.
{"@timestamp":"2024-07-30T08:41:10.561295200Z","log.level":"INFO","process.pid":67455,"process.thread.name":"main","service.name":"structured-logging-demo","log.logger":"com.example.structured_logging_demo.StructuredLoggingDemoApplication","message":"Started StructuredLoggingDemoApplication in 0.329 seconds (process running for 0.486)","ecs.version":"8.11"}
설정 전, 후 로그 출력 비교
- 설정 전

- 설정 후

파일로 로깅
콘솔 화면은 사람이 읽을 수 있는 기존 구조로 설정하고, 기계가 읽을 수 있는 구조의 로깅은 다른 특정 파일에 작성하고 싶을 수 있습니다. 다음과 같은 설정으로 파일에 구조화된 로깅을 설정할 수 있습니다.
logging.structured.format.file=ecs
logging.file.name=log.json
추가 필드를 추가
구조화된 로깅의 강력한 기능 중 하나는 개발자가 구조화된 방식으로 로그 이벤트에 정보를 추가할 수 있다는 것입니다. 예를 들어 모든 로그 이벤트에 사용자 아이디를 추가한 다음 나중에 해당 아이디를 필터링하여 특정 사용자가 무엇을 했는지 확인할 수 있습니다.
Elastic Common Schema와 Logstash 모두 JSON에 매핑된 진단 컨텍스트(Mapped Diagnostic Context)의 콘텐츠를 포함합니다. 실제로 작동하는지 확인하기 위해 자체 로그 메시지를 생성해 보겠습니다:
@Component
class MyLogger implements CommandLineRunner {
private static final Logger LOGGER = LoggerFactory.getLogger(MyLogger.class);
@Override public void run(String... args) {
MDC.put("userId", "1");
LOGGER.info("Hello structured logging!");
MDC.remove("userId");
}
}
로그 메시지를 로깅하기 전에 이 코드는 MDC에 “userId”도 설정합니다. Spring Boot는 JSON에 “userId”를 자동으로 포함합니다:
{ ... ,"message":"Hello structured logging!","userId":"1" ... }
또한 우아한 로깅 API를 사용하여 MDC에 의존하지 않고도 추가 필드를 추가할 수 있습니다:
@Component
class MyLogger implements CommandLineRunner {
private static final Logger LOGGER = LoggerFactory.getLogger(MyLogger.class);
@Override public void run(String... args) {
LOGGER.atInfo().setMessage("Hello structured logging!").addKeyValue("userId", "1").log();
}
}
Elastic Common Schema는 기본적으로 많은 필드 이름을 정의하고 있습니다. Spring Boot 에서는 서비스 이름, 서비스 버전, 서비스 환경 및 노드 이름에 대한 기본 지원 기능을 갖추고 있습니다. 이러한 필드에 대한 값을 설정하려면 application.properties에서 다음을 사용할 수 있습니다:
logging.structured.ecs.service.name=MyService
logging.structured.ecs.service.version=1
logging.structured.ecs.service.environment=Production
logging.structured.ecs.service.node-name=Primary
JSON 출력에서 이제 service.name, service.version, service.environment, service.node.name와 같은 필드가 나타납니다. 이를 통해 로깅 시스템에서 노드 이름, 서비스 버전 등을 기준으로 필터링할 수 있게 됩니다. 이 네가지 필드 이외의 다른 Elastic Common Schema 필드에 대한 값은 properties 에서 설정할 수 없습니다. 다른 필드에 대한 값을 설정하려면 후에 서술할 맞춤형 로그 형식을 통해 추가해야 할 것 같습니다.
로깅 예시
{"@timestamp":"2024-08-29T11:30:41.325556Z","service.name":"MyService","service.version":"1","service.environment":"Production","service.node.name":"Primary","log.logger":"com.example.structuredlogging.StructuredLoggingApplication",...,"ecs.version":"8.11"}
맞춤형 로그 형식
각자의 프로젝트의 맞춤화된 로그 포맷을 위해 다음과 같은 단계가 필요합니다.
StructuredLogFormatter인터페이스의 커스텀 구현을 작성하세요
class MyStructuredLoggingFormatter implements StructuredLogFormatter<ILoggingEvent> {
@Override public String format(ILoggingEvent event) {
return "time=" + event.getTimeStamp() + " level=" + event.getLevel() + " message=" + event.getMessage() + "\n";
}
}
application.properties에서 해당 커스텀 구현을 참조하세요
logging.structured.format.console=com.example.structuredlogging.MyStructuredLoggingFormatter
Spring Boot 3.4에 새로 추가되는 JsonWriter 를 사용해 JSON 으로 출력할 수도 있습니다.
class MyStructuredLoggingFormatter implements StructuredLogFormatter<ILoggingEvent> {
private final JsonWriter<ILoggingEvent> writer = JsonWriter.<ILoggingEvent>of((members) -> {
members.add("time", (event) -> event.getInstant());
members.add("level", (event) -> event.getLevel());
members.add("thread", (event) -> event.getThreadName());
members.add("message", (event) -> event.getFormattedMessage());
members.add("application").usingMembers((application) -> {
application.add("name", "StructuredLoggingDemo");
application.add("version", "1.0.0-SNAPSHOT");
});
members.add("node").usingMembers((node) -> {
node.add("hostname", "node-1");
node.add("ip", "10.0.0.7");
});
}).withNewLineAtEnd();
@Override
public String format(ILoggingEvent event) {
return this.writer.writeToString(event);
}
}
마무리
Spring Boot 3.4 에서 강화된 구조화된 로깅 지원 기능은 매우 편리하게 로그에 다양한 정보를 추가할 수 있도록 도와줍니다. 로그를 남길때 특정 로그가 작성되는 시점의 맥락을 로그에 담았으면 좋겠다는 생각을 많이 하는데 이럴 때 유용하게 사용할 수 있을 것 같습니다. 특히 코드로 맞춤형 로그 구조를 직접 만들 수 있는 기능은 개인정보 마스킹등의 로그 요구사항도 비교적 손쉽게 구현이 가능해 질 것 같아서 기대가 됩니다.
추가 자료 링크
- https://spring.io/blog/2024/08/23/structured-logging-in-spring-boot-3-4
- 이 글의 모티브가 된 원문 소개글 입니다.
- https://github.com/spring-projects/spring-boot/issues/5479
- 구조화된 로깅이 추가된 배경과 관련한 Github 이슈 입니다.
- https://www.baeldung.com/java-structured-logging
- 3.4 이전 버전에서의 구조화된 로깅을 지원하는 방법을 다루는 튜토리얼 입니다.

댓글을 달려면 로그인해야 합니다.