Logback

duckbill413·2024년 6월 14일
0

Spring boot

목록 보기
13/13
post-thumbnail

Logback

Logback 실습 예제

Logback은 Java 기반 애플리케이션에서 로깅을 구현하기 위해 널리 사용되는 프레임워크

SLF4J(Simple Logging Facade for Java)의 구현체 중 하나로, 강력한 기능과 성능, 유연성을 제공

Logback은 기존의 로깅 프레임워크인 Log4j의 후속작

SLF4j vs Log4j2

  • SLF4J2 (Simple Logging for Facade for Java) SLF4J는 로깅 프레임워크에 대한 추상화 계층을 제공하는 라이브러리. SLF4J 자체는 로깅 구현체가 아니라, 다양한 로깅 프레임워크 (예: Logback, Log4j, Log4j2)와 함께 사용할 수 있는 API를 제공 이를 통해 애플리케이션 코드를 변경하지 않고도 로깅 프레임워크를 교체할 수 있는 유연성을 제공
  • Log4j2 Log4j2는 Apache에서 제공하는 고성능 로그인 프레임워크로, Log4j의 단점을 개선하고 다양한 기능을 추가함. 비동기 로깅, 플러그인 아키텍처, 조건부 로깅, 다양한 출력 형식을 지원

Appender 종류

  • ConsoleAppender: 콘솔에 log를 출력
  • FileAppender: 파일 단위로 log를 저장
  • RollingFileAppender: (설정 옵션에 따라) log를 여러 파일로 나누어 저장
  • SMTPAppender: log를 메일로 전송하여 기록
  • DBAppender: log를 DB에 저장

MDC 로깅

  • 멀티 스레드 환경에서 로그를 남길 때 사용하는 개념
  • 스레드 마다 고유한 log 값을 가지고 있고 그걸 logback에 전달해 주기 위해서 사용하는 개념
  • 스레드 별로 mdc에 들어가 있는 값을 관리
  • MDC를 clear 하지 않고 다른 요청이 동일한 스레드를 참조 한다면 이 값들이 정리가 되어 있지 않은 경우 다음 스레드가 get 하게 된다. 따라서, clear를 사용하는 것을 잊지 말자
  • MDC는 로그에서 해당 값에 저장 되어 있는 값을 동적으로 가져와서 출력하기 위해서 사용

Logback 실습

프로젝트 구조

Spring의 profile 설정을 통해 profile 별로 logging 설정을 다르게 하는 방법

Logging Profile

resources 폴더 내부에 로깅에 대한 설정을 하기 위한 logback-spring.xml 파일 생성

logback-spring.xml

<?xml version="1.0" encoding="utf-8" ?>
<!-- 부모 logback xml 파일 -->
<!--기존 Spring의 default logback 설정이 아래로 덮어 쓰여짐 -->
<configuration>
    <include resource="org/springframework/boot/logging/logback/defaults.xml"/>
    <!-- spring profile 에 따라 다른 logback 설정이 필요 -->
    <include resource="logback-spring-${spring.profiles.active}.xml"/>
