spring 프로젝트 log4j2 로그 파일 분리하기
해당 포스팅은 log4j2를 사용하는 스프링 프로젝트에서 특정 용도의 로그에 대한 로그 파일을 별도로 분리하는 방법을 정리한 내용입니다.
혹시나 log4j2가 아니라 logback을 쓰시더라도 java 코드단의 적용 방식은 같기 때문에 로그 설정 xml 파일의 작성 방법만 logback에 맞춰서 적용하시면 됩니다.
1. 로그 파일을 분리하려는 이유
먼저 로그 파일의 분리를 고려하게 된 가장 큰 이유는 결국 잘 관리된 로그를 통해 서비스 유지보수 및 운영을 더 효율적으로 하기 위함인데요.
기본적으로 서비스의 전체 로그는 RollingFile의 'TimeBasedTriggeringPolicy' 및 'SizeBasedTriggeringPolicy'를 통해 날짜별, 사이즈별로 나눠지고 있었지만, 이렇게 쌓인 로그 자체를 활용하는 것이 아니라 서비스에 문제가 생겼을 때 해당 시점의 로그를 확인하는 용도로만 거의 사용되고 있었습니다.
때문에 서비스에서 발생하는 exception에 대한 로깅만을 따로 파일로 남기고, 해당 파일을 통해 서비스에 문제가 발생했던 부분은 없는지 확인하는 용도로 사용하고 싶었습니다.
2. 로그 파일 분리를 위해 고려한 방법
로그 파일을 분리하기 위해 적용할 수 있는 방법으로 '1. 특정 목적에 사용할 로거를 따로 정의하는 방법'과 '2. 커스텀 로그 레벨을 사용하는 방법' 두 가지를 생각해 보았는데요.
용도를 생각하면 커스텀 로그 레벨을 추가로 정의하고 관리하는 것보다 단순하게 필요한 로거만을 따로 정의하여 해당 로그를 파일로 저장하는 방법이 적합하다고 생각했으며, 커스텀 로그 레벨을 사용하는 방법 또한 적용 방식이 궁금하여 함께 구현해 보았습니다.
/*
GlobalExceptionHandler에서 Exception을 잡아 error 레벨 로그를 찍고 error 로그만 따로 파일로 뱉도록 하면 되지 않을까? 라고도 생각할 수 있지만, 서비스에 따라 error 레벨의 로그를 단순히 Exception 뿐만 아니라 다른 용도로도 사용할 수 있다고 생각하기 때문에 해당 방법은 사용하지 않았습니다.
*/
2-1. 특정 목적에 사용할 로거를 따로 정의해서 파일을 분리하는 방법
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@Controller
public class CustomLoggerTestController {
private static final Logger logger = LoggerFactory.getLogger(CustomLoggerTestController.class);
private static final Logger customLogger = LoggerFactory.getLogger("CustomLogger");
@GetMapping("/customLogger")
public void test() {
logger.info("기본 logger를 통한 로그 출력");
customLogger.info("CustomLogger를 통한 로그 출력");
}
}
특정 목적에 사용할 로거를 따로 정의하는 방법은 간단합니다.
위 예시 코드에서 처럼 로그 출력이 필요한 곳에서 'LoggerFactory.getLogger("사용할_로거_이름")'를 통해 로거를 생성합니다.
<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="INFO">
<Appenders>
<!-- Console Appender -->
<Console name="ConsoleAppender" target="SYSTEM_OUT">
<PatternLayout pattern="[%d{yyyy-MM-dd HH:mm:ss}:%-3relative][%thread] %-5level %logger{35} - %msg%n"/>
</Console>
<!-- File Appender -->
<RollingFile name="FileAppender" fileName="/project/log/log4j2.log" filePattern="/project/log/log4j2_%d{yyyy-MM-dd}_%i.log.gz">
<PatternLayout pattern="[%d{yyyy-MM-dd HH:mm:ss}:%-3relative][%thread] %-5level %logger{35} - %msg%n"/>
<Policies>
<SizeBasedTriggeringPolicy size="10MB"/>
<TimeBasedTriggeringPolicy interval="1"/>
</Policies>
<DefaultRolloverStrategy max="7" fileIndex="min"/>
</RollingFile>
<!-- CustomLogger File Appender -->
<RollingFile name="CustomLoggerFileAppender" fileName="/project/log/customLog.log" filePattern="/project/log/costomLog_%d{yyyy-MM-dd}_%i.log.gz">
<PatternLayout pattern="[%d{yyyy-MM-dd HH:mm:ss}:%-3relative][%thread] %-5level %logger{35} - %msg%n"/>
<Policies>
<SizeBasedTriggeringPolicy size="10MB"/>
<TimeBasedTriggeringPolicy interval="1"/>
</Policies>
<DefaultRolloverStrategy max="7" fileIndex="min"/>
</RollingFile>
</Appenders>
<Loggers>
<Root level="INFO" additivity="false">
<AppenderRef ref="ConsoleAppender"/>
<AppenderRef ref="FileAppender"/>
</Root>
<!-- CustomLogger 추가 -->
<Logger name="CustomLogger" additivity="true">
<AppenderRef ref="CustomLoggerFileAppender"/>
</Logger>
</Loggers>
</Configuration>
그리고 log4j2.xml 파일에서 위에서 지정한 로거 이름(예시로 치면 CustomLogger)을 name으로 가진 Logger를 추가하고, 해당 로거에 대한 내용을 파일로 저장하는 Appender를 설정해 주면 됩니다.
[2024-08-25 13:52:48:13413][http-nio-8080-exec-2] INFO com.example.logmvc.controller.MainController - 기본 logger를 통한 로그 출력
[2024-08-25 13:52:48:13414][http-nio-8080-exec-2] INFO CustomLogger - CustomLogger를 통한 로그 출력
(log4j2.log 파일에 저장된 내용)
[2024-08-25 13:52:48:13414][http-nio-8080-exec-2] INFO CustomLogger - CustomLogger를 통한 로그 출력
(customLog.log 파일에 저장된 내용)
그러면 다음과 같이 'CustomLogger'를 통해 출력한 내용이 따로 파일로 분리되어 저장되는 것을 확인할 수 있는데요.
여기서 xml 파일 내용 중 Logger의 'additivity'는 로깅이 상위 로거로 전파되는 방식을 제어하는 옵션으로 값이 'true'일 경우 현재 로거에서 처리된 로그 메시지가 상위 로거로 전파되며, 'false'인 경우 현재 로거에서 처리된 메시지가 상위 로거로 전파되지 않습니다.
위 xml 파일에서는 CustomLogger에 대한 additivity 값을 true로 주었기 때문에 CustomLogger에서 처리된 로그 메시지가 모든 로거의 최상위 로거인 Root Logger에서 한번 더 처리되어 log4j2.log 파일에도 해당 내용이 기록된 것입니다.
2-2. 커스텀 로그 레벨을 사용해서 파일을 따로 분리하는 방법
import org.apache.logging.log4j.Level;
public class CustomLogLevel {
public static final Level CUSTOM_LEVEL = Level.forName("CUSTOM_LEVEL", 150);
}
먼저 커스텀 로그 레벨을 정의할 클래스를 생성하고, 클래스 내부적으로 사용할 커스텀 로그 레벨을 정의합니다.
이때 사용되는 Level 클래스는 'org.apache.logging.log4j' 패키지의 클래스입니다.
추가로 커스텀 로그 레벨을 정의할 때 로그 레벨명 다음으로 들어가는 int 값은 로그 레벨에 대해 정수로 정의된 우선순위 값을 나타내는 intLevel 값입니다.
해당 값은 로그 레벨 간의 순서를 결정하며, 로그 메시지의 중요도와 로그 필터링 시에 어떤 로그를 출력할지 결정하는 데 사용됩니다.
(OFF: 0, FATAL: 100, ERROR: 200, WARN: 300, INFO: 400, DEBUG: 500, TRACE: 600, ALL: Integer.MAX_VALUE)
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import config.CustomLogLevel;
@Controller
public class CustomLogLevelTestController {
private final static Logger logger = LogManager.getLogger(CustomLogLevelTestController.class);
@GetMapping("/customLogLevel")
public void test() {
logger.info("info 레벨의 로그 출력");
logger.log(CustomLogLevel.CUSTOM_LEVEL, "정의한 CUSTOM_LEVEL 레벨의 로그 출력");
}
}
이어서 로그 출력이 필요한 곳에서 'LogManager.getLogger()' 메서드를 통해 사용할 로거를 생성합니다.
***
앞서 CustomLogger 예시에서는 'org.slf4j' 패키지의 LoggerFactory 클래스와 Logger 클래스를 사용했지만, 여기에서 사용되는 LogManager 클래스와 Logger 클래스는 'org.apache.logging.log4j' 패키지의 클래스인데요.
void log(Level level, String message);
그 이유는 로그 레벨을 포함하여 로깅을 호출하는 log() 메서드가 'org.apache.logging.log4j.Logger' 클래스에 있기 때문입니다.
<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="INFO">
<CustomLevels>
<CustomLevel name="CUSTOM_LEVEL" intLevel="150" />
</CustomLevels>
<Appenders>
<!-- Console Appender -->
<Console name="ConsoleAppender" target="SYSTEM_OUT">
<PatternLayout pattern="[%d{yyyy-MM-dd HH:mm:ss}:%-3relative][%thread] %-5level %logger{35} - %msg%n"/>
</Console>
<!-- File Appender -->
<RollingFile name="FileAppender" fileName="/project/log/log4j2.log" filePattern="/project/log/log4j2_%d{yyyy-MM-dd}_%i.log.gz">
<PatternLayout pattern="[%d{yyyy-MM-dd HH:mm:ss}:%-3relative][%thread] %-5level %logger{35} - %msg%n"/>
<Policies>
<SizeBasedTriggeringPolicy size="10MB"/>
<TimeBasedTriggeringPolicy interval="1"/>
</Policies>
<DefaultRolloverStrategy max="7" fileIndex="min"/>
</RollingFile>
<!-- CustomLevel File Appender -->
<RollingFile name="CustoLogLevelFileAppender" fileName="/project/log/customLevel.log" filePattern="/project/log/costomLevel_%d{yyyy-MM-dd}_%i.log.gz">
<PatternLayout pattern="[%d{yyyy-MM-dd HH:mm:ss}:%-3relative][%thread] %-5level %logger{35} - %msg%n"/>
<Policies>
<SizeBasedTriggeringPolicy size="10MB"/>
<TimeBasedTriggeringPolicy interval="1"/>
</Policies>
<DefaultRolloverStrategy max="7" fileIndex="min"/>
</RollingFile>
</Appenders>
<Loggers>
<Root level="INFO" additivity="false">
<AppenderRef ref="ConsoleAppender"/>
<AppenderRef ref="FileAppender"/>
<AppenderRef ref="CustoLogLevelFileAppender" level="CUSTOM_LEVEL"/>
</Root>
</Loggers>
</Configuration>
마지막으로 xml 파일 설정입니다.
앞서 CustomLogLevel 클래스에서 정의한 커스텀 로그를 <CustomLevels> 부분과 같이 명시해 줍니다.
명시해주지 않는 경우 Unknown level constant 경고 메시지가 발생하게 됩니다.
그리고 Root 로거의 AppenderRef 쪽과 같이 사용할 Logger 쪽에서 level을 지정한 커스텀 로그 레벨로 설정한 Appender를 추가해 주면 됩니다.
[2024-08-25 16:49:42:288828][http-nio-8080-exec-3] INFO com.example.logmvc.controller.CustomLogLevelTestController - info 레벨의 로그 출력
[2024-08-25 16:49:42:288830][http-nio-8080-exec-3] CUSTOM_LEVEL com.example.logmvc.controller.CustomLogLevelTestController - 정의한 CUSTOM_LEVEL 레벨의 로그 출력
(log4j2.log 파일에 저장된 내용)
[2024-08-25 16:49:42:288830][http-nio-8080-exec-3] CUSTOM_LEVEL com.example.logmvc.controller.CustomLogLevelTestController - 정의한 CUSTOM_LEVEL 레벨의 로그 출력
(customLevel.log 파일에 저장된 내용)
새로 정의한 로깅 레벨인 'CUSTOM_LEVEL'에 대한 로그 파일이 따로 저장되는 것을 확인할 수 있습니다.
< 참고 자료 >
https://logging.apache.org/log4j/2.x/manual/customloglevels.html
< 로깅 관련 함께 볼만한 자료 >
2024.02.27 - [Programming/Spring] - (spring) logback, log4j2 로깅 적용 과정과 xml 파일 차이점 정리
2023.08.20 - [Programming/Spring Boot] - spring boot 로그 파일 남기는 방법 (logback-spring.xml 설정)
'Programming > Spring' 카테고리의 다른 글
Logback PatternLayout을 통한 로그 마스킹 처리 방법 (1) | 2024.11.02 |
---|---|
MyBatis SQL Injection 발생하는 상황 및 방어 방법 (0) | 2024.04.10 |
spring + oracle(ojdbc) 연결 방법 정리 (0) | 2024.03.30 |
mybatis selectKey 사용 방법 및 주의할 점 (selectKey 다중 컬럼) (0) | 2024.03.20 |
ObjectMapper는 Bean으로 등록해서 사용하자 (0) | 2024.03.12 |