Junhyunny's Devlogs

글 검색

기술 글 제목, 본문, 카테고리에서 검색합니다.

    이전 화면으로

    스프링 부트 Logback 설정

    • java
    • spring-boot
    이 글의 목차

    0. 들어가면서

    스프링 부트(Spring Boot) 프레임워크는 기본(default) 로거로 logback을 사용하고 있다. 다양한 로깅 프레임워크가 존재하지만, 스프링에서 기본으로 채택한 logback의 사용 방법을 이번 포스트에서 먼저 정리해보겠다.

    1. Logback Architecture

    logback은 세 개의 모듈로 나뉜다.

    • logback-core
      • 클래식(classic), 액세스(access) 두 모듈의 기초가 되는 범용 모듈이다.
    • logback-classic
      • 코어(core)를 확장(extends), SLF4J API를 구현(implements)한 모듈이다.
      • SLF4J API는 클래식 모듈을 통해 logback 로깅 프레임워크 기능을 호출한다.
    • logback-access
      • 제티(Jetty) 혹은 톰캣(Tomcat) 같은 서블릿 컨테이너와 통합되어 HTTP 액세스 로그 기능을 제공한다.

    아래 주요 세 개 클래스들은 함께 동작하면서 개발자들이 결정한 로그 타입, 레벨, 메시지 포맷 등에 맞게 로그를 출력한다.

    • Logger
      • logback-classic 모듈에 속한다.
      • 로깅 이벤트를 캡처하여 어펜더(Appender)에게 전달한다.
    • Appender
      • logback-core 모듈에 속한다.
      • 로그를 어디에 기록할지 결정하며 포맷에 맞는 메시지 생성은 레이아웃(Layout)에게 의존한다.
      • ConsoleAppender, OutputStreamAppender, FileAppender, RollingFileAppender, SocketAppender, SSLSocketAppender, SMTPAppender, DBAppender, SyslogAppender, SiftingAppender, AsyncAppender 등 다양한 어펜더들을 제공한다.
      • https://logback.qos.ch/manual/appenders.html
    • Layout
      • logback-core 모듈에 속한다.
      • 어펜더는 레이아웃에 의존하여 로깅 이벤트를 포맷에 맞는 문자열로 변환하고 기록한다.
      • logback 0.9.19 버전 이후부터 FileAppender 및 하위 클래스는 인코더(Encoder)를 사용하며 더 이상 레이아웃을 사용하지 않는다고 한다.
      • https://logback.qos.ch/manual/encoders.html

    2. Log Level and Log Inheritance

    2.1. Basic Selection Rule

    logback은 5가지 로그 레벨을 가지고 있다. 높은 로그 레벨 값을 가질수록 중요도가 높다.

    • ERROR
      • 애플리케이션에 치명적일 수 있는 에러 이벤트이다.
      • 예상하지 못한 문제나 예외 등을 출력한다.
      • 중요도 1위이다.
    • WARN
      • 잠재적으로 위험할 수 있는 상황을 나타내는 메시지이다.
      • 예상 가능한 예외 처리 등을 출력한다.
      • 중요도 2위이다.
    • INFO
      • 애플리케이션의 전반적인 흐름을 나타내는 정보성 메시지이다.
      • 운영 중 애플리케이션의 중요한 비즈니스 프로세스를 로깅한다.
      • 중요도 3위이다.
    • DEBUG
      • 중요도가 낮은 정보성 메시지이다.
      • 개발 단계에서 디버깅을 위한 로깅을 의미한다.
      • 중요도 4위이다.
    • TRACE
      • 중요도가 매우 낮은 정보성 메시지이다.
      • 모든 레벨에 대한 로깅이 추적되므로 개발 단계에서만 사용한다.
      • 중요도 5위이다.
    package ch.qos.logback.classic;
    
    // import ...
    
    public final class Level implements java.io.Serializable {
    
        public static final int OFF_INT = Integer.MAX_VALUE;
        public static final int ERROR_INT = 40000;
        public static final int WARN_INT = 30000;
        public static final int INFO_INT = 20000;
        public static final int DEBUG_INT = 10000;
        public static final int TRACE_INT = 5000;
        public static final int ALL_INT = Integer.MIN_VALUE;
    
        // ... other codes
    }

    로거(Logger)는 적용된 로그 레벨에 따라 요청받은 로깅 이벤트를 출력할지 여부를 선택한다. 적용된 로그 레벨보다 우선순위가 높거나 같은 경우에만 로그를 출력한다.

    A log request of level p issued to a logger having an effective level q, is enabled if p >= q.

    • 적용된 로그 레벨이 TRACE인 경우
      • 모든 로그 요청이 출력된다.
    • 적용된 로그 레벨이 DEBUG인 경우
      • TRACE를 제외한 로그 요청이 출력된다.
    • 적용된 로그 레벨이 INFO인 경우
      • TRACE, DEBUG를 제외한 로그 요청이 출력된다.
    • 적용된 로그 레벨이 WARN인 경우
      • WARN, ERROR 로그 요청만 출력된다.
    • 적용된 로그 레벨이 ERROR인 경우
      • ERROR 로그 요청만 출력된다.
    Level of Request \ Effective Level TRACE DEBUG INFO WARN ERROR OFF
    TRACE YES NO NO NO NO NO
    DEBUG YES YES NO NO NO NO
    INFO YES YES YES NO NO NO
    WARN YES YES YES YES NO NO
    ERROR YES YES YES YES YES NO

    2.2. Level Inheritance

    로거는 로거 컨텍스트(logger context)라는 고유한 공간을 가지며 이름을 통해 상속 계층이 결정된다. 간단한 예시로 “com.foo” 이름을 가진 로거는 “com.foo.Bar”라는 이름을 가진 로거의 부모이다. 상속 계층 최상단에는 루트(root) 로거가 존재하며 루트 로거를 통해 기본적인 로그 레벨을 부여할 수 있다. 로그 레벨은 로거의 상속 계층을 따라 하위로 적용된다.

    2.2.1. Example 1

    • 루트 로거의 로그 레벨이 debug이다.
    • 하위 로거들의 로그 레벨이 지정되어 있지 않으므로 루트를 따라 debug 레벨이 적용된다.
    Logger Name Assigned Level Effective Level
    root DEBUG DEBUG
    X none DEBUG
    X.Y none DEBUG
    X.Y.Z none DEBUG

    2.2.2. Example 2

    • 루트 로거의 로그 레벨이 error이다.
    • X 이름을 가진 로거의 로그 레벨이 info로 지정된다.
      • 해당 로거에 적용되는 로그 레벨은 info이다.
    • X.Y 이름을 가진 로거의 로그 레벨이 debug로 지정된다.
      • 해당 로거에 적용되는 로그 레벨은 debug이다.
    • X.Y.Z 이름을 가진 로거의 로그 레벨이 warn으로 지정된다.
      • 해당 로거에 적용되는 로그 레벨은 warn이다.
    Logger Name Assigned Level Effective Level
    root ERROR ERROR
    X INFO INFO
    X.Y DEBUG DEBUG
    X.Y.Z WARN WARN

    2.2.3. Example 3

    • 루트 로거의 로그 레벨이 debug이다.
    • X 이름을 가진 로거의 로그 레벨이 info로 지정된다.
      • 해당 로거에 적용되는 로그 레벨은 info이다.
    • X.Y 이름을 가진 로거는 로그 레벨이 지정되어 있지 않으므로 부모를 따라 info 레벨이 적용된다.
    • X.Y.Z 이름을 가진 로거의 로그 레벨이 error으로 지정된다.
      • 해당 로거에 적용되는 로그 레벨은 error이다.
    Logger Name Assigned Level Effective Level
    root DEBUG DEBUG
    X INFO INFO
    X.Y none INFO
    X.Y.Z ERROR ERROR

    3. Practice

    간단한 요구 사항을 만족하는 로깅 환경을 구성해보겠다.

    • 로컬 환경
      • DEBUG 로그 레벨을 기본으로 지정한다.
      • 모든 로그는 콘솔로 출력한다.
    • 개발 환경
      • DEBUG 로그 레벨을 기본으로 지정한다.
      • ERROR 레벨 출력은 별도 파일로 저장한다.
      • 모든 로그는 콘솔로 출력한다.
    • 운영 환경
      • INFO 로그 레벨을 기본으로 지정한다.
      • ERROR 레벨 출력은 별도 파일로 저장한다.
      • 모든 로그는 콘솔과 파일로 출력한다.

    실습 프로젝트 구성은 다음과 같다.

    ./
    ├── HELP.md
    ├── build.gradle
    ├── gradle
    │   └── wrapper
    │       ├── gradle-wrapper.jar
    │       └── gradle-wrapper.properties
    ├── gradlew
    ├── gradlew.bat
    ├── settings.gradle
    └── src
        ├── main
        │   ├── java
        │   │   └── action
        │   │       └── in
        │   │           └── blog
        │   │               └── ActionInBlogApplication.java
        │   └── resources
        │       ├── appender
        │       │   ├── console-appender.xml
        │       │   ├── error-rolling-file-appender.xml
        │       │   └── rolling-file-appender.xml
        │       ├── application.yml
        │       ├── logback-spring.xml
        │       ├── static
        │       └── templates
        └── test
            └── java
                └── action
                    └── in
                        └── blog
                            └── ActionInBlogApplicationTests.java

    3.1. application.yml

    • 각 환경별로 설정을 분리한다.
      • 실습 편의상 하나의 파일에 모든 설정을 추가하였다.
    • 현재 활성화된 환경은 local이다.
    • 각 환경별로 로그를 저장하는 경로를 지정한다.
      • 개발 환경은 ./dev-logs이다.
      • 운영 환경은 ./prod-logs이다.
    spring:
      profiles:
        active: local
    ---
    spring:
      config:
        activate:
          on-profile: dev
    log:
      path: ./dev-logs
    ---
    spring:
      config:
        activate:
          on-profile: prod
    log:
      path: ./prod-logs

    3.2. console-appender.xml

    • 특정 패턴의 로그를 콘솔에 출력한다.
    <included>
        <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
            <encoder>
                <pattern>[%d{yyyy-MM-dd HH:mm:ss}:%-3relative][%thread] %-5level %logger{36} - %msg%n</pattern>
            </encoder>
        </appender>
    </included>

    3.3. error-rolling-file-appender.xml

    • 특정 패턴의 로그를 파일에 출력한다.
    • LevelFilter 클래스를 사용해 ERROR 레벨 로그만 기록한다.
    • 파일 사이즈가 10KB가 넘어가면 새로운 파일로 저장한다.
      • 실습을 위해 용량을 작게 설정하였다.
      • 10KB가 넘어가면 history-%d-%i.log 파일로 아카이브(archive)된다.
    • 7일 동안 파일을 보관하며 오래된 파일은 지워진다.
    <included>
        <appender name="ERROR_ROLLING_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
            <file>${LOG_PATH}/errors/real-time-${LOG_DATE}.log</file>
            <filter class="ch.qos.logback.classic.filter.LevelFilter">
                <level>ERROR</level>
                <onMatch>ACCEPT</onMatch>
                <onMismatch>DENY</onMismatch>
            </filter>
            <encoder>
                <pattern>[%d{yyyy-MM-dd HH:mm:ss}:%-3relative][%thread] %-5level %logger{36} - %msg%n</pattern>
            </encoder>
            <rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
                <fileNamePattern>${LOG_PATH}/errors/history-%d-%i.log</fileNamePattern>
                <maxFileSize>10KB</maxFileSize>
                <maxHistory>7</maxHistory>
            </rollingPolicy>
        </appender>
    </included>

    3.4. rolling-file-appender.xml

    • 특정 패턴의 로그를 파일에 출력한다.
    • 파일 사이즈가 10KB가 넘어가면 새로운 파일로 저장한다.
      • 실습을 위해 용량을 작게 설정하였다.
      • 10KB가 넘어가면 history-%d-%i.log 파일로 아카이브된다.
    • 7일 동안 파일을 보관하며 오래된 파일은 지워진다.
    <included>
        <appender name="ROLLING_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
            <file>${LOG_PATH}/logs/real-time-${LOG_DATE}.log</file>
            <encoder>
                <pattern>[%d{yyyy-MM-dd HH:mm:ss}:%-3relative][%thread] %-5level %logger{36} - %msg%n</pattern>
            </encoder>
            <rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
                <fileNamePattern>${LOG_PATH}/logs/history-%d-%i.log</fileNamePattern>
                <maxFileSize>10KB</maxFileSize>
                <maxHistory>7</maxHistory>
            </rollingPolicy>
        </appender>
    </included>

    3.5. logback-spring.xml

    • 스프링 부트 프레임워크에서 로그 설정을 초기화할 때 사용하는 파일이다.
      • logback은 xml, groovy 확장자를 가지는 설정 파일을 사용한다.
      • -spring 접미사가 붙은 파일을 사용하는 것이 좋다.
        • logback-spring.xml, logback-spring.groovy
      • 표준 로그 설정 파일을 사용하는 경우 완전한 초기화를 할 수 없다.
        • logback.xml, logback.groovy
    • include 태그를 통해 필요한 어펜더들을 추가한다.
    • springProperty 태그를 통해 필요한 설정은 application.yml 파일에서 주입받는다.
    • springProfile 태그를 통해 실행 환경별로 다른 어펜더들을 적용한다.
    <?xml version="1.0" encoding="UTF-8"?>
    <configuration>
    
        <timestamp key="LOG_DATE" datePattern="yyyy-MM-dd"/>
        <springProperty name="LOG_PATH" source="log.path" defaultValue="./local-logs"/>
        <include resource="appender/console-appender.xml" />
        <include resource="appender/error-rolling-file-appender.xml" />
        <include resource="appender/rolling-file-appender.xml" />
    
        <springProfile name="local">
            <root level="DEBUG">
                <appender-ref ref="STDOUT"/>
            </root>
        </springProfile>
    
        <springProfile name="dev">
            <root level="DEBUG">
                <appender-ref ref="STDOUT"/>
                <appender-ref ref="ERROR_ROLLING_FILE"/>
            </root>
        </springProfile>
    
        <springProfile name="prod">
            <root level="INFO">
                <appender-ref ref="STDOUT"/>
                <appender-ref ref="ROLLING_FILE"/>
                <appender-ref ref="ERROR_ROLLING_FILE"/>
            </root>
        </springProfile>
    </configuration>

    3.6. ActionInBlogApplication Class

    • 애플리케이션을 실행하면 각 로그 레벨별로 문자열을 출력한다.
    package action.in.blog;
    
    import lombok.extern.slf4j.Slf4j;
    import org.springframework.boot.CommandLineRunner;
    import org.springframework.boot.SpringApplication;
    import org.springframework.boot.autoconfigure.SpringBootApplication;
    
    import java.util.stream.IntStream;
    
    @Slf4j
    @SpringBootApplication
    public class ActionInBlogApplication implements CommandLineRunner {
    
        public static void main(String[] args) {
            SpringApplication.run(ActionInBlogApplication.class, args);
        }
    
        private static void sleep(long millis) {
            try {
                Thread.sleep(millis);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }
    
        @Override
        public void run(String... args) throws Exception {
            IntStream.range(0, Integer.MAX_VALUE)
                    .forEach(number -> {
                        log.trace("Hello Trace World {}", number);
                        log.debug("Hello Debug World {}", number);
                        log.info("Hello Info World {}", number);
                        log.warn("Hello Warn World {}", number);
                        log.error("Hello Error World {}", number);
                        sleep(100);
                    });
        }
    }

    3.7. Run Application

    각 실행 환경별로 애플리케이션을 실행하고 생성되는 로그를 확인한다. 애플리케이션을 실행하기 위해 jar 파일로 빌드한다.

    $ ./gradlew :bootJar                                      
    
    BUILD SUCCESSFUL in 1s
    4 actionable tasks: 4 executed

    3.7.1. Local Profile

    $ java -jar action-in-blog-0.0.1-SNAPSHOT.jar
    Result
    • 로그 레벨이 DEBUG이므로 TRACE를 제외한 모든 로그가 콘솔에 출력된다.
    • 별도 파일로 저장되지 않는다.

    3.7.2. Dev Profile

    $ java -jar -Dspring.profiles.active=dev action-in-blog-0.0.1-SNAPSHOT.jar
    Result
    • 로그 레벨이 DEBUG이므로 TRACE를 제외한 모든 로그가 콘솔에 출력된다.
    • ERROR 로그들은 ./dev-logs/errors 경로에 파일로 저장된다.

    3.7.3. Prod Profile

    $ java -jar -Dspring.profiles.active=prod action-in-blog-0.0.1-SNAPSHOT.jar
    Result
    • 로그 레벨이 INFO이므로 TRACE, DEBUG를 제외한 모든 로그가 콘솔에 출력됩니다.
    • ERROR 로그들은 ./prod-logs/errors 경로에 파일로 저장됩니다.
    • 출력된 모든 로그들은 ./prod-logs/logs 경로에 파일로 저장됩니다.

    KEEP READING

    DISCUSSION

    댓글