SW 개발/Spring
Spring Boot, Kotlin, 로깅 정책 수립 및 방법 정리
지단로보트
2021. 3. 18. 08:22
개요
- 로그는 매우 중요하다. 나는 감히 로그가 애플리케이션의 시작과 끝이라고 단언할 수 있다. 잘 설계된 로그는 빠른 장애 대응 뿐만 아니라 회사 매출과도 직결되는 유의미한 데이터로서도 활용될 수 있다. Spring Boot의 로깅은 기본적으로
SLF4J
인터페이스를 기반으로 하여Logback
을 구현체로 작동하도록 설계되어 있다. 이번 글에서는 여기에 더하여 Kotlin 환경에서kotlin-logging
을 이용하여 로깅 정책을 수립하고 로그 적재의 예를 설명하고자 한다.
SLF4J 로그 레벨의 이해
- 로그 레벨을 명확히 이해하면 개발환경, 운영환경 단위로 어느 수준으로 로그를 남길지를 결정할 수 있다. 로그 레벨은 계층 구조로서 아래와 같다. (상위 계층은 하위 계층을 모두 포함하기 때문에 상위로 갈수록 상세한 로그를 남기게 된다.)
TRACE (TRACE, DEBUG, INFO, WARN, ERROR, FATAL) // 특정 케이스에 대한 트러블슈팅에 적합
DEBUG (DEBUG, INFO, WARN, ERROR, FATAL) // 개발 환경에 적합
INFO (INFO, WARN, ERROR, FATAL) // 운영 환경에 적합
WARN (WARN, ERROR, FATAL)
ERROR (ERROR, FATAL)
FATAL (FATAL)
build.gradle
- 프로젝트 루트의 /build.gradle 파일에 아래 내용을 추가한다.
dependencies {
implementation group: 'io.github.microutils', name: 'kotlin-logging-jvm', version: '2.0.6'
}
application.yml
- 프로젝트의 /src/main/resources/application.yml 파일에 아래 내용을 추가한다.
logging:
config: classpath:logback.xml
- 위는 로깅 정책을 Logback의 환경 설정 파일에 위임하는 조치로, Logback이 제공하는 다양한 로깅 정책을 세밀하게 지정할 수 있다. 프로파일 단위로 다른 환결 설정 파일을 지정하면 개발 환경, 운영 환경을 특성에 맞게 분리하여 정책을 수립하는 것도 가능하다.
logback.xml
- 가장 중요한 로그 환경 설정 파일을 작성할 차례이다. 공통적으로 많이 사용되는 로깅 패턴인 TEXT_CONSOLE, TEXT_FILE에 추가로 원격지 서버에 GELF 형식의 메시지를 UDP로 전송하는 GELF_UDP를 추가하였다. 프로젝트의 /src/main/resources/logback.xml 파일에 아래 내용을 추가한다.
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration>
<configuration>
<!-- 로그 파일을 적재할 디렉토리 경로를 지정 -->
<property name="LOG_FILE_PATH" value="/home/foobar/logs"/>
<!-- 로그 파일을 적재할 파일 이름을 지정 -->
<property name="LOG_FILE_NAME" value="foobar"/>
<!-- TEXT_CONSOLE 어펜더 정의 -->
<!-- 로컬 개발 환경에 적합 -->
<appender name="TEXT_CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<charset>UTF-8</charset>
<Pattern>%d %-4relative [%thread] %-5level %logger{35} - %msg%n</Pattern>
</encoder>
</appender>
<!-- TEXT_FILE 어펜더 정의 -->
<!-- 운영 환경에 적합 -->
<!-- 일자별로 로그 파일을 적재하되, 100MB를 초과하면 로그 파일 분할 -->
<!-- 최대 7일치를 보관하고 나머지는 삭제, 최대 1GB를 초과해도 나머지를 삭제 -->
<appender name="TEXT_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>${LOG_FILE_PATH}/${LOG_FILE_NAME}.log</file>
<rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
<fileNamePattern>${LOG_FILE_PATH}/${LOG_FILE_NAME}_%d{yyyy-MM-dd}.%i.log</fileNamePattern>
<maxFileSize>100MB</maxFileSize>
<maxHistory>7</maxHistory>
<totalSizeCap>1GB</totalSizeCap>
</rollingPolicy>
<encoder>
<charset>UTF-8</charset>
<pattern>%d %-4relative [%thread] %-5level %logger{35} - %msg%n</pattern>
</encoder>
</appender>
<!-- GELF_UDP 어펜더 정의 -->
<!-- 로그를 원격지의 Graylog 서버로 전송 -->
<appender name="GELF_UDP" class="biz.paluch.logging.gelf.logback.GelfLogbackAppender">
<host>udp:{hostname}</host>
<port>{port}</port>
<version>1.1</version>
<extractStackTrace>true</extractStackTrace>
<filterStackTrace>true</filterStackTrace>
<mdcProfiling>true</mdcProfiling>
<timestampPattern>yyyy-MM-dd HH:mm:ss,SSSS</timestampPattern>
<includeFullMdc>true</includeFullMdc>
<additionalFields>appender=GELF_UDP</additionalFields>
<additionalFieldTypes>appender=String</additionalFieldTypes>
<maximumMessageSize>8192</maximumMessageSize>
</appender>
<!-- 로거 이름이 GELF_UDP로 발생된 모든 INFO 레벨 이하 로그를 GELF_UDP 어펜더로 로그 전송 -->
<!-- 다른 어펜더에는 적재되지 않도록 제한 -->
<logger name="GELF_UDP" level="INFO" additivity="false">
<appender-ref ref="GELF_UDP"/>
</logger>
<!-- INFO 레벨 이하 로그는 TEXT_FILE 어펜더로 로그 적재 -->
<root level="INFO">
<appender-ref ref="TEXT_FILE"/>
</root>
</configuration>
로그 사용 예
- 애플리케이션 소스 코드 내에서의 로그 사용 예는 아래와 같다.
// 방법 1. 클래스 외부에 로거 선언
private val logger = KLogging()
class Foo {
// 방법 2. 클래스 내부에 로거 선언
companion object : KLogging()
// 방법 3. (앞서 logback.xml에서 정의한) 특정 로거 이름을 지정하여 로거 선언
companion object : NamedKLogging("TEXT_CONSOLE")
fun bar() {
val message = "로그 메시지 테스트"
logger.info("정상 로그: $message")
logger.warn("경고 로그: $message")
logger.error("오류 로그: $message")
}
}
추가로 읽어볼만한 글
- Spring Boot, Graylog로 GELF UDP 로그 전송하기
- Spring Boot, 비동기 쓰레드에서 호출 쓰레드의 MDC 보존하기
- 오픈 소스 로그 수집 관리 플랫폼, Graylog 기술 및 도입 전략 정리