본문 바로가기
프로그래밍/Spring Boot

Kotlin 에서 Slf4j를 통해 로깅하기

by 카프카뮈 2024. 1. 1.

개요

 

8. 로그 파일 만들기, 인터셉터 구현하기

코드 확인을 위해, 관련 PR 링크를 첨부한다. #5 로깅 구현 및 로깅을 위한 인터셉터 구현하기 by include42 · Pull Request #10 · include42/spring-books-diary Resolved: #5 실수로 Develop 브랜치에 커밋해 버려서, Rev

kafcamus.tistory.com

이전에 위 글을 통해, java 기반의 spring 프로젝트에서 어떻게 로깅을 하는지에 대해 정리해 두었다.

다만 kotlin에서는 lombok 사용에 불편함이 있어, 다른 방식으로 로깅을 수행해야 한다.

이때 어떻게 하면 좋은지 간단하게 정리해 보고자 한다.

팩토리 메소드 

아래와 같은 코드를 전역 범위에서 쓸 수 있도록 만들어 준다.

import org.slf4j.Logger
import org.slf4j.LoggerFactory

inline fun <reified T> T.logger(): Logger {
    if (T::class.isCompanion) {
        return LoggerFactory.getLogger(T::class.java.enclosingClass)
    }
    return LoggerFactory.getLogger(T::class.java)
}

위 코드를 간단히 해석해보면 다음과 같다.

  • 특정한 클래스 하위에서 logger 메소드를 호출시 Logger 클래스를 생성하여 반환
  • 이때 Logger의 이름은 호출한 클래스의 이름으로 지정됨
  • logger 메소드가 할당될 값이 companion object 로 지정되는 경우, enclosingClass의 이름을 가져와서 Logger의 이름으로 지정

위의 메소드를 실제 사용하는 예는 다음과 같다.

@Service
class ExampleService(
) {
    private val log = logger() // log 생성

    fun doSomething() {
  	val value = getValue()
        if (value == null) {
            log.error("cannot find value!")
            return
        }
        (...)
    }
}
  • log를 logger 메소드를 통해 호출해 주면, 로그 발생시 해당 클래스(ExampleService)의 이름으로 로깅이 수행된다.
  • 사용은 slf4j 와 동일하게 가능하다.

Logger 의 이름 설정하기

다만 위와 같이 사용할 경우, 아쉬운 점이 생긴다.

이전에 쓴 로깅 관련 포스트에서, RollingFileAppender를 통해 로그를 파일로 저장하는 방법에 대해 언급하였다.

아래는 당시에 사용한 예시 logback.xml 파일이다. (resource 경로에 두면 됨)

<?xml version="1.0" encoding="UTF-8"?>
<configuration>
    <include resource="org/springframework/boot/logging/logback/base.xml"/>

    <property name="home" value="logs"/>

    <appender name="DEFAULT_FILE_APPENDER" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
            <fileNamePattern>${home}/access-%d{yyyy-MM-dd}.%i.log</fileNamePattern>
            <maxFileSize>15mb</maxFileSize>
            <maxHistory>30</maxHistory>
        </rollingPolicy>
        <encoder>
            <charset>utf8</charset>
            <pattern>${FILE_LOG_PATTERN}</pattern>
        </encoder>
    </appender>

    <appender name="ERROR_FILE_APPENDER" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
            <fileNamePattern>${home}/error-%d{yyyy-MM-dd}.%i.log</fileNamePattern>
            <maxFileSize>15mb</maxFileSize>
            <maxHistory>30</maxHistory>
        </rollingPolicy>
        <encoder>
            <charset>utf8</charset>
            <pattern>${FILE_LOG_PATTERN}</pattern>
        </encoder>
    </appender>

    <logger name="ERROR_FILE_LOGGER" level="ERROR" additivity="true">
        <appender-ref ref="ERROR_FILE_APPENDER"/>
    </logger>

    <logger name="DEFAULT_FILE_LOGGER" level="DEBUG" additivity="true">
        <appender-ref ref="DEFAULT_FILE_APPENDER"/>
    </logger>
</configuration>

위와 같은 설정이 되어있는 경우, 다음과 같은 문제가 발생한다.

  • java에서 로깅을 수행할 때에는 @Slf4j 어노테이션에 name 필드를 지정해서, "ERROR_FILE_LOGGER"와 같은 특정 로깅 타입의 이름을 지정했었다.
  • 다만 현재 LoggerFactory 메소드는 클래스를 지정하고 있고, name property는 getter만을 제공한다.

이를 해결하는 방법은 간단하다. (너무 쉬워서 오히려 한참 돌아갔지만...)

private val log = LoggerFactory.getLogger("ERROR_FILE_LOGGER")

위와 같이 클래스 대신 name 값을 전달해서 getLogger 메소드를 호출하면 된다.

이러한 방식은 아래 경우에서 사용할 수 있다.

  • 어디에서 호출되었는지 중요하지 않은 로그 (인터셉터나 필터 등에서 호출되는 로그)
  • 해당 로그를 카테고리로 묶어 특정 동작(slack 알람, 파일 저장 등...)을 수행해야 하는 경우

나의 경우, 아래 두 클래스에 대해서는 위의 예시처럼 이름을 지정하였다.

  • global ExceptionHandler: RestControllerAdvice 로 프로젝트 전체의 예외 발생에 대해 핸들링을 해주고 있는데, 여기에서 찍어주는 로그는 ERROR_FILE_LOGGER 로 지정해두고 파일로 저장하여 관리하고 있다.
    • 예외가 발생하는 부분에는 해당 클래스를 이름으로 가지는 logger가 별도의 로그를 찍어주고 있음
  • intercepter: HTTP로 들어오는 요청 및 응답을 로깅하기 위해 intercepter 클래스를 만들어두었는데, 해당 클래스에서는 WEBLOG_FILE_LOGGER 로 로그를 지정하고 파일로 저장하여 관리하고 있다.

마치며

최근에 일이 바빠 포스팅을 잘 못하고 있다.

다만 개인적으로 틈이 날 때 조금씩 토이 프로젝트를 하고 있어서,

kotlin 기반의 프로젝트를 만들다 보니 이렇게 조금씩 다른 부분들을 배우고 정리하게 된다.

 

혹시 잘못되었거나 개선할 내용이 있다면, 코멘트 부탁드립니다.

반응형

댓글