게임 옥션 서버 프로젝트를 개발하며 어떠한 API에서 서버 장애, 개발자의 오타 혹은 잘못된 파라미터 사용 등 다양한 상황에서의 Exception이 발생하였으며 이에 대한 기록을 남기기위해 로깅을 하는 방법에 대해 알아보게 되었습니다.

로깅 라이브러리는 Log4j, Logback, Log4j2등의 다양한 로깅 라이브러리들이 있었으며 각각의 특징들 중 가장 퍼포먼스 성능이 높다고 확인된 Log4j2를 사용하게 되었습니다.

 

이번 포스팅에서는 Log4j2를 사용한 로깅에 대해서 알아보겠습니다. 

 

Log4j, Logback, Log4j2

  • Log4j
    • 스프링 프레임워크 초기에 등장한 로깅 라이브러리이며 XML 기반의 설정 파일을 통해 로깅을 구성할 수 있다.
    • 현재는 개발이 중단되었으며, 특정 메시지를 입력하여 컴퓨터 권한을 탈취당할 수 있는 취약점이 발견되어 사용을 권장하지 않는다.
  • Logback
    • SLF4J(Simple Logging Facade for Java)와 통합되어 사용할 수 있다.
    • Logger 객체를 사용하여 동적으로 로깅 이벤트를 관리하며 런타임시에도 실시간으로 변경할 수 있다.
    • XML외에도 Groovy를 사용하여 작성할 수 있다.
  • Log4j2
    • 비동기 로깅을 지원하여 멀티스레드 환경에서 로깅작업이 기존의 Log4j,Logback보다 처리량이 높다.
    • 다양한 Appender, Layouts, Filter를 통해 로깅 형식 및 출력을 유연하게 구성할 수 있다.
    • 자바의 람다식을 활용할 수 있는 메서드도 정의되어 있다.

Log4j, Logback, Log4j2에 대한 성능 비교 - https://logging.apache.org/log4j/2.x/performance.html

해당 그래프는 초당 몇개의 파일을 쓰는지에 대한 성능 비교 그래프입니다.

쓰레드의 개수가 1~2의 경우는 비동기성을 사용하기에 부적합하여 성능에 대한 차이가 크게 나지않습니다.

하지만 멀티스레드의 개수가 늘어날수록 Log4j2의 성능이 Log4j와 Logback보다 퍼포먼스가 높게 출력되었습니다.

이로인해 Log4j2 를 사용한 로깅을 사용하였습니다.

 

 

 

Log4j2 적용하기

[사용 환경]

Spring Boot : 3.1.3

BuildTool : Gradle

 

build.gradle

configurations {
	configureEach {
		exclude group: 'org.springframework.boot', module: 'spring-boot-starter-logging'
	}
}

dependencies {
	implementation 'org.springframework.boot:spring-boot-starter-log4j2'
}

 

  • configurations
    • Spring에서는 기본적으로 Logback을 사용하며, 다른 라이브러리 Log4j2를 추가하면 로깅 라이브러리끼리 충돌할 가능성이 있기때문에 Logback을 사용하지 않게 설정합니다.
  • dependencies
    • Log4j2 의존성을 주입합니다.

 

log4j2.xml

<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="info">
    <Properties>
        <Property name="logPath">./logs</Property>
        <Property name="logPattern">[%-5level] %d{yyyy-MM-dd HH:mm:ss} [%t] %c{1} - %msg%n</Property>
        <Property name="serviceName">game-auction-server</Property>
    </Properties>
    <Appenders>
        <Console name="console">
            <PatternLayout pattern="${logPattern}"/>
        </Console>
        <RollingFile
                name="file"
                append="true"
                fileName="${logPath}/${serviceName}.log"
                filePattern="${logPath}/${serviceName}.%d{yyyy-MM-dd}.%i.log.zip">
            <PatternLayout pattern="${logPattern}"/>
            <Policies>
                <SizeBasedTriggeringPolicy size="5MB"/>
                <TimeBasedTriggeringPolicy/>
            </Policies>
            <DefaultRolloverStrategy>
                <Delete basePath="${logPath}" maxDepth="5">
                    <IfFileName glob="${serviceName}.*.log"/>
                    <IfLastModified age="15d"/>
                </Delete>
            </DefaultRolloverStrategy>
        </RollingFile>
    </Appenders>
    <Loggers>
        <Logger name="consolelog" level="info" additivity="false">
            <AppenderRef ref="console"/>
            <AppenderRef ref="file"/>
        </Logger>
    </Loggers>
