Spring boot – Log 설정

Spring boot에서 사용할 수 있는 대표적인 로그 관리 프레임워크

Log4j

2001년도에 개발된 로그 관리 프레임워크로, XML 설정 파일을 사용하여 로깅 구성을 정의하고, 다양한 출력 형식을 지원한다.

다음과 같은 단점을 가지며, 현재 많이 사용되지는 않는다.

  • 성능이 떨어질 수 있고, 멀티 스레드 환경에서의 안정성이 부족할 수 있다.
  • 보안 취약점 (CVE-2021-44228, “Log4Shel”)이 발견되어 사용이 권장되지 않는다.

Log4j2

2014년도 개발된 로그 관리 프레임워크로, Log4j2의 후속 버전으로 성능과 기능이 크게 개선되었다. 비동기 로깅을 지원하여 높은 성능을 발휘하며, 플러그인 아키텍처를 통해 유연한 확장이 가능하며, 다양한 설정 형식을 지원한다.

주요 특징은 다음과 같다.

  • 높은 성능과 안정성, 더 나은 비동기 지원
  • API 호환성 덕분에 기존 Log4j 사용자들이 쉽게 마이그레이션 가능
  • 멀티 쓰레드 환경에서의 비동기 로거의 경우, 다른 로깅 프레임워크보다 많은 처리량과 짧은 대기 시간을 제공
  • 람다 표현식과 사용자 정의 로그 레벨을 지원
  • 필터링 기능과 자동 리로딩을 지원

Logback

2008년도에 개발된 로그 관리 프레임워크로, Log4j의 창시자가 개발하였다. SLF4J (Simple Logging Façade for Java)와의 통합이 잘 되어 있어, 다양한 로깅 프레임워크와 함께 사용할 수 있다.

주요 특징은 다음과 같다.

  • 성능이 뛰어나고, 비동기 로깅을 지원
  • Log4j2보다 더 간단한 구성 및 설정을 제공
  • 로그 회전 및 압축 기능이 내장되어 있어 관리가 용이
  • Spring boot의 기본 로그 프레임워크로 내장되어 있음

Log4j2 vs Logback

Spring boot 반영

Log 활성화

Slf4j (Simple Logging Facade for Java)

로깅 프레임워크 구현체에 대한 추상 레이어 역할을 수행하는 인터페이스로, slf4j를 사용하며 코드 수정 없이 로깅 프레임워크 구현체를 바꿀 수 있는 조건을 만들 수 있다.

Spring boot Start – web 추가

Spring boot Start는 기본적으로 사용되는 라이브러리들에 대한 의존성들이 포함되어 있다. spring-boot-starter-web에는 로그를 사용하기 위한 spring-boot-starter-logging 라이브러리가 포함되어 있다. 해당 라이브러리를 사용함으로 인해, 기본적으로 slf4j와 그 구현체인 logback이 포함된다.

log4j2 의존성 수정

spring-boot-starter-logging 라이브러리에는 logback이 포함되어 있기 때문에, 아래와 같은 설정으로 logback을 제거하고, log4j2 의존성을 추가해 준다.

dependencies {
    implementation “org.springframework.boot:spring-boot-starter-log4j2”
}
configurations {
    all {
        exclude group: ‘org.springframework.boot’, module: ‘spring-boot-starter-logging’
    }
}

Jackson-dataformat-yaml (옵션)

만약 설정을 yaml 파일 형태를 사용할 것이라면, 아래와 같이 yaml 파일 해석을 위한 라이브러리를 추가해 줘야 한다. 해당 설정은 필수 요소가 아니기에, 해당 의존성이 포함되어 있지 않는 것 같다. (추후 변경될 가능성 있음)

implementation ‘com.fasterxml.jackson.dataformat:Jackson-dataformat-yaml’

콘솔의 로그 레벨에 따른 색깔 표시

로그 레벨에 따라 색을 칠하려면 다음과 같이 2가지 설정이 필요하다.

  1. log4j2설정에서 disableAnsi를 비활성화
    Configutaion.Appenders:Console.PatternLayout.disableAnsi: false
  2. 패턴에 highlight 패턴으로 감싸줌
    %highlight{} 패턴으로 다음과 같이 출력된 부분을 감싸 준다.
    %highlight{%-level}

위와 같이 설정하면, 설정된 부분에 로그 레벨에 따라 미리 시스템에 의해, 혹은 사용자 정의로 설정된 색으로 구문을 표시해준다.

Lombok을 통한 Logging