</configuration>
  • <configuration>을 통해 Spring의 기본 log 설정을 덮어 쓸 수 있다.
  • ${spring.profiles.active}를 통해 실행된 profile의 정보를 받아 온다.
  • defaults.xml은 log의 기본적인 변수를 제공한다. 내부를 살펴 보면 아래와 같은 변수들이 설정 된 것을 확인할 수 있다.
     <?xml version="1.0" encoding="UTF-8"?>
    
    <!--
    Default logback configuration provided for import
    -->
    
    <included>
    	<conversionRule conversionWord="applicationName" converterClass="org.springframework.boot.logging.logback.ApplicationNameConverter" />
    	<!-- ... -->
    	
    	<property name="CONSOLE_LOG_PATTERN" value="${CONSOLE_LOG_PATTERN:-%clr(%d{${LOG_DATEFORMAT_PATTERN:-yyyy-MM-dd'T'HH:mm:ss.SSSXXX}}){faint} %clr(${LOG_LEVEL_PATTERN:-%5p}) %clr(${PID:- }){magenta} %clr(---){faint} %clr(%applicationName[%15.15t]){faint} %clr(${LOG_CORRELATION_PATTERN:-}){faint}%clr(%-40.40logger{39}){cyan} %clr(:){faint} %m%n${LOG_EXCEPTION_CONVERSION_WORD:-%wEx}}"/>
    	<property name="CONSOLE_LOG_CHARSET" value="${CONSOLE_LOG_CHARSET:-${file.encoding:-UTF-8}}"/>
    	<property name="CONSOLE_LOG_THRESHOLD" value="${CONSOLE_LOG_THRESHOLD:-TRACE}"/>
    	<property name="FILE_LOG_PATTERN" value="${FILE_LOG_PATTERN:-%d{${LOG_DATEFORMAT_PATTERN:-yyyy-MM-dd'T'HH:mm:ss.SSSXXX}} ${LOG_LEVEL_PATTERN:-%5p} ${PID:- } --- %applicationName[%t] ${LOG_CORRELATION_PATTERN:-}%-40.40logger{39} : %m%n${LOG_EXCEPTION_CONVERSION_WORD:-%wEx}}"/>
    	<property name="FILE_LOG_CHARSET" value="${FILE_LOG_CHARSET:-${file.encoding:-UTF-8}}"/>
    	<property name="FILE_LOG_THRESHOLD" value="${FILE_LOG_THRESHOLD:-TRACE}"/>
    
    	<logger name="org.apache.catalina.startup.DigesterFactory" level="ERROR"/>
    	<!-- ... -->
    </included>

Log Test를 위한 DemoController 생성

@Slf4j // logback 사용을 위한 Annotation
@RestController
public class DemoController {
    @GetMapping("/demo")
    public String demo() {
        log.trace("log --> trace");
        log.debug("log --> debug");
        log.info("log --> info");
        log.warn("log --> warn");
        log.error("log --> error");
        return "demo";
    }
}

Console logging (Local 환경)

Local 환경에서 작업하는 경우 Console을 활용하여 Log를 확인하게 된다.

resouces 내부에 local 환경에서의 설정을 위한 logback-spring-local.xml 파일을 생성

logback-spring-local.xml

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

    <!-- console-appender xml 내부 -->
    <!--    <included>-->
    <!--        <appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">-->
    <!--            <filter class="ch.qos.logback.classic.filter.ThresholdFilter">-->
    <!--                <level>${CONSOLE_LOG_THRESHOLD}</level>-->
    <!--            </filter>-->
    <!--            <encoder>-->
    <!--                <pattern>${CONSOLE_LOG_PATTERN}</pattern>-->
    <!--                <charset>${CONSOLE_LOG_CHARSET}</charset>-->
    <!--            </encoder>-->
    <!--        </appender>-->
    <!--    </included>-->

    <!-- Appender 생성 예제 -->
    <appender name="CONSOLE2" class="ch.qos.logback.core.ConsoleAppender">
        <filter class="ch.qos.logback.classic.filter.ThresholdFilter">
            <level>INFO</level>
        </filter>
        <layout>
            <pattern>
                [CONSOLE2] [%-5level] %d{yyyy-MM-dd HH:mm:ss} [%thread] [%logger{20}:%line] - %msg%n
            </pattern>
        </layout>
    </appender>

    <!-- 로깅 레벨에 대한 설정 -->
    <!-- 로깅 레벨에 대한 세부 설정은 사용하는 Appender에 따라 달라질 수 있음 -->
    <root level="DEBUG">
        <!-- 사용할 Appender 지정 (복수 가능) -->
        <!--        <appender-ref ref="CONSOLE"/>-->
        <appender-ref ref="CONSOLE2"/>
    </root>
