
패키지, 메서드 별로 세부적인 설정
파일로 보관
출력 패턴을 커스터마이징
LogBack 을 사용해서 콘솔 출력 + 파일 저장을 할 수 있습니다. 감사하게도 관련 글이 많아서 쉽게 익힐 수 있었습니다.
먼저 resources/logbakc-spring.xml 파일을 생성합니다.
logback-spring.xml 전체 코드입니다.
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<conversionRule conversionWord="clr" converterClass="org.springframework.boot.logging.logback.ColorConverter"/>
<!-- 날짜 출력 패턴 설정 -->
<timestamp key="BY_DATE" datePattern="yyyy-MM-dd"/>
<!-- 절대 위치 설정 -->
<property name="LOG_ABSOLUTE_PATH" value="./logs"/>
<!-- 백업 위치 설정 -->
<property name="LOG_BACKUP_PATH" value="./logs/backup"/>
<!-- 콘솔 출력 패턴 설정 -->
<property name="CONSOLE_LOG_PATTERN"
value="[%d{yyyy-MM-dd HH:mm:ss}:%-3relative] %clr(%-5level) %clr(${PID:-}){magenta} %clr(---){faint} %clr([%15.15thread]){faint} %clr(%-40.40logger{36}){cyan} %clr(:){faint} %yellow(%msg%n)"/>
<!-- 파일 패턴 설정 -->
<property name="FILE_LOG_PATTERN"
value="[%d{yyyy-MM-dd HH:mm:ss}:%-3relative] %-5level ${PID:-} --- [%15.15thread] %-40.40logger{36} : %msg%n"/>
<!--
<appender> 를 설정하는 파일을 나눠서 작성하는 편이 보기가 깔끔합니다.
콘솔에 어떻게 출력할지, 파일로는 어떻게 저장할지를 상세하게 설정할 수 있습니다.
RollingFileAppender, SizeAndTimeBasedRollingPolicy 등을 이용해서 유동적으로 로그를 저장할 수 있습니다.
보관하는 날짜, 파일이 저장될 경로, 파일의 이름, 파일의 최대 사이즈 등을 설정합니다.
ThresholdFilter 를 이용해서 특정 로그 레벨내에 해당하는 모든 로그에 대해 적용시킵니다.
LevelFilter 를 통해서 정확하게 일치하는 로그만을 필터링 할 수 있습니다.
-->
<!-- TRACE 레벨 콘솔 출력 설정 -->
<include resource="log-config/console-trace-appender.xml"/>
<!-- DEBUG 레벨 콘솔 출력 설정 -->
<include resource="log-config/console-debug-appender.xml"/>
<!-- INFO 레벨 콘솔 출력 설정 -->
<include resource="log-config/console-info-appender.xml"/>
<!-- ERROR 레벨 콘솔 출력 설정 -->
<include resource="log-config/console-error-appender.xml"/>
<!-- DEBUG 레벨 로그 파일 저장 설정 -->
<include resource="log-config/file-debug-appender.xml"/>
<!-- ERROR 레벨 로그 파일 저장 설정 -->
<include resource="log-config/file-error-appender.xml"/>
<!-- INFO 레벨 로그 파일 저장 설정 -->
<include resource="log-config/file-info-appender.xml"/>
<!--
프로파일 별로 설정을 다르게 적용할 수 있습니다. 필요 시 yaml 파일에서 값을 호출할 수도 있습니다. (시도해봤으나 자꾸 오류가 발생해서 패스했습니다.)
root 레벨을 설정할 수 있고, logger 를 사용해서 패키지를 특정할 수 있습니다. root, logger 내에서 어떤 appender 지정해야 합니다.
logger 내에서 name 안에 입력하는 값은 패키지 경로입니다. 기능의 중요도, 호출되는 횟수에 따라서 출력되거나 저장되는 로그를 튜닝할 수 있습니다.
-->
<!-- profile: local 일 경우-->
<springProfile name="local">
<!--
local 개발 단계인 경우, 모든 로그를 확인하고자 trace 레벨로 콘솔을 확인합니다.
-->
<logger name="com.example.oauth2backend" level="trace">
<appender-ref ref="CONSOLE-TRACE"/>
</logger>
<root level="info">
<appender-ref ref="CONSOLE-INFO"/>
</root>
</springProfile>
<!-- profile: dev 일 경우 -->
<springProfile name="dev">
<!--
dev 단계일 경우 debug 로그까지 콘솔에 출력합니다.
로그 파일은 error 만 남깁니다.
-->
<logger name="com.example.oauth2backend" level="info">
<appender-ref ref="FILE-ERROR"/>
<appender-ref ref="CONSOLE-DEBUG"/>
</logger>
<logger name="org.springframework.web" level="info">
<appender-ref ref="CONSOLE-INFO"/>
</logger>
<root level="info">
<appender-ref ref="CONSOLE-INFO"/>
</root>
</springProfile>
<!-- profile: prod 일 경우 -->
<springProfile name="prod">
<!--
prod 단계인 경우, 로그 파일은 error 만 남깁니다.
콘솔에는 info, warn, error 메시지를 출력합니다.
-->
<logger name="com.example.oauth2backend | org.springframework.web" level="info">
<appender-ref ref="FILE-ERROR"/>
<appender-ref ref="CONSOLE-INFO"/>
</logger>
<root level="info">
<appender-ref ref="CONSOLE-INFO"/>
</root>
</springProfile>
</configuration>
한번에 보기에는 너무 많으니 잘게 쪼개서 살펴보겠습니다.
<appender></appender> 를 파일을 분리해서 관리할 수 있습니다.

