2024. 4. 30. 12:21ㆍ스프링 (Spring)/스프링 팁 (Spring Tip)
slf4j
스프링에서 간단하게 로깅할 필요가 있었는데
찾아보니 코틀린 진영에서는 kotlin-logging, klogging이 꽤나 유명한가보군요
뭐... 당연히 써도 됩니다.
다만 저는 의존성의 저주를 한 번 겪어봤기 때문에,
굳이 외부 라이브러리가 아닌 스프링에 내장된 걸로 사용하고 싶었었네요.
스프링에 내장된 로깅 라이브러리로는 slf4j가 있었습니다.
여기서 말하는 퍼사드(facade)란 복잡한 여러 과정을 하나로 묶어
정말 간단하게 사용할 수 있게끔 만들어준 것이라고 생각하면 됩니다.
정말 간단하긴 하더라고요
val logger: Logger = LoggerFactory.getLogger("hello")
logger.debug("world!")
여담으로 스프링 공식 문서를 보면 로깅에 크게 관심이 없는 것 같아 보였네요.
Spring Boot has no mandatory logging dependency, except for the Commons Logging API, which is typically provided by Spring Framework’s spring-jcl module. To use Logback, you need to include it and spring-jcl on the classpath.
Wrap Logger
시작 전, kotlin-logging 에서 유의미한 정보를 찾을 수 있었습니다.
스프링에서 로그 레벨을 지정해도, 무시하고 자원을 쓰나보네요.
해당 로직까지 포함해서 간단하게 만들어보죠.
2024. 05. 02 : 테스트 중 가장 높은 Level이 ERROR임을 확인해서, 사실 error()는 로그 레벨을 확인하는 로직이 필요 없어 제거했습니다.
class SimpleLogger(private val logger: Logger) {
fun debug(message: String) {
if (logger.isDebugEnabled) {
logger.debug(message)
}
}
fun debug(callMessage: () -> Any?) {
if (logger.isDebugEnabled) {
logger.debug(callMessage().toString())
}
}
fun info(message: String) {
if (logger.isInfoEnabled) {
logger.info(message)
}
}
fun info(callMessage: () -> Any?) {
if (logger.isInfoEnabled) {
logger.info(callMessage().toString())
}
}
fun warn(message: String) {
if (logger.isWarnEnabled) {
logger.warn(message)
}
}
fun warn(callMessage: () -> Any?) {
if (logger.isWarnEnabled) {
logger.warn(callMessage().toString())
}
}
fun error(message: String) {
logger.error(message)
}
fun error(callMessage: () -> Any?) {
logger.error(callMessage().toString())
}
}
companion object에서 정의한 로그를 사용하기 위해서 abstract class 도 만들어줍니다.
abstract class Logging {
val log: SimpleLogger = SimpleLogger(logger = LoggerFactory.getLogger(javaClass.enclosingClass))
}
log라는 이름은 입맛에 따라 바꿔도 됩니다.
그리고 혹시 Logger도 매번 생성하는 게 아닐까 걱정되서,
getLogger를 조금 파보면 LogerContext까지 도달하게 되는데
public class LoggerContext extends ContextBase implements ILoggerFactory, LifeCycle {
private Map<String, Logger> loggerCache = new ConcurrentHashMap();
...
public Logger getLogger(String name) {
if (name == null) {
throw new IllegalArgumentException("name argument cannot be null");
} else if ("ROOT".equalsIgnoreCase(name)) {
return this.root;
} else {
int i = 0;
Logger logger = this.root;
Logger childLogger = (Logger)this.loggerCache.get(name);
...
}
...
}
이름 기준으로 캐싱해두고 있었네요. 다행입니다 ㅎ
Logging
테스트 코드 몇 개 뚝딱 작성해서 실행해보면
class SimpleLoggerTest {
companion object : Logging()
@Nested
@TestMethodOrder(OrderAnnotation::class)
inner class BlockLoggingTest {
@Test
@Order(10)
fun givenMessageBlock_whenDebug_thenLogged() {
// given & when & then
log.debug { "hello debug block!" }
}
@Test
@Order(20)
fun givenMessageBlock_whenInfo_thenLogged() {
// given & when & then
log.info { "hello info block!" }
}
@Test
@Order(30)
fun givenMessageBlock_whenWarn_thenLogged() {
// given & when & then
log.warn { "hello warn block!" }
}
@Test
@Order(40)
fun givenMessageBlock_whenError_thenLogged() {
// given & when & then
log.error { "hello error block!" }
}
}
@Nested
inner class StringLoggingTest {
@Test
@Order(10)
fun givenMessage_whenDebug_thenLogged() {
// given & when & then
log.debug("hello debug message.")
}
@Test
@Order(20)
fun givenMessage_whenInfo_thenLogged() {
// given & when & then
log.info("hello info message.")
}
@Test
@Order(30)
fun givenMessage_whenWarn_thenLogged() {
// given & when & then
log.warn("hello warn message.")
}
@Test
@Order(40)
fun givenMessage_whenError_thenLogged() {
// given & when & then
log.error("hello error message.")
}
}
}
멋지게 실행되네요!
테스트 코드에서 볼 수 있듯 아래처럼 정적 변수로 만들어주면 해당 클래스에서 편리하게 사용할 수 있습니다
companion object : Logging()
Bonus : logback.xml
테스트할 때만 로그 레벨을 낮추고 싶다면 logback.xml을 만들어주면 됩니다.
참고 : https://docs.spring.io/spring-boot/how-to/logging.html#howto.logging.logback
저는 테스트에서는 DEBUG로 낮추고 싶었기에 test 디렉터리 바로 아래에 resources/logback.xml 파일을 만들고
가이드에 나온대로 간단하게 아래처럼 작성하고 root level을 원하는 만큼 낮추면 됩니다.
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<include resource="org/springframework/boot/logging/logback/defaults.xml"/>
<include resource="org/springframework/boot/logging/logback/console-appender.xml" />
<root level="DEBUG">
<appender-ref ref="CONSOLE" />
</root>
<logger name="org.springframework.web" level="DEBUG"/>
</configuration>
'스프링 (Spring) > 스프링 팁 (Spring Tip)' 카테고리의 다른 글
Kotlin 플러그인 All-open 파헤치기 (feat, JPA Fetch) (0) | 2024.05.18 |
---|---|
테스트 시 발생하는 위험 메시지 무시해보기 (0) | 2024.05.01 |
Mockito를 이용해 JpaRepository 테스트하기 (0) | 2024.04.30 |
Spring에 Jacoco 적용해보기 (0) | 2024.04.29 |
Spring에서 Gmail 보내기 (1) | 2024.04.29 |