현재 프로젝트의 환경과 그에 따라 필요한 로그 설정은 다음과 같습니다.
프로젝트에서는 Slf4j, Logback을 사용하기로 했기 때문에 이를 기반으로 로그 설정을 진행했습니다.
초기 구현 시에는 다음과 같이 실행 환경(Profile)에 따라 단순하게 구현했습니다.
<?xml version="1.0" encoding="UTF-8" ?>
<configuration>
<springProperty scope="context" name="LOG_DIR" source="log.directory" />
<timestamp key="BY_DATE" datePattern="yyyy-MM-dd" />
<springProfile name="local">
<include resource="org/springframework/boot/logging/logback/defaults.xml" />
<include resource="org/springframework/boot/logging/logback/console-appender.xml" />
<root level="INFO">
<appender-ref ref="CONSOLE" />
</root>
</springProfile>
<springProfile name="dev">
<appender name="FILE-WARN" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>${LOG_DIR}/warn/warn-${BY_DATE}.log</file>
<filter class="ch.qos.logback.classic.filter.LevelFilter">
<level>WARN</level>
<onMatch>ACCEPT</onMatch>
<onMismatch>DENY</onMismatch>
</filter>
<rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
<fileNamePattern>${LOG_DIR}/backup/warn/warn-%d{yyyy-MM-dd}.%i.log</fileNamePattern>
<maxFileSize>100MB</maxFileSize>
<maxHistory>30</maxHistory>
<totalSizeCap>3GB</totalSizeCap>
</rollingPolicy>
</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>
<root level="INFO">
<appender-ref ref="FILE-WARN" />
<appender-ref ref="FILE-ERROR" />
</root>
</springProfile>
<springProfile name="prod">
<appender name="FILE-WARN" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>${LOG_DIR}/warn/warn-${BY_DATE}.log</file>
<filter class="ch.qos.logback.classic.filter.LevelFilter">
<level>WARN</level>
<onMatch>ACCEPT</onMatch>
<onMismatch>DENY</onMismatch>
</filter>
<rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
<fileNamePattern>${LOG_DIR}/backup/warn/warn-%d{yyyy-MM-dd}.%i.log</fileNamePattern>
<maxFileSize>100MB</maxFileSize>
<maxHistory>30</maxHistory>
<totalSizeCap>3GB</totalSizeCap>
</rollingPolicy>
</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>
<appender name="SLACK" class="com.ddang.ddang.configuration.log.SlackAppender">
<filter class="ch.qos.logback.classic.filter.ThresholdFilter">
<level>ERROR</level>
</filter>
</appender>
<appender name="ASYNC-SLACK" class="ch.qos.logback.classic.AsyncAppender">
<appender-ref ref="SLACK" />
</appender>
<root level="INFO">
<appender-ref ref="FILE-WARN" />
<appender-ref ref="FILE-ERROR" />
<appender-ref ref="ASYNC-SLACK" />
</root>
</springProfile>
</configuration>
이 경우 다음과 같은 문제점이 있습니다.
위에서 발생한 문제 중 가장 시급한 것은 코드 중복으로 인해 가독성이 떨어진다는 점입니다.
이를 고려해 다음과 같이 Appender를 분리했습니다.
<included>
<include resource="org/springframework/boot/logging/logback/defaults.xml" />
<include resource="org/springframework/boot/logging/logback/console-appender.xml" />
</included>
<included>
<appender name="FILE-WARN" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>${LOG_DIR}/warn/warn-${BY_DATE}.log</file>
<filter class="ch.qos.logback.classic.filter.LevelFilter">
<level>WARN</level>
<onMatch>ACCEPT</onMatch>
<onMismatch>DENY</onMismatch>
</filter>
<rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
<fileNamePattern>${LOG_DIR}/backup/warn/warn-%d{yyyy-MM-dd}.%i.log</fileNamePattern>
<maxFileSize>100MB</maxFileSize>
<maxHistory>30</maxHistory>
<totalSizeCap>3GB</totalSizeCap>
</rollingPolicy>
</appender>
</included>
<included>
<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>
</included>
<included>
<appender name="SLACK_APPENDER" class="com.ddang.ddang.configuration.log.SlackAppender">
<filter class="ch.qos.logback.classic.filter.ThresholdFilter">
<level>ERROR</level>
</filter>
</appender>
<appender name="ASYNC_SLACK_APPENDER" class="ch.qos.logback.classic.AsyncAppender">
<appender-ref ref="SLACK_APPENDER" /></appender>
</included>
<?xml version="1.0" encoding="UTF-8" ?>
<configuration>
<springProperty scope="context" name="LOG_DIR" source="log.directory" />
<timestamp key="BY_DATE" datePattern="yyyy-MM-dd" />
<springProfile name="local">
<include resource="console-logging.xml" />
<root level="INFO">
<appender-ref ref="CONSOLE" />
</root>
</springProfile>
<springProfile name="dev">
<include resource="file-warn-logging.xml" />
<include resource="file-error-logging.xml" />
<root level="INFO">
<appender-ref ref="FILE-WARN" />
<appender-ref ref="FILE-ERROR" />
</root>
</springProfile>
<springProfile name="prod">
<include resource="file-warn-logging.xml" />
<include resource="file-error-logging.xml" />
<include resource="slack-error-logging.xml" />
<root level="INFO">
<appender-ref ref="FILE-WARN" />
<appender-ref ref="FILE-ERROR" />
<appender-ref ref="ASYNC-SLACK" />
</root>
</springProfile>
</configuration>
Appender 설정을 별도의 설정 파일로 분리해 가독성을 향상시켰습니다.
하지만 나머지 문제는 여전히 해결하지 못한 상황입니다.
코드 중복은 미미하지만, 동적으로 로그 설정이 불가능하다는 점이 가장 불편했습니다.
로그 설정을 진행하던 시점은 프로젝트 초기였고, 대략적인 로그 정책만 잡혀있을 뿐 세부사항은 꽤 자주 변경되었기 때문입니다.
특정 실행 환경에서 로그 정책이 변경될 경우 재배포를 해야 한다는 점이 정말 불편했습니다.
특히 큰 변경 없이 개발 서버, 운영 서버에서는 INFO 레벨의 로그를 관리했다가 관리하지 않기로 변경하는 등 단순 Appender를 활성화/비활성화 할 때 마다 CI/CD를 실행해야 했으므로 시간이 꽤나 소요되어 불편했습니다.
Appender를 분리할 때 분리한 Appender마다 특정 Profile을 가지도록 변경했습니다.
<included>
<springProfile name="console-logging">
<filter class="ch.qos.logback.classic.filter.LevelFilter">
<level>INFO</level>
<onMatch>ACCEPT</onMatch>
<onMismatch>DENY</onMismatch>
</filter>
<include resource="org/springframework/boot/logging/logback/defaults.xml" />
<include resource="org/springframework/boot/logging/logback/console-appender.xml" />
<root level="info">
<appender-ref ref="CONSOLE" />
</root>
</springProfile>
</included>
<included>
<springProfile name="file-warn-logging">
<appender name="FILE-WARN" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>${LOG_DIR}/warn/warn-${BY_DATE}.log</file>
<filter class="ch.qos.logback.classic.filter.LevelFilter">
<level>WARN</level>
<onMatch>ACCEPT</onMatch>
<onMismatch>DENY</onMismatch>
</filter>
<rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
<fileNamePattern>${LOG_DIR}/backup/warn/warn-%d{yyyy-MM-dd}.%i.log</fileNamePattern>
<maxFileSize>100MB</maxFileSize>
<maxHistory>30</maxHistory>
<totalSizeCap>3GB</totalSizeCap>
</rollingPolicy>
</appender>
<root level="info">
<appender-ref ref="FILE-WARN" />
</root>
</springProfile>
</included>
<included>
<springProfile name="file-error-logging">
<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>
<root level="info">
<appender-ref ref="FILE-ERROR" />
</root>
</springProfile>
</included>
<included>
<springProfile name="slack-error-logging">
<filter class="ch.qos.logback.classic.filter.ThresholdFilter">
<level>ERROR</level>
</filter>
<appender name="SLACK" class="com.ddang.ddang.configuration.log.SlackAppender" />
<appender name="ASYNC-SLACK" class="ch.qos.logback.classic.AsyncAppender">
<appender-ref ref="SLACK" /></appender>
<root level="info">
<appender-ref ref="ASYNC-SLACK" />
</root>
</springProfile>
</included>
<?xml version="1.0" encoding="UTF-8"?>
<configuration scan="true">
<springProperty scope="context" name="LOG_DIR" source="log.directory"/>
<timestamp key="BY_DATE" datePattern="yyyy-MM-dd"/>
<include resource="log/console-logging.xml" />
<include resource="log/file-error-logging.xml" />
<include resource="log/file-info-request-logging.xml"/>
<include resource="log/file-warn-logging.xml"/>
<include resource="log/slack-error-logging.xml"/>
</configuration>
logback-spring.xml에서는 분리한 Appender를 include하면 끝입니다.
스프링은 spring.profiles.active
을 통해 활성화시킬 Profile을 지정할 수 있습니다.
이를 통해 다음과 같이 로그 설정을 할 수 있습니다.
java -jar application.jar --spring.profiles.active=console-logging, file-warn-logging, file-error-logging, slack-error-logging
만약 특정 실행 환경에서 콘솔로 로그를 출력할 필요가 없어졌다면 해당하는 Profile만 제거해주면 됩니다.
java -jar application.jar --spring.profiles.active=file-warn-logging, file-error-logging, slack-error-logging
이를 통해 특정 환경에 따라 Appender를 추가/삭제해 동적으로 로그 설정을 제어할 수 있습니다.
즉, 설정의 변경에 의해 CI/CD를 수행할 필요 없이 애플리케이션 재실행만으로 조절이 가능합니다.
spring:
profiles:
default: local
group:
local:
- console-logging
dev:
- console-logging
- file-warn-logging
- file-error-logging
- slack-error-logging
prod:
- file-warn-logging
- file-error-logging
- slack-error-logging
java -jar application.jar --spring.profiles.active=prod
추후 로그 설정이 확정된다면 스프링 설정을 통해 Profile 끼리 조합해 간단하게 사용할 수 있습니다.