Lombok 추가 후, @Slf4j 어노테이션을 추가하면, 별도의 추가 작업 없이 바로 log 명령어를 통해 로거에 접근할 수 있다.

Logging.Properties를 통한 로깅 사용 자제

Spring boot 에서는 logging.properties를 통한 로그 구성 파일을 제공하는 기능을 지원하지만, jar에서 실행할 때 문제를 일으키는 알려진 클래스 로딩 문제가 있기 때문에 해당 방법을 사용하는 것에 신중해야 한다.

Log4j2 설정

Configuration:
Appenders:
Console:
name: Console_Appender
target: SYSTEM_OUT
PatternLayout:
charset: UTF-8
pattern: “%d{HH:mm:ss.SSS} [%t] %-5level %logger{36} – %msg%n”
disableAnsi: false
RollingFile:
name: RollingFile_Appender
fileName: ./logs/auth-server.log
filePattern: ./logs/auth-server-%d{yyyy-MM-dd}-%i.log
PatternLayout:
charset: UTF-8
pattern: “%d{HH:mm:ss.SSS} [%t] %-5level %logger{36} – %msg%n”
disableAnsi: false
Policies:
TimeBasedTriggeringPolicy:
interval: 1
modulate: true
SizeBasedTriggeringPolicy:
size: 10MB
DefaultRolloverStrategy:
max: 10
Loggers:
Root:
level: debug
AppenderRef:
ref: Console_Appender
ref: RollingFile_Appender

Async Logger 활성화

AsyncLogger설정을 통한 Async Logger 활성화를 수행한다.

Configuration:

...
Loggers:
AsyncLogger:
name: asnyc-logger
level: info
additivity: false
AppenderRef:
- ref: RollingFile_Appender

에러 처리

AsyncLogger를 추가했을 경우, 아래와 같은 에러가 발생할 수 있다. 이 경우엔, 필요로 하는 라이브러리를 참조 추가하여 해결한다.

에러)
Caused by: java.lang.ClassNotFoundException: com.lmax.disruptor.EventHandler
    at java.base/jdk.internal.loader.BuiltinClassLoader.loadClass(BuiltinClassLoader.java:641)
    at java.base/jdk.internal.loader.ClassLoaders$AppClassLoader.loadClass(ClassLoaders.java:188)

수정 사항)

// Log4j2 설정
implementation 'org.springframework.boot:spring-boot-starter-log4j2'
implementation 'com.lmax:disruptor:3.4.2'

 

 

성능 개선 확인 

Log4j2 – sync 방식의 성능 

관련 코드 

    /* 

     * 테스트 로그를 쌓음 

     */ 

    @GetMapping(“/writeLogAsync”) 

    @Async 

    public CompletableFuture<String> writeLogAsync() { 

        var startDateTime = LocalTime.now(); 

        for (int i = 0; i < 100000 ; i++) { 

            asyncLogger.warn(“LOG – {}”, i); 

        } 

        var endDateTime = LocalTime.now(); 

        asyncLogger.info(“End DateTime : {} ==> {}”, startDateTime, endDateTime); 

        return CompletableFuture.completedFuture(“{ S : ” + startDateTime.toString() + “, E : ” + endDateTime.toString() + ” }”); 

    } 

 

성능 확인 

15:52:07.402 [http-nio-8200-exec-2] INFO  com.wemade.cq.webapi.aspect.autoLogAspect – [<< END >> com.wemade.cq.webapi.controller.TestController::writeLog() – Duration elapsed : 9076 ms] 

15:52:27.411 [http-nio-8200-exec-3] INFO  com.wemade.cq.webapi.aspect.autoLogAspect – [<< END >> com.wemade.cq.webapi.controller.TestController::writeLog() – Duration elapsed : 9017 ms] 

16:35:45.800 [http-nio-8200-exec-5] INFO  com.wemade.cq.webapi.aspect.autoLogAspect – [<< END >> com.wemade.cq.webapi.controller.TestController::writeLog() – Duration elapsed : 9439 ms] 

 

Log4j2 – async 방식의 성능 

관련 코드 

    /* 

     * 테스트 로그를 쌓음 

     */ 

    @GetMapping(“/writeLog”) 

    public String writeLog() { 

        var startDateTime = LocalTime.now(); 

        for (int i = 0; i < 100000 ; i++) { 

            syncLogger.warn(“LOG – {}”, i); 

        } 

        var endDateTime = LocalTime.now(); 

        syncLogger.info(“End DateTime : {} ==> {}”, startDateTime, endDateTime); 

        return “{ S : ” + startDateTime.toString() + “, E : ” + endDateTime.toString() + ” }”; 

    } 