</included>
  • console-appender.xml의 내부를 보면 앞서 있던 defaults.xml의 변수를 활용하는 것을 확인할 수 있다.
  • Appender의 생성을 보면 콘솔 로깅을 위한 ConsoleAppender 클래스가 지정된 것을 확인할 수 있으며, Logging Level과 Pattern이 설정된 것을 볼 수 있다.
  • <root>에서는 전역으로 사용할 설정을 할 수 있다. logging level과 프로젝트 실행 중 사용할 Appender을 지정할 수 있다.
  • 로깅을 하면 다음과 같이 로그가 기록된다.
    [CONSOLE2] [INFO ] 2024-06-14 23:14:43 [http-nio-8080-exec-1] [w.d.l.c.DemoController:14] - log --> info
    [CONSOLE2] [WARN ] 2024-06-14 23:14:43 [http-nio-8080-exec-1] [w.d.l.c.DemoController:15] - log --> warn
    [CONSOLE2] [ERROR] 2024-06-14 23:14:43 [http-nio-8080-exec-1] [w.d.l.c.DemoController:16] - log --> error

File logging (Prod 환경)

실제 배포 환경에서 log를 활용한다면 console 보다는 파일을 활용하여 로그를 확인하게 된다. 따라서, FileAppender 을 활용하여 log를 File에 기록하는 법을 알아 보겠습니다.

logback-spring-prod.xml 생성

<included>
    <property resource="logback-variables.properties"/>
    <appender name="REQUEST1" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <file>
            ${LOG_DIR}/request1.log
        </file>
        <rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
            <fileNamePattern>${LOG_DIR}/archive/request1.%d{yyyy-MM-dd}_%i.log</fileNamePattern>
            <maxFileSize>1KB</maxFileSize> <!-- 로그 파일 최대 크기 -->
            <maxHistory>30</maxHistory> <!-- 로그 파일 최대 보관 주기 (단위: 일) -->
        </rollingPolicy>
        <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
            <pattern>
                [REQUEST1] ${LOG_PATTERN}
            </pattern>
            <outputPatternAsHeader>true</outputPatternAsHeader>
        </encoder>
    </appender>

    <appender name="REQUEST2" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <file>
            ${LOG_DIR}/request2.log
        </file>
        <rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
            <fileNamePattern>${LOG_DIR}/archive/request2.%d{yyyy-MM-dd}_%i.log</fileNamePattern>
            <maxFileSize>10KB</maxFileSize> <!-- 로그 파일 최대 크기 -->
            <maxHistory>30</maxHistory> <!-- 로그 파일 최대 보관 주기 (단위: 일) -->
        </rollingPolicy>
        <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
            <pattern>
                [REQUEST2] ${LOG_PATTERN}
            </pattern>
            <outputPatternAsHeader>true</outputPatternAsHeader>
        </encoder>
    </appender>

    <root level="INFO">
        <appender-ref ref="REQUEST1"/>
        <appender-ref ref="REQUEST2"/>
    </root>
