스프링 부트 로그를 Json 형식으로 저장하기
스프링부트 로그를 Grafana로 확인해보자
현재 토이 프로젝트의 경우 아래와 같이 Logback을 설정하여 로그를 저장하고 있었습니다. 이렇게 설정할 경우 아래와 같이 로그가 찍히는 것을 볼 수 있습니다.
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<!-- 로그 패턴 설정 -->
<property name="LOG_PATTERN" value="[%d{yyyy-MM-dd HH:mm:ss.SSS}] [%-5level] [TRACE_ID: %X{traceId}] [%thread] %logger{36} %msg%n"/>
<!-- ConsoleAppender 설정: 콘솔에 로그 출력 -->
<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>${LOG_PATTERN}</pattern>
</encoder>
</appender>
<!-- RollingFileAppender 설정: 파일에 로그 출력 -->
<appender name="DAILY_LOG" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>./application.log</file>
<append>true</append>
<!-- 로그 파일 롤링 정책: 하루 단위로 롤링 -->
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>./application-%d{yyyy-MM-dd}.log</fileNamePattern>
<maxHistory>7</maxHistory>
</rollingPolicy>
<encoder>
<pattern>${LOG_PATTERN}</pattern>
</encoder>
</appender>
<!-- Root Logger 설정 (애플리케이션 전체 적용) -->
<root level="INFO">
<appender-ref ref="CONSOLE"/>
<appender-ref ref="DAILY_LOG"/>
</root>
</configuration>
[2024-12-27 21:49:31.581] [WARN ] [TRACE_ID: c252502a-d698-4321-bd45-bee57642baef] [http-nio-8080-exec-3] o.s.w.s.m.s.DefaultHandlerExceptionResolver Resolved [org.springframework.web.bind.MethodArgumentNotValidException: Validation failed for argument [0] in public org.springframework.http.ResponseEntity<jakarta.servlet.http.Cookie> com.sparta.admin.member.controller.AdminMemberController.login(com.sparta.admin.member.dto.request.LoginRequest,jakarta.servlet.http.HttpServletResponse): [Field error in object 'loginRequest' on field 'password': rejected value [1]; codes [Size.loginRequest.password,Size.password,Size.java.lang.String,Size]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [loginRequest.password,password]; arguments []; default message [password],20,8]; default message [비밀번호는 8~20자여야 합니다.]] ]
Grafana에서도 동일하게 콘솔에서 보여지는 바와 같이 로그가 확인되는 것을 확인했습니다. 문제는 이 경우 데이터를 필터링 및 분석하거나 로그를 볼 때 가독성이 떨어지는 문제가 발생했습니다.
로그 모니터링과 관련하여 확인해보니 주로 로그를 저장할 때는 데이터를 유연하게 필터링하고 분석하기 위해서 Json 형식으로 저장한다는 사실을 알게 됐습니다.
그에 따라 Grafana에서 LogQL을 사용하여 Json 형식으로 데이터를 조회해오도록 하였습니다.
{job="admin_logs"}
| Json
하지만 다음과 같이 Json 파싱 에러가 발생했습니다. 에러 메시지를 통해 로그가 Json 타입으로 저장되지 않았기 때문에 이를 Grafana가 Json 타입으로 가져올 때 파싱 문제가 발생했다는 사실을 알게됐습니다.
JsonLayout과 JacksonJsonFormatter를 사용하여 로그를 Json 형식으로 저장하기
주로 로그를 Json 형식으로 저장할 때 JsonLayout과 JacksonJsonFormatter를 많이 사용하기에 해당 라이브러리를 사용해 다음과 같이 logback-spring 설정을 변경하였습니다.
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<!-- 로그 패턴 설정 -->
<property name="LOG_PATTERN" value="[%d{yyyy-MM-dd HH:mm:ss.SSS}] [%-5level] [TRACE_ID: %X{traceId}] [%thread] %logger{36} %msg%n"/>
<!-- ConsoleAppender 설정: 콘솔에 로그 출력 -->
<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>${LOG_PATTERN}</pattern>
</encoder>
</appender>
<!-- RollingFileAppender 설정: 파일에 로그 출력 -->
<appender name="DAILY_LOG" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>./application.log</file>
<append>true</append>
<!-- 로그 파일 롤링 정책: 하루 단위로 롤링 -->
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>./application-%d{yyyy-MM-dd}.log</fileNamePattern>
<maxHistory>7</maxHistory>
</rollingPolicy>
<encoder class="ch.qos.logback.core.encoder.LayoutWrappingEncoder">
<layout class="ch.qos.logback.contrib.json.classic.JsonLayout">
<timestampFormat>yyyy-MM-dd' 'HH:mm:ss.SSS</timestampFormat>
<appendLineSeparator>true</appendLineSeparator>
<jsonFormatter class="ch.qos.logback.contrib.jackson.JacksonJsonFormatter">
<prettyPrint>false</prettyPrint>
</jsonFormatter>
</layout>
</encoder>
</appender>
<!-- Root Logger 설정 (애플리케이션 전체 적용) -->
<root level="INFO">
<appender-ref ref="CONSOLE"/>
<appender-ref ref="DAILY_LOG"/>
</root>
</configuration>
확인해보니 정상적으로 로그가 Json 형식으로 찍히는 것을 확인했습니다. 문제는 로그의 패턴을 커스터마이징 할 수 없다는 것이었습니다. 이번 토이 프로젝트에서는 traceId를 사용하여 사용자 로그를 추적할 수 있도록 설정하였는데 위와 같이 JsonLayout 라이브러리를 사용할 경우 traceId를 출력할 수 없었습니다.
LoggingEventCompositeJsonEncoder를 사용하여 로그를 Json 형식으로 저장하기
JsonLayout은 로그 패턴을 커스터마이징 할 수 없다는 사실을 알았습니다. 그렇다면 커스터마이징 가능한 라이브러리가 있는지 확인해본 결과 LoggingEventCompositeJsonEncoder의 경우 동적인 로그 패턴을 적용할 수 있음을 확인했습니다.
build.gradle에 아래의 종속성을 추가한 뒤 logback-spring.xml을 아래와 같이 수정하였습니다.
// Log Json encoder
implementation 'net.logstash.logback:logstash-logback-encoder:7.4'
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<!-- ConsoleAppender 설정: 콘솔에 로그 출력 -->
<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
<encoder class="net.logstash.logback.encoder.LoggingEventCompositeJsonEncoder">
<providers>
<pattern>
<pattern>
{
"timestamp":"%d{yyyy-MM-dd HH:mm:ss.SSS}",
"level": "%-5level",
"traceId": "%X{traceId}",
"thread": "%thread",
"logger": "%logger{36}",
"message": "%message"
}
</pattern>
</pattern>
</providers>
</encoder>
</appender>
<!-- RollingFileAppender 설정: 파일에 로그 출력 -->
<appender name="DAILY_LOG" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>./application.log</file>
<append>true</append>
<!-- 로그 파일 롤링 정책: 하루 단위로 롤링 -->
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>./application-%d{yyyy-MM-dd}.log</fileNamePattern>
<maxHistory>7</maxHistory>
</rollingPolicy>
<encoder class="net.logstash.logback.encoder.LoggingEventCompositeJsonEncoder">
<providers>
<pattern>
<pattern>
{
"timestamp":"%d{yyyy-MM-dd HH:mm:ss.SSS}",
"level": "%-5level",
"traceId": "%X{traceId}",
"thread": "%thread",
"logger": "%logger{36}",
"message": "%message"
}
</pattern>
</pattern>
</providers>
</encoder>
</appender>
<!-- Root Logger 설정 (애플리케이션 전체 적용) -->
<root level="INFO">
<appender-ref ref="CONSOLE"/>
<appender-ref ref="DAILY_LOG"/>
</root>
</configuration>
설정을 적용한 뒤 콘솔에 찍히는 로그를 확인해보니 필자가 설정한 로그 패턴대로 출력되는 것을 알 수 있었습니다.
해당 로그를 Grafana에서도 확인해보겠습니다. Grafana 모니터링을 보면 Key-Value 형태로 로그를 볼 수 있습니다. 이를 통해 로그 필터링이 필요할 경우 해당 Key-Value를 이용하여 조회할 수 있습니다.
참조
https://stackoverflow.com/questions/63841905/json-layout-with-pattern-in-logback-java
JSON Layout with Pattern in Logback Java
I am using logback for logging in my spring boot application and using the pattern as per: "%d [%thread] %-5p [%c] [%F:%L] [trace=%X{X-B3-TraceId:-},span=%X{X-B3-SpanId:-}] - %msg%n" No...
stackoverflow.com
https://www.baeldung.com/java-log-json-output