성능 확인 

15:49:37.397 [http-nio-8200-exec-2] INFO  com.wemade.cq.webapi.aspect.autoLogAspect – [<< END >> com.wemade.cq.webapi.controller.TestController::writeLog() – Duration elapsed : 665 ms] 

15:49:56.652 [http-nio-8200-exec-3] INFO  com.wemade.cq.webapi.aspect.autoLogAspect – [<< END >> com.wemade.cq.webapi.controller.TestController::writeLog() – Duration elapsed : 632 ms] 

15:50:18.316 [http-nio-8200-exec-4] INFO  com.wemade.cq.webapi.aspect.autoLogAspect – [<< END >> com.wemade.cq.webapi.controller.TestController::writeLog() – Duration elapsed : 617 ms] 

 

특이 사항 

  1. Async 방식의 특징에 따라, 결과 리턴 후, 계속 로그가 쌓여가는 현상이 발생함

 

Logback – sync 방식의 성능 

관련 코드 

    /* 

     * 테스트 로그를 쌓음 

     */ 

    @GetMapping(“/writeLog”) 

    public String writeLog() { 

        var startDateTime = LocalTime.now(); 

        for (int i = 0; i < 100000; i++) { 

            logger.warn(“LOG – {}”, i); 

        } 

        var endDateTime = LocalTime.now(); 

        logger.info(“End DateTime : {} ==> {}”, startDateTime, endDateTime); 

        return “{ S : ” + startDateTime.toString() + “, E : ” + endDateTime.toString() + ” }”; 

    } 

성능 확인 

10:24:47.661 [http-nio-8200-exec-2] INFO  c.w.cq.webapi.aspect.autoLogAspect – [<< END >> com.wemade.cq.webapi.controller.TestController::writeLog() – Duration elapsed : 11259 ms] 

10:25:06.786 [http-nio-8200-exec-3] INFO  c.w.cq.webapi.aspect.autoLogAspect – [<< END >> com.wemade.cq.webapi.controller.TestController::writeLog() – Duration elapsed : 11742 ms] 

10:25:27.619 [http-nio-8200-exec-4] INFO  c.w.cq.webapi.aspect.autoLogAspect – [<< END >> com.wemade.cq.webapi.controller.TestController::writeLog() – Duration elapsed : 11049 ms] 

 

 

Logback – async 방식의 성능 

관련 코드 

    /* 

     * 테스트 로그를 쌓음 

     */ 

    @GetMapping(“/writeLog”) 

    public String writeLog() { 

        var startDateTime = LocalTime.now(); 

        for (int i = 0; i < 100000; i++) { 

            logger.warn(“LOG – {}”, i); 

        } 

        var endDateTime = LocalTime.now(); 

        logger.info(“End DateTime : {} ==> {}”, startDateTime, endDateTime); 

        return “{ S : ” + startDateTime.toString() + “, E : ” + endDateTime.toString() + ” }”; 

    } 

 

설정 파일 변경 내역 

Gradle의 logback classic 라이브러리 추가 
implementation ‘ch.qos.logback:logback-classic’; 

AsyncAppender 로 기존 로그 Appender Wrapping 

        <!– Async Appender –> 

        <appender name=”FILE-ASYNC” class=”ch.qos.logback.classic.AsyncAppender”> 

            <appender-ref ref=”FILE” /> 

            <includeCallerData>false</includeCallerData> 

            <neverBlock>false</neverBlock> 

        </appender> 

성능 확인 

10:45:30.398 [http-nio-8200-exec-1] INFO  c.w.cq.webapi.aspect.autoLogAspect – [<< END >> com.wemade.cq.webapi.controller.TestController::writeLog() – Duration elapsed : 9704 ms] 

10:45:49.463 [http-nio-8200-exec-2] INFO  c.w.cq.webapi.aspect.autoLogAspect – [<< END >> com.wemade.cq.webapi.controller.TestController::writeLog() – Duration elapsed : 8968 ms] 

10:46:07.141 [http-nio-8200-exec-3] INFO  c.w.cq.webapi.aspect.autoLogAspect – [<< END >> com.wemade.cq.webapi.controller.TestController::writeLog() – Duration elapsed : 9481 ms] 

 

특이 사항 

비동기 방식이지만, Log4j2 – async 방식과는 다르게 모든 로그가 출력된 이후, 결과가 리턴 되고 있음 

답글 남기기

이메일 주소는 공개되지 않습니다. 필수 필드는 *로 표시됩니다