이전 프로젝트를 진행하면서 Logback 으로 file-appender
를 구성한 경험이 있습니다.
하지만 Logback 에 익숙치 않아 코드가 매우 더러웠고, 무엇보다 Spring profile
에 따른 appender 변경이 불가능한 코드였습니다.
그래서 이번 프로젝트에선 "반드시 깔끔하고 우아한 Logback 구성을 만들어 내겠다"
라는 다짐이 있었습니다.
이번 포스팅에서는 이것이 어떻게 가능한지, 그리고 이를 활용한 file-appender
및 discord-appender
를 어떻게 구성하는지 공유하고자 합니다.
Spring Boot 는 다양한 logging framework 를 지원합니다.
때문에 spring boot 는 framework 간 격차를 줄이기 위해 다음 목록의 스프링 변수를 시스템 변수 (Environment Variable) 로 제공, 통합하여 로깅을 진행합니다.
특히 Logback 을 사용할 경우 아래의 속성 또한 이전됩니다.
이를 통해 한가지 전략을 생각할 수 있습니다.
로그 패턴, time format 등 잡다한 구성은 Spring property 에 선언, 환경변수로 Logback config 에 사용하자
우리가 흔히 Logback 구성을 진행할 때, 가장 마음이 편치 않은 부분은 코드의 중복
입니다.
<!-- ANSI 적용되는 콘솔용 로그 패턴 -->
<!-- 패턴 수정하려면 이 길고 긴 값을 수정해야 함 -->
<property name="LOG_PATTERN_CONSOLE" value="%clr(%d{yyyy-MM-dd HH:mm:ss.SSS}){faint} %clr(%5p) %clr(${PID:- }){magenta} %clr([%t]){faint} %clr(%-40.40logger{39}){cyan} %clr(:){faint} %m%n%wEx"/>
<!-- ANSI 코드 없는 그냥 로그 패턴 -->
<property name="LOG_PATTERN_PLAIN" value="%d{yyyy-MM-dd HH:mm:ss.SSS} %5p ${PID:- } [%t] %-40.40logger{39} : %m%n%wEx"/>
<!-- 예시 console appender -->
<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>${LOG_PATTERN_CONSOLE}</pattern>
</encoder>
</appender>
<!-- 예시 file appender -->
<appender name="FILE-ERROR" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>${LOG_DIR}/error/error-${BY_DATE}.log</file>
<filter class="ch.qos.logback.classic.filter.ThresholdFilter">
<level>ERROR</level>
</filter>
<rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
<fileNamePattern>${LOG_DIR}/backup/error/error-%d{yyyy-MM-dd}.%i.log</fileNamePattern>
<maxFileSize>100MB</maxFileSize>
<maxHistory>30</maxHistory>
<totalSizeCap>3GB</totalSizeCap>
</rollingPolicy>
</appender>
위 예시에선 로그 패턴
, 로그 경로
등을 property
로 사용하긴 했지만, 그 값을 변경하려면 위 config
파일의 내용을 변경해야 합니다.
또한 임시 file appender
의 경우 maxFileSize
, maxHistory
등의 속성을 변경하려면 직접 찾아 변경해야 합니다.
즉, config 마다 중복된 내용을 선언해야 할 뿐더러 내용을 변경하기 아주 귀찮다는 것입니다.
때문에 이들을 Logback config 에 직접 선언하지 말고, application.yaml
에서 선언, export 된 환경변수를 사용하자는 것입니다.
logging:
pattern:
console: '%clr(%d{yyyy-MM-dd HH:mm:ss.SSS}){faint} %clr(%5p) %clr(${PID:- }){magenta} %clr([%t]){faint} %clr(%-40.40logger{39}){cyan} %clr(:){faint} %m%n%wEx'
file: '%d{yyyy-MM-dd HH:mm:ss.SSS} %5p ${PID:- } [%t] %-40.40logger{39} : %m%n%wEx'
logback:
rollingpolicy:
max-file-size: 10MB
total-size-cap: 2GB
max-history: 7
clean-history-on-start: true
<!-- 이런식으로 spring 이 export 해준 환경 변수를 이용 -->
<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>${CONSOLE_LOG_PATTERN}</pattern>
</encoder>
</appender>
<appender name="FILE-ERROR" class="ch.qos.logback.core.rolling.RollingFileAppender">
<filter class="ch.qos.logback.classic.filter.ThresholdFilter">
<level>ERROR</level>
</filter>
<rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
<maxFileSize>
${LOGBACK_ROLLINGPOLICY_MAX_FILE_SIZE:-10MB}
</maxFileSize>
<maxHistory>
${LOGBACK_ROLLINGPOLICY_MAX_HISTORY:-7}
</maxHistory>
<totalSizeCap>
${LOGBACK_ROLLINGPOLICY_TOTAL_SIZE_CAP:-0}
</totalSizeCap>
</rollingPolicy>
</appender>
springProfile
문서를 더 읽어보면 Spring Boot 가 Logback 에 추가적으로 제공하는 기능 이 서술되어 있고, 이 중 <springProfile>
tag 에 대한 설명이 있습니다.
<springProfile/>
는 활성화된 profile 에 따라 정의된 config 섹션을 선택적으로 읽는 기능 으로, Logback config 파일 내 어디에든 사용 가능하다 서술되 있습니다.
<springProfile name="console-error">
<appender name="CONSOLE_ERROR" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>%-4relative [%thread] %-5level %logger{35} - %msg %n</pattern>
</encoder>
<filter class="ch.qos.logback.classic.filter.LevelFilter">
<level>ERROR</level>
<onMatch>ACCEPT</onMatch>
<onMismatch>DENY</onMismatch>
</filter>
</appender>
</springProfile>
<springProfile name="console-info">
<appender name="CONSOLE_INFO" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>%-4relative [%thread] %-5level %logger{35} - %msg %n</pattern>
</encoder>
<filter class="ch.qos.logback.classic.filter.LevelFilter">
<level>INFO</level>
<onMatch>ACCEPT</onMatch>
<onMismatch>DENY</onMismatch>
</filter>
</appender>
</springProfile>
즉, 위 예시처럼 구성하면 console-error
profile 에는 CONSOLE_ERROR
를, console-info
에는 CONSOLE_INFO
를 읽어 사용할 수 있다는 것입니다.
<springProfile>
문서를 통해 Logback config 를 어떻게 구현할지 생각할 수 있습니다.
1. 필요한 모든 appender 를 정의해 둡니다.
2. 정의한 appender 를 선택해 상황에 알맞은 logger 를 구성합니다. 이 때 logger 는
<springProfile>
을 통해 특정 profile 활성화시 읽어들이도록 구성합니다.3.
application.yaml
에서"logger-profile"
을 추가, 필요한 logger 를 사용합니다.
참고로 Spring 문서를 보면 logging framework 별 Spring Boot 가 어떻게 로깅을 어떻게 진행하는지 확인할 수 있습니다.
특히 이 중 Logback 을 확인해보면 Spring Boot 가 사용하는 file-appender 를 확인하실 수 있습니다.
<?xml version="1.0" encoding="UTF-8"?>
<!--
File appender logback configuration provided for import, equivalent to the programmatic
initialization performed by Boot
-->
<included>
<appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<filter class="ch.qos.logback.classic.filter.ThresholdFilter">
<level>${FILE_LOG_THRESHOLD}</level>
</filter>
<encoder>
<pattern>${FILE_LOG_PATTERN}</pattern>
<charset>${FILE_LOG_CHARSET}</charset>
</encoder>
<file>${LOG_FILE}</file>
<rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
<fileNamePattern>${LOGBACK_ROLLINGPOLICY_FILE_NAME_PATTERN:-${LOG_FILE}.%d{yyyy-MM-dd}.%i.gz}</fileNamePattern>
<cleanHistoryOnStart>${LOGBACK_ROLLINGPOLICY_CLEAN_HISTORY_ON_START:-false}</cleanHistoryOnStart>
<maxFileSize>${LOGBACK_ROLLINGPOLICY_MAX_FILE_SIZE:-10MB}</maxFileSize>
<totalSizeCap>${LOGBACK_ROLLINGPOLICY_TOTAL_SIZE_CAP:-0}</totalSizeCap>
<maxHistory>${LOGBACK_ROLLINGPOLICY_MAX_HISTORY:-7}</maxHistory>
</rollingPolicy>
</appender>
</included>
이제 위 내용을 활용해 필요한 로거를 구성해보도록 하겠습니다.
file-logger
error-warn-file-appender.xml
앞서 본 file-appender
를 바탕으로 WARN level 이상의 로그를 저장하는 appender 를 구성해 줍니다.
<?xml version="1.0" encoding="UTF-8" ?>
<included>
<include resource="logback/base.xml"/>
<property name="ERROR_WARN_LOG_FILE" value="error-warn"/>
<property name="ERROR_WARN_FILE_PATH" value="${LOG_DIR:-./logs}/${ERROR_WARN_LOG_FILE}"/>
<appender class="ch.qos.logback.core.rolling.RollingFileAppender" name="ERROR_WARN_FILE">
<append>true</append>
<encoder>
<charset>${FILE_LOG_CHARSET}</charset>
<pattern>${FILE_LOG_PATTERN}</pattern>
</encoder>
<filter class="ch.qos.logback.classic.filter.ThresholdFilter">
<level>WARN</level>
</filter>
<rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
<cleanHistoryOnStart>
${LOGBACK_ROLLINGPOLICY_CLEAN_HISTORY_ON_START:-false}
</cleanHistoryOnStart>
<fileNamePattern>
${LOGBACK_ROLLINGPOLICY_FILE_NAME_PATTERN:-${ERROR_WARN_FILE_PATH}.%d{yyyy-MM-dd}.%i.log}
</fileNamePattern>
<maxFileSize>
${LOGBACK_ROLLINGPOLICY_MAX_FILE_SIZE:-10MB}
</maxFileSize>
<maxHistory>
${LOGBACK_ROLLINGPOLICY_MAX_HISTORY:-7}
</maxHistory>
<totalSizeCap>
${LOGBACK_ROLLINGPOLICY_TOTAL_SIZE_CAP:-0}
</totalSizeCap>
</rollingPolicy>
</appender>
</included>
이를 이전 Spring Boot 의 file-appender
와 비교하면 크게 다른 점이 없는 것을 볼 수 있습니다.
다만 필요를 위해 ERROR_WARN_LOG_FILE
, ERROR_WARN_FILE_PATH
의 property
를 구성했고, rolling policy 의 fileNamePattern
에 활용됨을 알 수 있습니다.
더불어 <file>
tag 를 생략했는데, 이는 모든 로그가 rolling 되며 저장하기 위해 생략했습니다.
file-logger.xml
다음으로 이를 활용하는 로거를 구성해 줍니다.
<?xml version="1.0" encoding="UTF-8" ?>
<included>
<springProfile name="file-logger">
<include resource="logback/appender/error-warn-file-appender.xml"/>
<root level="info">
<appender-ref ref="ERROR_WARN_FILE"/>
</root>
</springProfile>
</included>
이 때 <springProfile>
을 이용해 해당 구성이 file-logger
profile 에만 활성화 되도록 구성해 줍니다.
discord-logger
다음으로 Discord 에 알림을 보내는 로거를 구성해봅시다.
해당 로거는 Logback 의 AsyncAppender
와 오픈소스로 열려있는 logback-discord-appender
을 조합해 구성할 수 있습니다.
이 때 logback-discord-appender
을 사용하기 위해선 build.gradle
에 아래와 같은 설정이 필요합니다.
repositories {
maven { url 'https://jitpack.io' }
}
dependencies {
runtimeOnly 'com.github.napstr:logback-discord-appender:1.0.0'
}
discord-appender.xml
logback-discord-appender
문서의 기본 구성을 바탕으로 아래와 같은 appender 를 구성할 수 있습니다.
<?xml version="1.0" encoding="UTF-8" ?>
<included>
<include resource="logback/base.xml"/>
<springProperty name="DISCORD_WEBHOOK_URL" scope="context" source="logback-config.discord-webhook"/>
<appender class="com.github.napstr.logback.DiscordAppender" name="DISCORD">
<avatarUrl>http://i.imgur.com/UoiA3OQ.png</avatarUrl>
<layout class="ch.qos.logback.classic.PatternLayout">
<pattern>### 에러가 발생했어요%n- 발생 시각 : `%d{yyyy-MM-dd'T'HH:mm:ss.SSSz,Asia/Seoul}`%n- Request ID :
`${LOG_LEVEL_PATTERN}`%n- 레벨 : `[%-5level]`%n- 로거 : `%logger{36}`%n- 메시지 : %msg%n- 전체 로그 :
%n```${FILE_LOG_PATTERN}```
</pattern>
</layout>
<tts>false</tts>
<username>[!!!ERROR!!!]</username>
<webhookUri>${DISCORD_WEBHOOK_URL}</webhookUri>
</appender>
</included>
이 때 DISCORD_WEBHOOK_URL
알림을 보낼 Discord 의 webhook url 로, Spring property 중 logback-config.discord-webhook
로 정의된 값을 이용하는 것을 볼 수 있습니다.
discord-error-logger.xml
다음으로 discord-appender
를 활용해 ERROR level 이상 로그를 알림 보내는 discord-error-logger
를 구성할 수 있습니다.
<?xml version="1.0" encoding="UTF-8" ?>
<included>
<springProfile name="discord-error-logger">
<include resource="logback/appender/discord-appender.xml"/>
<appender name="ASYNC_DISCORD" class="ch.qos.logback.classic.AsyncAppender">
<appender-ref ref="DISCORD"/>
<filter class="ch.qos.logback.classic.filter.ThresholdFilter">
<level>ERROR</level>
</filter>
</appender>
<root level="info">
<appender-ref ref="ASYNC_DISCORD"/>
</root>
</springProfile>
</included>
물론 ASYNC_DISCORD
appender 를 따로 구성할수도 있지만 그다지 큰 이점이 없어보여 discord-error-logger
에 포함시켰습니다.
Logback-spring.xml
과 application.yaml
이제 모든 로거를 구성했으니 이를 불러올 Logback-spring
을 구성해 줍니다.
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<include resource="logback/base.xml"/>
<springProperty name="LOG_DIR" scope="context" source="logback-config.log-directory"/>
<include resource="logback/logger/file-logger.xml"/>
<include resource="logback/logger/discord-error-logger.xml"/>
<root level="info">
<appender-ref ref="CONSOLE"/>
</root>
</configuration>
기본적으로 로그는 console 에 보여야 한다 생각해 Spring Boot 의 console-appender
를 포함시켰습니다.
이후 아래처럼 application.yaml
을 구성할 수 있습니다.
spring:
profiles:
default: local
group:
local:
- file-logger # 필요한 logger profile 을 선택
staging:
- file-logger
- discord-error-logger
prod:
- file-logger
- discord-error-logger
logback-config: # file-appender, discord-appender 에 필요했던 변수들
log-directory: ./logs
discord-webhook: ${discord.webhook-url}
logging: # file-appender 의 rolling policy
logback:
rollingpolicy:
max-file-size: 10MB
total-size-cap: 2GB
max-history: 7
clean-history-on-start: true
이제 모든 구성을 마쳤으니 테스트를 진행해 봅시다.
file-logger
discord-error-logger
모두 정상 작동됨을 확인할 수 있습니다.
src/main/resources/
├── logback
│ ├── appender
│ │ ├── discord-appender.xml
│ │ └── error-warn-file-appender.xml
│ ├── logger
│ │ ├── discord-error-logger.xml
│ │ └── file-logger.xml
│ └── base.xml
├── application.yaml
└── logback-spring.xml
base.xml
<?xml version="1.0" encoding="UTF-8" ?>
<included>
<include resource="org/springframework/boot/logging/logback/defaults.xml"/>
<include resource="org/springframework/boot/logging/logback/console-appender.xml"/>
</included>