</Configuration>

 

  • Property
    • xml 파일 내부의 변수로 사용하기위해 정의하는 부분
  • Appender
    • 로그 이벤트를 콘솔과 파일에 출력하는데 사용한다.
    • <Console> : 콘솔에 로그를 출력하는 appender를 정의한다.
    • <PatternLayout> : 콘솔에 출력되는 로그의 패턴을 정의한다.
  • RollingFile
    • 파일에 로그를 기록하는 appender를 정의한다.
    • appender="true" : 기존의 파일에 덮어씌우기를 할것인지 정의한다.
    • fileName : 로그 파일의 경로와 이름을 정의한다. 
  • Policies
    • RollingFile의 행동을 결정한다.
    • <SizeBasedTriggeringPolicy size ="5MB"/> : 파일 크기가 5MB 이상일 시 새로운 파일로 롤 오버 한다.
  • DefaultRolloverStrategy
    • RollingFile이 발생 시 사용할 기본 롤 오버의 전략을 정의한다.
    • <Delete basePath= > : 일정 조건에 따라 로그 파일을 삭제한다. 
  • Logger
    • 로그 레벨과 로깅 이벤트를 특정 appender에 연결한다.
    • level : 해당 로거의 로그 레벨을 정의한다
      • TRACE < DEBUG < INFO < WARN < ERROR
      • level=INFO 설정시, 하위의 DEBUG,TRACE는 기록하지 않는다.
      • TRACE : 어플리케이션의 내부 동작을 상세하게 추적하고 문제해결에 도움이 되는 정보를 제공한다.
      • DEBUG : 어플리케이션의 상태와 흐름에 관한 세부 정보를 기록하며, 문제 해결에 도움을 준다.
      • INFO : 일반적인 정보를 나타내며 주요 이벤트 또는 실행 상태에 대한 정보를 기록한다.
      • WARN : 어플리케이션의 경고나 예상치 못한 상황에 대한 로그를 나타낸다.
      • ERROR : 프로덕션 환경에서 발생한 심각한 오류나 예외 상황에 대한 로그를 나타낸다.

예시)

        if (bidItem == null || !(bidItem.getBidStatus().equals(BidStatus.SALE))) {
            log.info(ErrorCode.ITEM_FORBIDDEN.getMessage());
            throw new CustomException(ErrorCode.ITEM_FORBIDDEN);

입찰 API과정에서 예외처리에 대한 결과를 로깅한다.

 

결과)

로그파일에 설정한 포맷형태의 HH:mm:ss, level, method:line, message가 출력된다.

 

 

느낀점

Log4j, Logger, Log4j2의 각각의 특징을 알 수 있었으며,그 중 Log4j2를 왜 사용해야하는지에 대해 알 수 있었습니다.

또한 Log4j2를 사용하여 Exception 발생시 해당 내용을 로그파일로 기록하여 에러 발생시 해결 시간을 단축할 수 있었으며 이는 곧 유지보수성이 향상되었다고 볼 수 있습니다.

또한 AWS CloudWatch를 사용하여 모니터링 연동을 하였는데, 'ca.pjer:logback-awslogs-appender:1.6.0' 라이브러리를 사용하여 Log4j2 라이브러리와 충돌이 있었습니다. 이로 인해 라이브러리 간의 충돌시 발생하는 문제점 및 해결법을 알 수 있었으며, 이에 관한 내용은 AWS CloudWatch를 사용한 모니터링 포스트글에서 작성하겠습니다.

 

 

출처

https://medium.com/@201924576/spring-boot-log4j2-%EC%84%A4%EC%A0%95-21f3b6da38c6

https://logback.qos.ch/performance.html

 

+ Recent posts