<include> 통해서 호출할 수 있습니다.
<!-- TRACE 레벨 콘솔 출력 설정 -->
<include resource="log-config/console-trace-appender.xml"/>
<!-- DEBUG 레벨 콘솔 출력 설정 -->
<include resource="log-config/console-debug-appender.xml"/>
<!-- INFO 레벨 콘솔 출력 설정 -->
<include resource="log-config/console-info-appender.xml"/>
<!-- ERROR 레벨 콘솔 출력 설정 -->
<include resource="log-config/console-error-appender.xml"/>
<!-- DEBUG 레벨 로그 파일 저장 설정 -->
<include resource="log-config/file-debug-appender.xml"/>
<!-- ERROR 레벨 로그 파일 저장 설정 -->
<include resource="log-config/file-error-appender.xml"/>
<!-- INFO 레벨 로그 파일 저장 설정 -->
<include resource="log-config/file-info-appender.xml"/>
console-debug-appender.xml
<included>
<appender name="CONSOLE-DEBUG" class="ch.qos.logback.core.ConsoleAppender">
<layout class="ch.qos.logback.classic.PatternLayout">
<pattern>${CONSOLE_LOG_PATTERN}</pattern>
</layout>
<filter class="ch.qos.logback.classic.filter.ThresholdFilter">
<level>DEBUG</level>
</filter>
</appender>
</included>
file-error-appender.xml
<included>
<appender name="FILE-ERROR" class="ch.qos.logback.core.rolling.RollingFileAppender">
<!-- 기록할 파일 위치 설정 -->
<file>${LOG_ABSOLUTE_PATH}/error/error-${BY_DATE}.log</file>
<filter class="ch.qos.logback.classic.filter.LevelFilter">
<level>ERROR</level>
<onMatch>ACCEPT</onMatch>
<onMismacth>DENY</onMismacth>
</filter>
<encoder>
<pattern>${FILE_LOG_PATTERN}</pattern>
</encoder>
<rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
<fileNamePattern>${LOG_BACKUP_PATH}/error/error-%d{yyyy-MM-dd}.%i.log</fileNamePattern>
<maxFileSize>200MB</maxFileSize>
<maxHistory>30</maxHistory>
</rollingPolicy>
</appender>
</included>
ThresholdFilter 를 통해서 범위로 지정했습니다. 정확한 로그 레벨에 을 지정하기 위해서는 LevelFilter 를 사용합니다.
각 profile 별로 적용하는 방법은 아래와 같습니다.
<!-- profile: local 일 경우-->
<springProfile name="local">
<!--
local 개발 단계인 경우, 모든 로그를 확인하고자 trace 레벨로 콘솔을 확인합니다.
-->
<logger name="com.example.oauth2backend" level="trace">
<appender-ref ref="CONSOLE-TRACE"/>
</logger>
<root level="info">
<appender-ref ref="CONSOLE-INFO"/>
</root>
</springProfile>
<!-- profile: dev 일 경우 -->
<springProfile name="dev">
<!--
dev 단계일 경우 debug 로그까지 콘솔에 출력합니다.
로그 파일은 error 만 남깁니다.
-->
<logger name="com.example.oauth2backend" level="info">
<appender-ref ref="FILE-ERROR"/>
<appender-ref ref="CONSOLE-DEBUG"/>
</logger>
<logger name="org.springframework.web" level="info">
<appender-ref ref="CONSOLE-INFO"/>
</logger>
<root level="info">
<appender-ref ref="CONSOLE-INFO"/>
</root>
</springProfile>
<!-- profile: prod 일 경우 -->
<springProfile name="prod">
<!--
prod 단계인 경우, 로그 파일은 error 만 남깁니다.
콘솔에는 info, warn, error 메시지를 출력합니다.
-->
<logger name="com.example.oauth2backend | org.springframework.web" level="info">
<appender-ref ref="FILE-ERROR"/>
<appender-ref ref="CONSOLE-INFO"/>
</logger>
<root level="info">
<appender-ref ref="CONSOLE-INFO"/>
</root>
</springProfile>
이게 다가 아닙니다. 결국 logback 도 복잡한 계층을 가지고 있는 라이브러리 입니다. 구현체를 직접 작성하고, filter 를 좀 더 세부적으로 커스터마이징 할 수 있습니다.
모든 로그를 전부 다 출력하고, 제외하는게 좋을지
혹은 모든 로그를 지워버리고 원하는 로그만 활성화 시키는게 좋을지
프로젝트의 진행 단계에 따라서 최적의 선택지 달라질 것 같습니다. 구글링 한 결과, 특정한 상황에서는 로그 기록을 막을 수 있습니다. (출처)
// Filter<ILoggingEvent>를 상속받은 LogbackFilter 클래스 생성
public class LogbackFilter extends Filter<ILoggingEvent> {
@Override
public FilterReply decide(ILoggingEvent event) {
if(event.getMessage().contains("filter")) { // filter가 들어간 로그는 출력 안함
return FilterReply.DENY;
} else {
return FilterReply.ACCEPT;
}
}
}
구현체를 appender 에 등록해주면 됩니다.
// logback.xml에서 filter 클래스 추가
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<filter class="com.springboot.myapp.LogbackFilter"/>
<encoder>
<pattern>%d{yyyyMMdd HH:mm:ss.SSS} [%thread] %-3level %logger{5} - %msg %n</pattern>
</encoder>
</appender>
또한 엔티티가 엄청나게 많아진다면 콘솔에 쿼리문이 출력되는 것 조차 보는 것이 고역일 수 있습니다. 해당 경우에도 간단한 해결책이 있습니다.
/* filter */ 를 추가해줍니다.
// 출력하고 싶지 않은 쿼리에 주석 filter 추가
<select id="filter" resultType="com.springboot.myapp.filter" parameterType="hashMap">
/* filter */
SELECT
data
FROM table
</select>
결국 로그를 남길 appender 와 적용할 패키지를 지정해주면 됩니다. 물론 yml 파일 내에서도 패키지 별로 로그 레벨을 지정할 수 있습니다. 로그 파일의 중요성은 다들 알고 계실 거라고 생각합니다.
전에 저의 어처구니 없는 실수로 json 파싱 관련 에러가 발생했는데, 개발자들은 모르고 있었고 고객이 특정 api 콜을 했을 때 오류가 발생했습니다. 구체적으로 로그를 남겨놓지 않았다면 문제 해결에 며칠이 걸렸을 지도 모릅니다. 문제가 됐던 클래스 내에 로그를 잘 남겨놓아서 문제를 5분만에 파악할 수 있었습니다.
또한 이용자가 늘어나니 로그 파일이 쌓이는 속도가 무섭게 증가했습니다. 로그 파일의 용량도 엄청 커지고, 쓸데없는 사항이 모두 로그파일에 기록 됐던 상황이어서 문제 발생 시에 찾기가 굉장히 힘들었던 적도 있었습니다.
당시 거의 모든 로그를 파일에 저장하는 상황이어서 좀 더 세부적으로 로그 설정을 했다면 운영 단계에서 훨씬 수월하게 문제를 해결할 수 있었을 것 같습니다.
특히, api 콜을 처리하는 패키지, db 와의 연결이 잦은 패키지, 생성, 업데이트가 빈번하게 일어나는 패키지, 반대로 거의 호출되지 않는 패키지 등 중요도에 따라 차등 순위로 로그를 기록할 수 있는 방법을 알게 되어 유익한 시간이었습니다.