본문 바로가기
  • 개발하는 곰돌이
Development/Spring & Spring Boot

[Kotlin + Spring Boot] 코틀린 + 스프링부트에서 Slf4j를 사용하여 로그 찍기

by 개발하는 곰돌이 2023. 4. 6.

목차

    서론

    자바 + 스프링부트를 사용할 때는 대부분 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 라이브러리를 사용하는 방법은 외부 라이브러리를 추가해야 한다는 점에서 꺼려진다.

     

    물론 각자 편한 방법이 있을 것이고 어떤 방법을 사용할지는 개인의 선택이라고 생각한다. 또한 이 글에 정리한 방법들 외에도 여러가지 방법이 있을 수 있다. 

    참조링크

     

    r/Kotlin on Reddit: SLF4J loggers in 3 ways

    Posted by u/Fiskepudding - 38 votes and 14 comments

    www.reddit.com

     

    GitHub - oshai/kotlin-logging: Lightweight Multiplatform logging framework for Kotlin. A convenient and performant logging facad

    Lightweight Multiplatform logging framework for Kotlin. A convenient and performant logging facade. - GitHub - oshai/kotlin-logging: Lightweight Multiplatform logging framework for Kotlin. A conven...

    github.com

    댓글