본문 바로가기

스프링 부트

Logback과 로깅 전략

Logback 이란?

  • 자바 오픈소스 로깅 프레임워크, SLF4J의 구현체
  • log4j, log4j2 등과 성능을 비교했을 때에도 logback이 더 훌륭한 성능 (괸련 링크)
  • spring-boot-starter-web 안에 spring-boot-starter-logging에 구현체 존재
  • Spring Boot의 경우 logback-spring.xml 파일을 resources 디렉토리에 만들어서 참조

Logback의 참고 순서

  1. classpath(resources디렉토리 밑)에 logback-spring.xml파일이 있으면 설정파일을 읽음
  2. logback-spring.xml파일이 없다면 .yml(.properties)파일의 설정을 읽음
  3. logback-spring.xml파일과 .yml(.properties)파일이 동시에 있으면 .yml(.properties) 설정파일을 적용 후 xml파일이 적용된다.

Spring Boot의 기본 설정

base.xml

<included>
   <include resource="org/springframework/boot/logging/logback/defaults.xml" />
   <property name="LOG_FILE" value="${LOG_FILE:-${LOG_PATH:-${LOG_TEMP:-${java.io.tmpdir:-/tmp}}}/spring.log}"/>
   <include resource="org/springframework/boot/logging/logback/console-appender.xml" />
   <include resource="org/springframework/boot/logging/logback/file-appender.xml" />
   <root level="INFO">
       <appender-ref ref="CONSOLE" />
       <appender-ref ref="FILE" />
   </root>
</included>
  • <include resource=".../defaults.xml">
    • defaults.xml에서 추가로 설정을 읽어옴
    • Console Log Pattern과 File Log Pattern의 기본값이 정의되어 있음
    • 그 외의 tomcat이나 hibernate 등의 모듈의 log level 설정이 되어 있음
  • <property name="LOG_FILE" ...>
    • ${LOG_FILE}이 없으면 ${LOG_PATH}를 호출
    • ${LOG_PATH}가 없으면 ${LOG_TEMP}를 호출
    • 이러한 형식으로 application.yml의 property를 불러오고 있음
  • <include resource=".../console-appender.xml">
    • Console Appender 설정을 읽음
  • <include resource=".../file-appender.xml">
    • File Appender 설정을 읽음

logback-spring.xml

  • appender logger 크게 두개로 구분됨
  • scanPeriod 설정을 통해 해당 기간마다 로그 파일(logback-spring.xml)이 바뀌었는지 검사하고 바뀌면 갱신

appender

appender-ref

  • ref에 선언된 name의 appender를 찾아 추가한다.

root, logger

  • 설정한 appender를 참조하여 package와 level을 설정
  • root : 전역 설정, 지역적으로 선언된 logger 설정이 있다면 해당 logger 설정이 default로 적용
  • logger : 지역 설정, additivity 값은 root 설정 상속 유무 설정

property

  • 설정파일에서 사용될 변수값 선언

 

Logback 사용법

log 객제 정의

로깅을 하고 싶은 클래스에 다음과 같이 Logger를 받아온다. 

private final Logger log = LoggerFactory.getLogger(ControllerAdvice.class);

@Slf4j 어노테이션 사용

Lombok을 사용하여 어노테이션 하나만 붙이면 간단하게 log 변수로 Logger 객체를 사용할 수 있다.

 

Loging Level

TRACE < DEBUG < INFO < WARN < ERROR
  • ERROR : 요청을 처리하는 중 오류가 발생한 경우
  • WARN : 처리 가능한 문제, 향후 시스템 에러의 원인이 될 수 있는 경고성 메시지를 나타냄
  • INFO : 상태변경과 같은 정보성 로그 표시
  • DEBUG : 프로그램을 디버깅하기 위한 정보를 표시
  • TRACE : Debug보다 훨씬 상세한 정보를 나타냄

위의 순서대로 높은 레벨을 가지며, 출력 레벨의 설정에 따라 설정 레벨 이상의 로그를 출력 한다.

놀토의 Logging 전략

현재 놀토 프로젝트의 logback-spring.xml을 좀 정리해서 올렸다. 
완벽한 로깅 전략이 아닌, 우리의 기준으로 세운 로깅 전략이니 참고하시길!