</included>
  • File에 log를 남기기 위하여 RollingFileAppender 클래스를 상속하여 Appender 생성

  • <file>: log를 저장할 파일명 및 위치를 지정

  • <rollingPolicy>: Rolling Log File의 크기 및 보관 시간을 지정하기 위한 policy를 지정

    • <fileNamePattern>: 추가적인 로그 파일의 이름(서식) 및 위치 지정

      • archive 폴더에 이전 로그가 파일로 저장되는 것을 확인해 볼 수 있음
    • <maxFileSize>: 파일의 최대 크기 지정

    • <maxHistory>: 파일의 보관 주기 (단위: day)

  • <encoder>: 파일에 로그를 남길 패턴 등을 지정

    • <outputPatternAsHeader>: 로그를 남길 때 어떤 패턴으로 남기는지를 확인하기 쉽도록 로그 파일의 상단에 log pattern 정보를 함께 기록
  • <root>: logging level 및 어떤 Appender를 사용할 것인가 등을 지정

  • logback-variables.properties 생성

    logback-variables.properties 파일을 이용하여 prod 환경에서 사용하는 변수를 공통으로 관리하도록 하겠습니다.

    LOG_DIR=logs
    LOG_PATTERN=[%-5level] %d{yyyy-MM-dd HH:mm:ss} [%thread] [%logger{20}:%line] - %msg%n
    • logback-spring-prod.xml 파일의 2번 라인을 보면 property resourcelogback-variables.properties를 사용할 것임을 선언
  • REQUEST1 Append의 logging 결과 파일

    #logback.classic pattern: [REQUEST1] [%-5level] %d{yyyy-MM-dd HH:mm:ss} [%thread] [%logger{20}:%line] - %msg%n
    [REQUEST1] [INFO ] 2024-06-14 23:39:31 [main] [w.d.l.SpringLogbackApplication:50] - Starting SpringLogbackApplication using Java 17.0.10 with PID 24612 (D:\git\cs_diary\spring\spring-logback\build\classes\java\main started by uhyeon in D:\git\cs_diary\spring\spring-logback)
    [REQUEST1] [INFO ] 2024-06-14 23:39:31 [main] [w.d.l.SpringLogbackApplication:660] - The following 1 profile is active: "prod"
    [REQUEST1] [INFO ] 2024-06-14 23:39:32 [main] [o.s.b.w.e.t.TomcatWebServer:111] - Tomcat initialized with port 8080 (http)
    [REQUEST1] [INFO ] 2024-06-14 23:39:32 [main] [o.a.c.c.StandardService:173] - Starting service [Tomcat]
    [REQUEST1] [INFO ] 2024-06-14 23:39:32 [main] [o.a.c.c.StandardEngine:173] - Starting Servlet engine: [Apache Tomcat/10.1.24]
    [REQUEST1] [INFO ] 2024-06-14 23:39:32 [main] [o.a.c.c.C.[.[.[/]:173] - Initializing Spring embedded WebApplicationContext
    [REQUEST1] [INFO ] 2024-06-14 23:39:32 [main] [o.s.b.w.s.c.ServletWebServerApplicationContext:296] - Root WebApplicationContext: initialization completed in 756 ms
    [REQUEST1] [INFO ] 2024-06-14 23:39:32 [main] [o.s.b.w.e.t.TomcatWebServer:243] - Tomcat started on port 8080 (http) with context path '/'
    [REQUEST1] [INFO ] 2024-06-14 23:39:32 [main] [w.d.l.SpringLogbackApplication:56] - Started SpringLogbackApplication in 1.463 seconds (process running for 1.897)
    [REQUEST1] [INFO ] 2024-06-14 23:40:05 [http-nio-8080-exec-1] [o.a.c.c.C.[.[.[/]:173] - Initializing Spring DispatcherServlet 'dispatcherServlet'
    [REQUEST1] [INFO ] 2024-06-14 23:40:05 [http-nio-8080-exec-1] [o.s.w.s.DispatcherServlet:532] - Initializing Servlet 'dispatcherServlet'
    [REQUEST1] [INFO ] 2024-06-14 23:40:05 [http-nio-8080-exec-1] [o.s.w.s.DispatcherServlet:554] - Completed initialization in 0 ms
    [REQUEST1] [INFO ] 2024-06-14 23:40:05 [http-nio-8080-exec-1] [w.d.l.c.DemoController:14] - log --> info
    [REQUEST1] [WARN ] 2024-06-14 23:40:05 [http-nio-8080-exec-1] [w.d.l.c.DemoController:15] - log --> warn
    [REQUEST1] [ERROR] 2024-06-14 23:40:05 [http-nio-8080-exec-1] [w.d.l.c.DemoController:16] - log --> error

MDC Logging

MDC 를 활용하여 로깅하는 방법

  1. MDC Controller의 생성

    @Slf4j
    @RestController
    public class MdcController {
    
        @GetMapping("/mdc")
        public String mdc() {
            MDC.put("job", "dev"); // key, value
            log.trace("log --> trace");
            log.debug("log --> debug");
            log.info("log --> info");
            log.warn("log --> warn");
            log.error("log --> error");
            MDC.clear();
    
            return "mdc";
        }
    }
    • MDC의 key, value를 이용하여 로깅하는 방법입니다.
    • put을 이용하여 값을 등록
    • clear을 사용하여 메소드 종료시 MDC를 clear 할 필요가 있음
  2. logback-spring-prod.xml에 MDC 설정 추가

    <included>
        <property resource="logback-variables.properties"/>
        
        <!-- 추가 하는 부분 -->
    
        <appender name="MDC" class="ch.qos.logback.core.rolling.RollingFileAppender">
            <file>
                ${LOG_DIR}/mdc.log
            </file>
            <rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
                <fileNamePattern>${LOG_DIR}/archive/mdc.%d{yyyy-MM-dd}_%i.log</fileNamePattern>
                <maxFileSize>10KB</maxFileSize> <!-- 로그 파일 최대 크기 -->
                <maxHistory>30</maxHistory> <!-- 로그 파일 최대 보관 주기 (단위: 일) -->
            </rollingPolicy>
            <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
                <!-- %X 를 통해 MDC 안의 값을 key 로 조회 가능 -->
                <pattern>
                    [MDC] %X{job}%n
                </pattern>
                <outputPatternAsHeader>true</outputPatternAsHeader>
            </encoder>
        </appender>
    		
    
        <root level="INFO">
    		    <!-- MDC Appender 사용 설정 -->
    		    <!-- 추가 하는 부분 -->
            <appender-ref ref="MDC"/>
        </root>
    </included>
    • <encoder><pattern>을 보면 [MDC] %X{job}%n에서 {job}MDC.put을 이용하여 등록한 값이다.
    • 이처럼, MDC를 활용하여 동적으로 Log를 남기는 것이 가능하다.

Logging Filter

  • 기존의 logging level을 이용하여 로그를 출력하는 방식은 상위의 로그가 전부 출력됨
  • 예를 들어 INFO level로 설정한다면 INFO < WARN < ERROR의 로그가 전부 출력됨
  • Logging Filtering을 통해 원하는 Log만 필터링하여 출력하거나 별개의 로그 파일로 관리할 수 있음
  1. logback-spring-prod.xml에 Logging Filter 설정 추가

    <included>
        <!-- 추가 하는 부분 -->
        <appender name="WARN" class="ch.qos.logback.core.rolling.RollingFileAppender">
            <file>
                ${LOG_DIR}/warn.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}/archive/warn.%d{yyyy-MM-dd}_%i.log</fileNamePattern>
                <maxFileSize>10KB</maxFileSize> <!-- 로그 파일 최대 크기 -->
                <maxHistory>30</maxHistory> <!-- 로그 파일 최대 보관 주기 (단위: 일) -->
            </rollingPolicy>
            <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
                <!-- %X 를 통해 MDC 안의 값을 key 로 조회 가능 -->
                <pattern>
                    [WARN] ${LOG_PATTERN}
                </pattern>
                <outputPatternAsHeader>true</outputPatternAsHeader>
            </encoder>
        </appender>
    
        <appender name="ERROR" class="ch.qos.logback.core.rolling.RollingFileAppender">
            <file>
                ${LOG_DIR}/error.log
            </file>
            <filter class="ch.qos.logback.classic.filter.LevelFilter">
                <level>error</level>
                <onMatch>ACCEPT</onMatch>
                <onMismatch>DENY</onMismatch>
            </filter>
            <rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
                <fileNamePattern>${LOG_DIR}/archive/error.%d{yyyy-MM-dd}_%i.log</fileNamePattern>
                <maxFileSize>10KB</maxFileSize> <!-- 로그 파일 최대 크기 -->
                <maxHistory>30</maxHistory> <!-- 로그 파일 최대 보관 주기 (단위: 일) -->
            </rollingPolicy>
            <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
                <!-- %X 를 통해 MDC 안의 값을 key 로 조회 가능 -->
                <pattern>
                    [ERROR] ${LOG_PATTERN}
                </pattern>
                <outputPatternAsHeader>true</outputPatternAsHeader>
            </encoder>
        </appender>
    
        <root level="INFO">
    		    <!-- 추가 하는 부분 -->
            <appender-ref ref="WARN"/>
            <appender-ref ref="ERROR"/>
        </root>
        
    </included>
    • WARN, ERROR에 대한 별도의 Appender를 생성
    • <filter>LevelFilter를 사용하여 WARN, ERROR 로깅 Level에 대한 필터를 생성
      • <level>: 필터링할 Logging Level 설정
      • <onMatch>: 일치하는 조건에 대한 필터링
      • <onMismatch>: 일치하지 않는 조건에 대한 필터링
  2. WARN Appender 결과에 대한 로깅 파일

    #logback.classic pattern: [WARN] [%-5level] %d{yyyy-MM-dd HH:mm:ss} [%thread] [%logger{20}:%line] - %msg%n
    [WARN] [WARN ] 2024-06-14 23:40:05 [http-nio-8080-exec-1] [w.d.l.c.DemoController:15] - log --> warn
    • WARN 로그 레벨에 대한 로그만 출력

Logger 생성

<root> 이외에 <logger>을 생성하여 로그를 기록할 수 있다.

  1. logback-spring-prod.xml에 Logger 생성 설정 추가

    <included>
    		<!-- 추가 하는 부분 -->
        <appender name="QUERY1" class="ch.qos.logback.core.rolling.RollingFileAppender">
            <file>
                ${LOG_DIR}/query.log
            </file>
            <rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
                <fileNamePattern>${LOG_DIR}/archive/query.%d{yyyy-MM-dd}_%i.log</fileNamePattern>
                <maxFileSize>10KB</maxFileSize> <!-- 로그 파일 최대 크기 -->
                <maxHistory>30</maxHistory> <!-- 로그 파일 최대 보관 주기 (단위: 일) -->
            </rollingPolicy>
            <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
                <pattern>
                    [QUERY] ${LOG_PATTERN}
                </pattern>
                <outputPatternAsHeader>true</outputPatternAsHeader>
            </encoder>
        </appender>
    
    		<!-- 추가 하는 부분 -->
        <!-- additivity 는 상위 로거의 속성 들로 merge 할 것인가 지정 -->
        <logger name="SQL_LOG1" level="INFO" additivity="false">
            <appender-ref ref="QUERY1"/>
        </logger>
    
        <logger name="SQL_LOG2" level="INFO" additivity="false">
            <appender-ref ref="QUERY1"/>
        </logger>
    </included>
    • <logger> 생성을 보면 Logging Level과 additivity 설정을 확인할 수 있음 additivity를 false로 하여 상위 Logger와의 merge를 막음
  2. SQL_LOG1 사용을 위한 QueryController1 생성

    @Slf4j(topic = "SQL_LOG1") // topic 으로 logger 를 지정
    @RestController
    public class QueryController1 {
    
        @GetMapping("/query1")
        public String query1() {
            log.trace("log --> trace");
            log.debug("log --> debug");
            log.info("log --> info");
            log.warn("log --> warn");
            log.error("log --> error");
            return "query1";
        }
    }
    • @Slf4jtopicSQL_LOG1으로 logger를 지정하여 출력할 수 있음
  3. SQL_LOG2 사용을 위한 QueryController2 생성

    @RestController
    public class QueryController2 {
        public static final Logger log = LoggerFactory.getLogger("SQL_LOG2");
    
        @GetMapping("/query2")
        public String query2() {
            log.trace("log --> trace");
            log.debug("log --> debug");
            log.info("log --> info");
            log.warn("log --> warn");
            log.error("log --> error");
            return "query2";
        }
    }
    • LoggerFactorygetLogger를 통해 logger를 지정할 수 있음
profile
같이 공부합시다~

0개의 댓글