Spring Profile 조합을 활용한 Logback 설정

appti·2024년 2월 22일
1

구현

목록 보기
1/2

서론

현재 프로젝트의 환경과 그에 따라 필요한 로그 설정은 다음과 같습니다.

  • 로컬
    • 단순 확인을 위한 콘솔 출력
  • 개발 서버
    • 별도 로그 수집 필요
    • INFO 레벨의 로그는 관리하지 않음
  • 운영 서버
    • 별도 로그 수집 필요
    • 심각한 문제 발생 시 로그 내용을 개발자에게 전달
    • INFO 레벨의 로그는 관리하지 않음

프로젝트에서는 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>

이 경우 다음과 같은 문제점이 있습니다.

  • 코드 중복
    • 가독성 하락
  • 동적으로 로그 설정 불가능
    • 설정 파일에 스프링 Profile을 명시적으로 표시했기 때문
    • 환경에 따라 구성이 변경될 경우 재배포 필요

Appender 분리

위에서 발생한 문제 중 가장 시급한 것은 코드 중복으로 인해 가독성이 떨어진다는 점입니다.

이를 고려해 다음과 같이 Appender를 분리했습니다.

console-logging.xml

<included>
    <include resource="org/springframework/boot/logging/logback/defaults.xml" />
    <include resource="org/springframework/boot/logging/logback/console-appender.xml" />
</included>

file-warn-logging.xml

<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>

file-error-logging.xml

<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>

slack-error-logging.xml

<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>

logback-spring.xml

<?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 설정을 별도의 설정 파일로 분리해 가독성을 향상시켰습니다.
하지만 나머지 문제는 여전히 해결하지 못한 상황입니다.

  • 코드 중복
    • 외부로 분리한 Appender 설정을 include 하는 코드 중복
  • 동적으로 로그 설정 불가능

코드 중복은 미미하지만, 동적으로 로그 설정이 불가능하다는 점이 가장 불편했습니다.
로그 설정을 진행하던 시점은 프로젝트 초기였고, 대략적인 로그 정책만 잡혀있을 뿐 세부사항은 꽤 자주 변경되었기 때문입니다.

특정 실행 환경에서 로그 정책이 변경될 경우 재배포를 해야 한다는 점이 정말 불편했습니다.

특히 큰 변경 없이 개발 서버, 운영 서버에서는 INFO 레벨의 로그를 관리했다가 관리하지 않기로 변경하는 등 단순 Appender를 활성화/비활성화 할 때 마다 CI/CD를 실행해야 했으므로 시간이 꽤나 소요되어 불편했습니다.

스프링 Profile을 활용한 로그 설정 조합

Appender를 분리할 때 분리한 Appender마다 특정 Profile을 가지도록 변경했습니다.

console-logging.xml

<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>

file-warn-logging.xml

<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>

file-error-logging.xml

<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>

slack-error-logging.xml

<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>

logback-spring.xml

<?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 끼리 조합해 간단하게 사용할 수 있습니다.

profile
안녕하세요

0개의 댓글