보통 개발 과정에서 실행 시 에러가 나면, 콘솔의 에러 로그를 찾아서 디버깅하고 다시 실행하기 마련이죠. 하지만 배포 환경이라면 어떨까요? 만약 배포 환경에서 에러가 난다면 콘솔의 로그를 찾는 것 만으로는 한계가 있을 거에요. 이처럼, 배포 환경에서의 로그는 개발 환경과는 다른 방식으로 처리되어야 하겠죠. 이번 글에서는 이러한 로깅을 도와주는 Log4j2를 다뤄보려고 합니다.
plugins {
id 'org.springframework.boot' version '2.3.1.RELEASE'
id 'io.spring.dependency-management' version '1.0.9.RELEASE'
id 'java'
}
group = 'com.fucct'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = '1.8'
configurations {
compileOnly {
extendsFrom annotationProcessor
}
}
repositories {
mavenCentral()
}
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-web'
implementation 'org.springframework.boot:spring-boot-starter-mail'
implementation 'org.springframework.boot:spring-boot-starter-log4j2'
testImplementation 'org.springframework.boot:spring-boot-starter-log4j2'
implementation 'org.apache.logging.log4j:log4j-web:2.12.1'
implementation 'com.fasterxml.jackson.dataformat:jackson-dataformat-yaml:2.11.1'
implementation 'com.lmax:disruptor:3.4.2'
compileOnly 'org.projectlombok:lombok'
runtimeOnly 'com.h2database:h2'
developmentOnly 'org.springframework.boot:spring-boot-devtools'
annotationProcessor 'org.projectlombok:lombok'
testImplementation('org.springframework.boot:spring-boot-starter-test') {
exclude group: 'org.junit.vintage', module: 'junit-vintage-engine'
}
}
configurations {
all {
exclude group: 'org.springframework.boot', module: 'spring-boot-starter-logging'
}
}
test {
useJUnitPlatform()
}
build.gradle
기본적으로 Spring은 Slf4j라는 로깅 프레임워크를 사용합니다. 구현체를 손쉽게 교체할 수 있도록 도와주는 프레임 워크입니다. Slf4j는 인터페이스고 내부 구현체로 logback을 가지고 있는데, 저는 Log4j 2를 사용하기 위해 exclude 했습니다.
<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="INFO">
<!-- 해당 설정파일에서 사용하는 프로퍼티-->
<Properties>
<Property name="logNm">Spring Log4j2 Test</Property>
<Property name="layoutPattern">%style{%d{yyyy/MM/dd HH:mm:ss,SSS}}{cyan} %highlight{[%-5p]}{FATAL=bg_red, ERROR=red,
INFO=green, DEBUG=blue} [%C] %style{[%t]}{yellow}- %m%n -</Property>
</Properties>
<!-- LogEvent를 전달해주는 Appender-->
<Appenders>
<Console name="Console_Appender" target="SYSTEM_OUT">
<PatternLayout pattern="${layoutPattern}"/>
</Console>
<RollingFile name="File_Appender" fileName="logs/${logNm}.log" filePattern="logs/${logNm}_%d{yyyy-MM-dd}_%i.log.gz">
<PatternLayout pattern="${layoutPattern}"/>
<Policies>
<SizeBasedTriggeringPolicy size="200KB"/>
<TimeBasedTriggeringPolicy interval="1"/>
</Policies>
<DefaultRolloverStrategy max="10" fileIndex="min"/>
</RollingFile>
</Appenders>
<!-- 실제 Logger-->
<Loggers>
<Root level="INFO" additivity="false">
<AppenderRef ref="Console_Appender"/>
<AppenderRef ref="File_Appender"/>
</Root>
<Logger name="org.springframework" level="DEBUG"
additivity="false">
<AppenderRef ref="Console_Appender" />
<AppenderRef ref="File_Appender"/>
</Logger>
<Logger name="com.fucct" level="INFO" additivity="false">
<AppenderRef ref="Console_Appender" />
<AppenderRef ref="File_Appender"/>
</Logger>
<Logger name="com.fucct.springlog4j2.loggertest" level="TRACE" additivity="false">
<AppenderRef ref="Console_Appender" />
</Logger>
</Loggers>
</Configuration>
log4j2.xml
Log4j 2에선 설정파일을 Json, xml, yml등 다양한 방식으로 사용할 수 있습니다. 저는 jackson 의존성을 추가하고 싶지 않아서 xml을 사용했습니다. 해당 설정파일에 대해 알지 못해도 괜찮습니다. 다음 글에서 충분히 설명해드릴게요!😎
해당 설정을 log4j2.xml 로 지정하고, resources 패키지에 저장하시고, application.yml 파일에 해당 프로퍼티를 추가하시면 됩니다.
logging:
config: classpath:log4j2.xml
application.yml
해당 설정을 완료하시면 로거를 자유롭게 사용하실 수 있습니다. 다만 설정을 어떻게 하는지는 다음 글에서 다뤄보도록 하겠습니다. 너무 많은 내용을 한 글에 담으면 지루하더라구요 😭
@RestController
@RequestMapping("/api/samples")
@RequiredArgsConstructor
@Slf4j
public class SampleController {
private static final Logger logger = LogManager.getLogger(SampleController.class);
@GetMapping
public ResponseEntity<String> create(@RequestParam("name") String request) {
logger.info("Hello. This is LogManager's logger");
log.info("Hello. This is Lombok's logger");
System.out.println(logger.equals(log));
return ResponseEntity.ok(request);
}
}
SampleController.java
(과연 출력문의 결과는 어떻게 될까요?)
해당 로깅의 결과는 다음과 같습니다.
컨트롤러의 첫 로거(logger)는 필드에 존재하는 로거입니다. 두 번째 로거(log)는 Lombok의 @Slf4j를 통해 받아온 로거입니다. 사실 같은 구현체를 쓰기 때문에 차이는 없습니다. 실제 Lombok이 컴파일된 코드는 다음과 같습니다.
참고로 테스트 코드에선 Lombok의존성과 Log4j2의존성을 테스트 스코프로 설정하지 않으면 사용할 수 없습니다. 물론 의존성을 추가하는 것도 좋지만 해당 방식으로 로거를 구해올 수 있습니다.
@SpringBootTest
public class LoggerTest {
private static Logger logger = null;
@BeforeEach
public void setLogger() {
System.setProperty("log4j.configurationFile", "log4j2.yml");
logger = LogManager.getLogger();
}
@RepeatedTest(2)
void givenLoggerWithDefaultConfig_whenLogToConsole_thanOK() {
Exception e = new RuntimeException("Hello World!");
logger.info("THIS IS INFO LEVEL LOG");
logger.debug("THIS IS DEBUG LEVEL LOG");
logger.error("THIS IS ERROR LEVEL LOG.", e);
logger.fatal("THIS IS FATAL LEVEL LOG.");
}
}
물론 단순한 테스트에도 SpringBootTest를 해야한다는 단점이 있어, 더 좋은 방법이 있다면 공유해드리도록 하겠습니다.
이렇게 간단한 Log4j2 설정방법과 사용법에 대해 공유해봤습니다. 물론 구체적인 내용을 말씀드리진 않았지만, 설정이 안되면 코딩을 하면서 공부하기가 어렵더라구요 🤔 코딩 하면서 공부하는 걸 좋아하는 저로썬, 설정 먼저 해서 직접 쳐보고, 그 내용을 바탕으로 확장시켜 나가는게 좋다고 생각합니다. 여기서 글을 마무리 하고, 다음 글에선 설정 파일의 의미를 설명드리고, 커스텀하는 방법에 대해 이야기 해볼게요. 긴 글 읽어주셔서 감사합니다😊
정말 잘봤습니다....감사합니다.
다음편이 기대 됩니다.