[Logback] - Spring profile 로 탈부착이 가능한 file-logger 와 discord-logger 를 만들어보자

청주는사과아님·2025년 7월 8일
0

side-story

목록 보기
7/7
post-thumbnail

이전 프로젝트를 진행하면서 Logback 으로 file-appender 를 구성한 경험이 있습니다.
하지만 Logback 에 익숙치 않아 코드가 매우 더러웠고, 무엇보다 Spring profile 에 따른 appender 변경이 불가능한 코드였습니다.

그래서 이번 프로젝트에선 "반드시 깔끔하고 우아한 Logback 구성을 만들어 내겠다" 라는 다짐이 있었습니다.

이번 포스팅에서는 이것이 어떻게 가능한지, 그리고 이를 활용한 file-appenderdiscord-appender 를 어떻게 구성하는지 공유하고자 합니다.


📑 스프링 문서 를 읽어보니

I. Spring 변수의 이전

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>

II. Profile-specific Configuration : 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

I. 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_PATHproperty 를 구성했고, rolling policy 의 fileNamePattern 에 활용됨을 알 수 있습니다.

더불어 <file> tag 를 생략했는데, 이는 모든 로그가 rolling 되며 저장하기 위해 생략했습니다.


II. 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'
}

I. 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 로 정의된 값을 이용하는 것을 볼 수 있습니다.


II. 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.xmlapplication.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>

profile
나 같은게... 취준?!

0개의 댓글