목차
서론
자바 + 스프링부트를 사용할 때는 대부분 Lombok을 사용하여 각종 어노테이션으로 객체나 메소드들을 생성하게 된다. Slf4j의 Logger 객체 역시 클래스 위에 Lombok의 @Slf4j
를 붙여주는 것으로 편하게 생성할 수 있다. 그러나 코틀린에서는 아직 Lombok을 제대로 지원하지 않는다. 따라서 @Slf4j
어노테이션을 사용하여 Logger 객체를 생성하는 방법 역시 사용할 수 없다.
그렇다면 직접 Logger 객체를 생성해야 하는데 코틀린 + 스프링부트에서 Slf4j의 Logger 객체를 생성하는 방법이 여러 가지 있어서 한 번 정리해보려고 한다.
Logger 객체 생성방법
기본
Slf4j의 Logger 객체를 생성하는 가장 기본적인 방법은 아래와 같이 클래스 내부에서 LoggerFactory를 통해 직접 객체를 생성하는 것이다.
import org.slf4j.LoggerFactory
import org.springframework.boot.ApplicationArguments
import org.springframework.boot.ApplicationRunner
import org.springframework.stereotype.Component
@Component
class LogTest : ApplicationRunner {
private val log = LoggerFactory.getLogger(this.javaClass)!!
override fun run(args: ApplicationArguments?) {
log.info("로그 테스트")
}
}
getLogger()
의 매개변수로는 Logger를 사용하는 클래스의 타입을 넘겨주면 된다.
테스트 해보면 아래와 같이 로그가 잘 찍히는 것을 볼 수 있다.
그런데 로그를 찍기 위해 클래스마다 LoggerFactory를 사용하는 중복 코드를 사용하는 것이 마음에 들지 않을 수 있다. 그럴 땐 아래의 방법을 사용해 볼 수 있다.
팩토리 메소드 사용
별개의 파일 최상단에 다음과 같이 Logger 객체를 반환하는 메소드를 작성하여 Logger 객체를 사용할 클래스에서 호출해서 사용할 수 있다.
import org.slf4j.LoggerFactory
inline fun <reified T> T.logger() = LoggerFactory.getLogger(T::class.java)!!
Logger 객체를 사용할 클래스에서는 다음과 같이 사용할 수 있다.
@Component
class LogTest : ApplicationRunner {
val log = logger()
override fun run(args: ApplicationArguments?) {
log.info("팩토리 메소드 로그 테스트")
}
}
추상 클래스를 상속받는 companion object
Logger 객체를 갖는 추상 클래스를 작성하여 Logger 객체를 사용할 클래스에서 이를 상속하여 사용하는 방법도 있다. 아래와 같은 추상 클래스를 작성한다.
import org.slf4j.LoggerFactory
abstract class LoggerCreator {
val log = LoggerFactory.getLogger(this.javaClass)!!
}
Logger 객체를 사용할 클래스에서는 추상 클래스를 상속받는 companion object
를 선언하여 다음과 같이 사용할 수 있다.
@Component
class LogTest : ApplicationRunner {
companion object : LoggerCreator()
override fun run(args: ApplicationArguments?) {
log.info("추상 클래스 / 인터페이스 로그 테스트")
}
}
이 방법은 companion object
를 사용했기 때문에 로그에 찍히는 클래스 타입에 $Companion
이 붙는다는 차이가 있다. companion object
를 사용하지 않고 클래스 자체에서 추상 클래스를 상속받아서 사용할 수도 있고 이 경우에는 로그에 찍히는 클래스 타입에 $Companion
이 붙지도 않지만, Logger를 사용할 클래스에서 다른 클래스를 상속받아야 한다면 반드시 companion object
를 통해서 사용해야 한다.
아래와 같이 인터페이스의 프로퍼티에 커스텀 getter를 사용하여 Logger 객체를 얻는 방법도 있고 이 경우에는 클래스 자체에서 인터페이스를 구현하는 것에도 전혀 문제가 없으나, 이 방법을 사용하면 로그를 찍을 때마다 LoggerFactory의 getLogger()
를 호출하게 되므로 좀 비효율적이지 않을까 싶다.
import org.slf4j.LoggerFactory
interface Logger {
val log: org.slf4j.Logger
get() = LoggerFactory.getLogger(this.javaClass)!!
}
위임 프로퍼티
위임 패턴을 사용하여 Logger 객체 생성을 위임받을 별개의 클래스를 만들어두고 Logger 객체를 사용할 클래스에서 객체 생성을 위임하는 방법을 사용할 수도 있다.
import org.slf4j.Logger
import org.slf4j.LoggerFactory
class LoggerDelegator {
private var logger: Logger? = null
operator fun getValue(thisRef: Any?, property: Any?) = logger ?: LoggerFactory.getLogger(thisRef?.javaClass)!!
}
Logger 객체를 사용할 클래스에서는 다음과 같이 Logger 객체를 얻어서 사용할 수 있다.
@Component
class LogTest : ApplicationRunner {
val log by LoggerDelegator()
override fun run(args: ApplicationArguments?) {
log.info("위임 프로퍼티 로그 테스트")
}
}
Kotlin Logging 라이브러리 사용
Gradle이나 Maven에 Kotlin Logging 라이브러리를 추가하여 객체를 생성할 수도 있다. 이 방법을 사용하기 위해선 먼저 아래와 같이 의존성 라이브러리를 추가한다.
Gradle
implementation 'io.github.microutils:kotlin-logging:3.0.5'
Maven
<dependency>
<groupId>io.github.microutils</groupId>
<artifactId>kotlin-logging</artifactId>
<version>3.0.5</version>
</dependency>
이후 Logger 객체를 사용할 클래스에서는 아래와 같이 Logger 객체를 얻을 수 있다.
@Component
class LogTest : ApplicationRunner {
val log = KotlinLogging.logger {}
override fun run(args: ApplicationArguments?) {
log.info("Kotlin Logging 로그 테스트")
}
}
결론
개인적으로는 팩토리 메소드를 사용하는 방법이 가장 간단하다고 생각된다. 추상 클래스를 사용하는 방법은 따로 프로퍼티를 선언할 필요는 없지만 약간의 제약이 따르고, 인터페이스를 사용하는 방법은 로그를 찍을 때마다 LoggerFactory의 getLogger()
를 호출하게 된다는 점이 조금 걸린다. 위임 프로퍼티를 사용하는 방법은 아직 위임 프로퍼티에 대해서 잘 모르다 보니 꺼려지고, Kotlin Logging 라이브러리를 사용하는 방법은 외부 라이브러리를 추가해야 한다는 점에서 꺼려진다.
물론 각자 편한 방법이 있을 것이고 어떤 방법을 사용할지는 개인의 선택이라고 생각한다. 또한 이 글에 정리한 방법들 외에도 여러가지 방법이 있을 수 있다.
참조링크
'Development > Spring & Spring Boot' 카테고리의 다른 글
[Spring Boot] 스프링부트 실행 시 나타나는 배너를 바꿔보자 (0) | 2023.04.14 |
---|---|
[WebClient] WebClient를 사용하여 외부API 호출 후 처리할 비즈니스 로직을 비동기로 처리해보기 (0) | 2023.04.12 |
[Spring] 유틸성 메소드를 작성할 때 Spring Bean과 정적 메소드를 모아놓은 클래스 중 무엇을 사용해야 할까? (0) | 2023.03.15 |
[Spring Boot] 다중 Profile을 이용하여 환경에 따라 다른 설정 적용하기 (0) | 2023.01.30 |
[Spring/Spring Boot] Service와 ServiceImpl 구조에 대하여 (2) | 2023.01.26 |
댓글