티스토리 뷰
로깅은 애플리케이션 문제 해결의 오랜 역사와 관찰 가능성의 세 가지 주요 요소(메트릭, 트레이스와 함께) 중 하나입니다. 운영 환경에서 아무런 정보 없이 문제를 해결하기는 어려우며, 장애가 발생했을 때 개발자들은 로그 파일이 있는 것을 반가워합니다. 로그는 주로 사람이 읽을 수 있는 형식으로 작성됩니다.
구조화된 로깅은 로그 출력을 잘 정의된, 종종 기계가 읽을 수 있는 형식으로 작성하는 기술입니다. 이러한 형식은 로그 관리 시스템에 입력되어 강력한 검색 및 분석 기능을 가능하게 합니다. 구조화된 로깅에 가장 일반적으로 사용되는 형식 중 하나는 JSON입니다.
Spring Boot 3.4에서는 기본적으로 구조화된 로깅을 지원합니다. Elastic Common Schema (ECS)와 Logstash 형식을 지원하며, 사용자 정의 형식으로 확장하는 것도 가능합니다.
여기서 잠깐 Elastic Common Schema 란?
Elastic Common Schema (ECS)는 Elastic Stack에서 데이터를 일관되게 수집하고 분석할 수 있도록 설계된 표준 스키마입니다. ECS는 로그, 메트릭, 보안 이벤트 등 다양한 소스에서 수집된 데이터를 공통된 형식으로 정리하여, 검색 및 분석을 쉽게 하고 데이터의 상호 운용성을 높입니다.
실습
2024.09월 기준 spring boot 3.4.0-M2를 새로운 프로젝트로 생성한다.
다른 dependency는 필요 없다.
logging.structured.format.console=ecs
이렇게 하면 Spring Boot가 Elastic 공통 스키마(ECS) 형식으로 로그를 표출한다.
그리고 애플리케이션을 실행해주면 아래처럼 ECS 형식의 로그가 출력되는 것을 확인할 수 있다.
...
{"@timestamp":"2024-09-03T02:47:07.300398Z","log.level":"INFO","process.pid":13808,"process.thread.name":"main","service.name":"spring-structured-logging-in-action","log.logger":"com.demo.logging.SpringStructuredLoggingInActionApplication","message":"No active profile set, falling back to 1 default profile: \"default\"","ecs.version":"8.11"}
{"@timestamp":"2024-09-03T02:47:07.533963Z","log.level":"INFO","process.pid":13808,"process.thread.name":"main","service.name":"spring-structured-logging-in-action","log.logger":"com.demo.logging.SpringStructuredLoggingInActionApplication","message":"Started SpringStructuredLoggingInActionApplication in 0.406 seconds (process running for 1.04)","ecs.version":"8.11"}
파일에 구조화된 로깅을 사용하도록 설정할 수도 있다. 예를 들어 콘솔에서 기존의 로그 출력 포맷을 사용하고 파일로는 구조화된 포맷을 남기고 싶으면 아래처럼 사용하면 된다.
#주석
#logging.structured.format.console=ecs
logging.structured.format.file=ecs
logging.file.name=log.json
기존 로깅 출력 포맷을 위해서 위에 건 주석 처리해 주고 파일로 저장하는 설정을 추가했다.
애플리케이션을 시작하면 콘솔에는 기존 로그 포맷으로 나오고 log.json 파일에 ECS 포맷의 로그가 작성되어 있는 것을 확인할 수 있다.
추가기능
구조화된 로깅의 강력한 기능 중 하나는 개발자가 구조화된 방식으로 로그 이벤트에 정보를 추가할 수 있다는 것입니다. 예를 들어 모든 로그 이벤트에 사용자 아이디를 추가한 다음 나중에 해당 아이디를 필터링하여 특정 사용자가 무엇을 했는지 확인할 수 있습니다.
Elastic Common Schema(ECS)와 Logstash는 모두 Mapped Diagnostic Context(MDC)의 데이터를 JSON 형식으로 로그에 포함할 수 있습니다. 이 기능을 통해, 애플리케이션의 상태와 컨텍스트 정보를 더 풍부하게 기록할 수 있습니다. 이제, MDC를 사용하여 실제로 로그 메시지를 생성하고, 그 결과를 확인해 보겠습니다.
@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에서 사용자 ID도 설정합니다. Spring Boot는 JSON에 사용자 ID를 자동으로 포함합니다.
{ ... ,"message":"Hello structured logging!","userId":"1" ... }
fluentAPI를 사용해서 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는 서비스 이름, 서비스 버전, 서비스 환경 및 노드 이름에 대한 기본 지원을 제공합니다. 이러한 필드에 대한 값을 설정하려면 프로퍼티에 아래처럼 설정해 주면 된다.
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
파일에 찍힌 로그 내용.
{
"@timestamp": "2024-09-03T05:21:33.650508Z",
"log.level": "INFO",
"process.pid": 1451,
"process.thread.name": "main",
"service.name": "MyService",
"service.version": "1",
"service.environment": "Production",
"service.node.name": "Primary",
"log.logger": "com.demo.logging.SpringStructuredLoggingInActionApplication",
"message": "No active profile set, falling back to 1 default profile: \"default\"",
"ecs.version": "8.11"
}
JSON 출력을 보면 서비스 이름, 서비스 버전, 서비스 환경 및 서비스 노드 이름에 대한 필드가 있습니다. 이제 로깅 시스템에서 노드 이름, 서비스 버전 등을 기준으로 필터링할 수 있습니다.
사용자 정의 로그 포맷
Spring Boot는 기본적으로 Elastic Common Schema(ECS)와 Logstash 형식을 지원하지만, 필요에 따라 개발자가 자신만의 로그 형식을 쉽게 추가할 수 있도록 유연성을 제공합니다. 이를 통해 개발자는 애플리케이션의 요구 사항에 맞춰 로깅을 커스터마이징하고, 다양한 로깅 전략을 구현할 수 있습니다.
사용자 정의 로그 포맷을 정의하려면 스프링이 만들어 놓은 StructuredLogFormatter 인터페이스를 구현해주면 된다. 그러고 나서 해당 구현체를 프로퍼티에서 참조해주기만 하면 된다. 바로 코드로 알아보자.
첫 번째로 StructuredLogFormatter 인터페이스를 구현하는 구현체를 생성한다.
class MyStructuredLoggingFormatter implements StructuredLogFormatter<ILoggingEvent> {
@Override
public String format(ILoggingEvent event) {
return "time=" + event.getTimeStamp() + " level=" + event.getLevel() + " message=" + event.getMessage() + "\n";
}
}
구조화된 로깅 지원은 JSON에만 국한되지 않으며 원하는 모든 문자열을 반환할 수 있습니다.
다음은 Spring Boot가 사용자 정의 구현을 참조하도록 해야 합니다. 이를 위해 application.properties에 다음을 추가합니다.
logging.structured.format.console=com.demo.logging.MyStructuredLoggingFormatter
애플리케이션을 실행해 주면 아래와 같은 결과를 확인할 수 있습니다.
...
time=1725342394761 level=INFO message=No active profile set, falling back to 1 default profile: "default"
time=1725342394942 level=INFO message=Started SpringStructuredLoggingInActionApplication in 0.339 seconds (process running for 0.614)
JSON을 작성하려면 Spring Boot 3.4에 새로 추가된 편리한 org.springframework.boot.json.JsonWriter를 사용할 수 있습니다.
public class MyStructuredJsonLoggingFormatter 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);
}
}
이후 애플리케이션을 실행해보면 결과는 아래처럼 나오게 된다.
...
{"time":"2024-09-03T06:00:00.020217Z","level":"INFO","thread":"main","message":"No active profile set, falling back to 1 default profile: \"default\"","application":{"name":"StructuredLoggingDemo","version":"1.0.0-SNAPSHOT"},"node":{"hostname":"node-1","ip":"10.0.0.7"}}
{"time":"2024-09-03T06:00:00.176696Z","level":"INFO","thread":"main","message":"Started SpringStructuredLoggingInActionApplication in 0.302 seconds (process running for 0.533)","application":{"name":"StructuredLoggingDemo","version":"1.0.0-SNAPSHOT"},"node":{"hostname":"node-1","ip":"10.0.0.7"}}
파일에도 저렇게 쓰고 싶고 console에서도 내가 설정한 포맷으로 출력하고 싶다면 프로퍼티 파일에 console에도 클래스 파일을 참조시켜 주면 된다.
logging.structured.format.console=com.demo.logging.MyStructuredJsonLoggingFormatter
logging.structured.format.file=com.demo.logging.MyStructuredJsonLoggingFormatte
결론
Spring Boot 3.4.x부터 추가된 구조화된 로깅 기능에 대해 살펴보았습니다. 블로그의 내용을 읽고 그대로 실행해 본 결과, 이 기능이 어떻게 활용될 수 있을지 생각해 보았습니다.
컨테이너화된 애플리케이션에서 기존에는 Fluent Bit이나 Fluentd를 사용해 ECS 로그 포맷을 Kibana나 BI 도구로 전달했지만, 이제는 Spring 애플리케이션에서 손쉽게 파일로 로그를 내릴 수 있게 되어 이 과정이 더욱 간단해질 것으로 기대됩니다. 이를 통해 로그 데이터를 더욱 쉽게 수집하고 분석할 수 있을 것입니다.
물론 내 생각이 아예 틀린 것일 수도 있다..
출처
https://spring.io/blog/2024/08/23/structured-logging-in-spring-boot-3-4
- Total
- Today
- Yesterday
- springboot
- mybatis
- input
- maven
- window
- localtime
- intellij
- Linux
- Spring
- oracle
- Spring Security
- jQuery
- mybatis config
- Bash tab
- svn
- 오라클
- docker
- Java
- Github Status
- LocalDateTime
- k8s
- LocalDate
- 북리뷰
- config-location
- 베리 심플
- rocky
- Kotlin
- JavaScript
- elasticsearch
- Mac
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | |||||
3 | 4 | 5 | 6 | 7 | 8 | 9 |
10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 | 18 | 19 | 20 | 21 | 22 | 23 |
24 | 25 | 26 | 27 | 28 | 29 | 30 |