스프링 부트 로깅 라이브러리는 기본으로 다음 로깅 라이브러리를 사용한다
로그 라이브러리는 Logback, Log4J 등등 수 많은 라이브러리가 있는데, 그것을 통합해서 인터페이스로 제공하는 것이 SLF4J 라이브러리다. SLF4J는 인터페이스이고, 그 구현체로 Logback 같은 로그 라이브러리를 선택하면 된다. 실무에서는 스프링 부트가 기본으로 제공하는 Logback을 대부분 사용한다.
Logback은 메시지의 중요도에 따라 로그 레벨을 설정한다.
Logback은 로그를 다양한 출력 대상으로 보낼 수 있는 Appender를 제공한다.
로그 메시지의 형식을 정의하는 기능으로, 일반적으로 PatternLayout이 사용된다.
Logback은 설정 파일로 애플리케이션의 로깅 동작을 정의하며, 설정 파일은 logback.xml 또는 logback-spring.xml 형식으로 작성한다.
<?xml version="1.0" encoding="UTF-8" ?>
<configuration>
<!-- %d{}: 로그 발생 시각, %thread: 로그가 실행된 스레드 이름, %level: 로그 레벨, %logger{}: 로그를 출력한 클래스 이름 -->
<property name="CONSOLE_PATTERN" value="%d{yyyy-MM-dd HH:mm:ss.SSS} [%X{requestId}] %magenta([%thread]) %highlight([%-3level]) %logger{5} - %msg %n" />
<!-- Console appender(로그를 콘솔에 출력) 설정 -->
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<Pattern>${CONSOLE_PATTERN}</Pattern>
</encoder>
</appender>
<!-- name: 로거 이름, level: 로깅 레벨, additivity: 부모 로거에 로그를 전달할 지 여부 -->
<logger name="jdbc" level="OFF" additivity="false">
<!-- <appender-ref>: 이 로거에서 사용할 appender를 참조. -->
<appender-ref ref="STDOUT"/>
</logger>
<logger name="jdbc.sqlonly" level="OFF" additivity="false" >
<appender-ref ref="STDOUT"/>
</logger>
<logger name="jdbc.sqltiming" level="OFF" additivity="false" >
<appender-ref ref="STDOUT"/>
</logger>
<logger name="org.hibernate.SQL" level="DEBUG" additivity="false">
<appender-ref ref="STDOUT"/>
</logger>
<logger name="com.study.controller" level="DEBUG" additivity="false" >
<appender-ref ref="STDOUT"/>
</logger>
<logger name="com.study.service" level="DEBUG" additivity="false" >
<appender-ref ref="STDOUT"/>
</logger>
<logger name="com.study.domain" level="DEBUG" additivity="false" >
<appender-ref ref="STDOUT"/>
</logger>
<!-- 모든 로거의 최상위 부모 -->
<root level="INFO">
<appender-ref ref="STDOUT"/>
</root>
</configuration>
MDC는 ThreadLocal 기반으로 작동하며, 각 스레드별로 고유한 컨텍스트 정보를 저장한다. MDC에 저장된 값은 로그 패턴에 자동으로 포함된다. (여기서 ThreadLocal은 해당 스레드만 접근할 수 있는 특별한 저장소)
Filter를 사용하여 HTTP 요청마다 MDC에 데이터를 추가해본다.
@Component
public class MDCFilter implements Filter {
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
//임의의 id 생성
String requestId = UUID.randomUUID().toString();
MDC.put("requestId", requestId); //MDC에 저장
try {
chain.doFilter(request, response);
} finally {
MDC.clear(); //요청 처리 후 MDC 정리
}
}
}
logback-spring.xml에 MDC에서 requestId 키에 해당하는 값을 로그에 포함시킨다.
<?xml version="1.0" encoding="UTF-8" ?>
<configuration>
<!-- %d{}: 로그 발생 시각, %thread: 로그가 실행된 스레드 이름, %level: 로그 레벨, %logger{}: 로그를 출력한 클래스 이름 -->
<property name="CONSOLE_PATTERN" value="%d{yyyy-MM-dd HH:mm:ss.SSS} [%X{requestId}] %magenta([%thread]) %highlight([%-3level]) %logger{5} - %msg %n" />
...
</configuration>
로그 테스트를 진행하기 위해 테스트 컨트롤러를 생성한다.
@RestController
public class TestController {
private static final Logger log = LoggerFactory.getLogger(TestController.class);
@GetMapping("/mdc-test")
public String mdcTest() {
log.info("MDC test");
return "MDC test";
}
}
이제 /mdc-test 경로로 GET 요청을 보내보면 다음과 같이 requestId 값이 포함된 로그가 출력된다.
2024-12-08 03:40:56.794 [fee410db-ed01-4de4-b290-3ce1e9dc712f] [http-nio-8080-exec-3] [INFO] c.s.c.TestController - MDC test