<?xml version="1.0" encoding="UTF-8"?>
<configuration>

    <springProperty name="SLACK_WEBHOOK_URI" source="logging.slack.webhook-uri"/>
    <appender name="LOCAL_CONSOLE_POLICY" class="ch.qos.logback.core.ConsoleAppender">
        <layout class="ch.qos.logback.classic.PatternLayout">
            <Pattern>%d{HH:mm:ss.SSS} %highlight([%-5level]) [%thread] %cyan([%logger{36}]) - %m%n
            </Pattern>
        </layout>
    </appender>

    <appender name="SQL_FILE_POLICY" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <file>${LOG_PATH}/logs/nolto-sql.log</file>
        <rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
            <fileNamePattern>${LOG_PATH}/logs/dateLog/%d{yyyy_MM_dd}_%i.nolto-sql.log
            </fileNamePattern>
            <maxFileSize>50MB</maxFileSize>
            <maxHistory>14</maxHistory>
            <totalSizeCap>1GB</totalSizeCap>
        </rollingPolicy>
        <encoder>
            <charset>utf8</charset>
            <Pattern>%d{HH:mm:ss.SSS} [%-5level] [%thread] [%logger{36}] - %m%n</Pattern>
        </encoder>
    </appender>


    <appender name="INFO_FILE_POLICY" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <filter class="ch.qos.logback.classic.filter.ThresholdFilter">
            <level>INFO</level>
        </filter>
        <file>${LOG_PATH}/logs/nolto-info.log</file>
        <encoder>
            <Pattern>%d{HH:mm:ss.SSS} [%-5level] [%thread] [%logger{36}] - %m%n</Pattern>
        </encoder>
        <rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
            <fileNamePattern>${LOG_PATH}/logs/dateLog/%d{yyyy_MM_dd}_%i.nolto-info.log
            </fileNamePattern>
            <maxFileSize>50MB</maxFileSize>
            <maxHistory>14</maxHistory>
            <totalSizeCap>1GB</totalSizeCap>
        </rollingPolicy>
    </appender>

    <appender name="ERROR_FILE_POLICY" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <filter class="ch.qos.logback.classic.filter.ThresholdFilter">
            <level>ERROR</level>
        </filter>
        <file>${LOG_PATH}/logs/nolto-error.log</file>
        <encoder>
            <Pattern>%d{HH:mm:ss.SSS} [%-5level] [%thread] [%logger{36}] - %m%n</Pattern>
        </encoder>
        <rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
            <fileNamePattern>${LOG_PATH}/logs/dateLog/%d{yyyy_MM_dd}_%i.nolto-error.log
            </fileNamePattern>
            <maxFileSize>50MB</maxFileSize>
            <maxHistory>14</maxHistory>
            <totalSizeCap>1GB</totalSizeCap>
        </rollingPolicy>
    </appender>

    <appender name="SLACK" class="com.github.maricn.logback.SlackAppender">
        <webhookUri>${SLACK_WEBHOOK_URI}</webhookUri>
        <layout class="ch.qos.logback.classic.PatternLayout">
            <pattern>[DEV] %-4relative [%thread] %-5level %class - %msg%n</pattern>
        </layout>
        <colorCoding>true</colorCoding>
    </appender>
    <appender name="ASYNC_SLACK" class="ch.qos.logback.classic.AsyncAppender">
        <appender-ref ref="SLACK"/>
        <filter class="ch.qos.logback.classic.filter.ThresholdFilter">
            <level>ERROR</level>
        </filter>
    </appender>

    <appender name="PROD-SLACK" class="com.github.maricn.logback.SlackAppender">
        <webhookUri>${SLACK_WEBHOOK_URI}</webhookUri>
        <layout class="ch.qos.logback.classic.PatternLayout">
            <pattern>[PROD] %-4relative [%thread] %-5level %class - %msg%n</pattern>
        </layout>
        <colorCoding>true</colorCoding>
    </appender>
    <appender name="PROD_ASYNC_SLACK" class="ch.qos.logback.classic.AsyncAppender">
        <appender-ref ref="PROD-SLACK"/>
        <filter class="ch.qos.logback.classic.filter.ThresholdFilter">
            <level>ERROR</level>
        </filter>
    </appender>

    <root level="INFO">
        <springProfile name="local">
            <appender-ref ref="LOCAL_CONSOLE_POLICY"/>
        </springProfile>

        <springProfile name="dev">
            <appender-ref ref="ERROR_FILE_POLICY"/>
            <appender-ref ref="INFO_FILE_POLICY"/>
            <appender-ref ref="ASYNC_SLACK"/>
        </springProfile>

        <springProfile name="prod">
            <appender-ref ref="ERROR_FILE_POLICY"/>
            <appender-ref ref="INFO_FILE_POLICY"/>
            <appender-ref ref="PROD_ASYNC_SLACK"/>
        </springProfile>
        
        <springProfile name="file-sql-logging">
            <logger name="org.hibernate.SQL" level="TRACE">
                <appender-ref ref="SQL_FILE_POLICY"/>
            </logger>
            <logger name="org.hibernate.type.descriptor" level="TRACE">
                <appender-ref ref="SQL_FILE_POLICY"/>
            </logger>
        </springProfile>
    </root>
</configuration>

 

  • LOG_PATH: 해당 변수의 값은 dev와 prod 환경 설정 yml에서 가지고 있음
  • SLACK_WEBHOOK_URI: 외부에 노출되면 계속해서 URL이 변하기에 각 환경의 yml에 해당 정보를 숨김

여러 전략의 appender를 만들어 두고 profile 설정별로 이를 조합하여 사용하도록 하였다. 

  • local: info 레벨의 console 로깅
  • dev: info 레벨의 file 로깅, error 레벨의 file, slack 로깅, trace 레벨의 sql 쿼리를 file 로깅
  • prod: info 레벨의 file 로깅, error 레벨의 file, slack 로깅

 

➕ Slack으로 알림받기

 

참고 자료