Developer.

5. Spring Boot Application Logging Customizing with Log4j2

🪛 한계점

스프링 어플리케이션이 띄우는 서버 정보에 대해 가독성이 부족하며, 추적 가능한 로그 기능이 필요하다.

📂 목차


📚 본문

Log4j2

우선 Log4j2 는 Asynchronous Logger + LMAX Disruptor 기반으로 1800만 건/s 로깅이 가능하며,

  • XML/JSON/YAML 설정 등을 지원
  • Lambda 기반 지연 로깅
  • MDC, Marker, 사용자 정의 Message API

등의 고급 기능들을 지원한다. 사용하려면 다음 의존성을 추가해준다.

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

기존에 있는 starter 팩에 Logback과의 충돌을 막기 위해 다음을 추가한다.

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

log4j2.properties

log4j2 는 기본적으로 resources에 log4j2.properties 를 참조하여 로깅 구성 설정을 할 수 있다. 기본적으로 Logging을 할 때의 기능들을 수행하는 컴포넌트 Appender, Logger 가 있다.

아래의 용어들을 보고 오길 바란다.

로깅 레벨 계층 와 이름 설정

로그를 띄울 때도 특정 정보들만 띄울 수 있도록 할 수 있다.

로그 레벨 계층은 다음과 같다
ALL → TRACE → DEBUG → INFO → WARN → ERROR → FATAL → OFF

  • ALL: 모든 로그 레벨 허용
  • TRACE: 가장 세밀한 단계(디테일 메서드 호출, 루프 등)
  • DEBUG: 개발 및 디버깅 시 유용한 정보-변수 상태, 흐름 등
  • INFO: 운영 중 기본적으로 남기는 일반 정보-시작/종료, 상태 전환 등
  • WARN: 문제는 아니지만 주의가 필요한 상황-성능 저하, 비추천 API 사용 등
  • ERROR: 기능 일부 실패 등 처리 중에 장애 발생
  • FATAL: 치명적인 오류로 어플리케이션 종료 수준
  • OFF: 모든 로그를 비활성화

status 의 프로퍼티를 주면 해당 status 보다 하위의 로그 메시지들을 출력하게 된다.

## log4j2.properties
# ▼ 내부 로깅 레벨
status = warn
name = PropertiesConfig

name으로는 로깅 설정 전체의 이름을 지정하는 식별자이다.

Filter 기능을 사용한 로그 이벤트 상세 제어

Log4j2는 필터를 사용하여 어떻게 처리해야 할지 제어할 수 있다.

우선 이벤트를 평가하는 것부터 보자면 기본적인 필터 로직은 로그 이벤트를 평가하고, ACCEPT, DENY, NEUTRAL 중 하나의 결과를 반환하게 된다.

  • ACCEPT: 해당 로그를 즉시 수용하고 다음 필터는 검사하지 않음(바로 출력)
  • DENY: 해당 로그를 즉시 버리고 이후 단계로 전달하지 않음(버림)
  • NEUTRAL: 판단을 보류하고 다음 필터로 넘김(다음 필터한테 인계)

위 3가지를 결정내리는 단계는 크게 4단계로 나뉘고, 각 필터 단계에서 위의 결과 중 하나를 가지게 된다.

  1. Context-wide: 전체 설정에 대한 초기 필터
  2. Logger-level: 특정 로거에 설정된 필터
  3. AppenderReference: 어떤 Appender에 보낼지 결정
  4. Appender-level: 실제 Appender 내부 필터

아래 코드는 threshold 라는 필터를 통해 전역 필터를 설정하고 있다.

# ▼ 전역 필터 설정: 디버그 이상 로그만 출력
filters = threshold     # 전역필터 이름은 threshold

# filters 뒤에 나열된 필터 이름은 이후 설정에서
# filter.<name>.type, filter.<name>.level 같이 참조 가능

# 전역 필터 threshold 의 타입으로 ThresholdFilter 라는 레벨 기반 필터를 사용한다.
filter.threshold.type = ThresholdFilter
# 메시지의 레벨이 설정한 기준 이상인지 비교하여 처리한다.

# 해당 ThresholdFilter 가 적용할 기준 레벨을 정의한다.
filter.threshold.level = debug
# 기본적으로 onMatch=NEUTRAL, onMismatch=DENY 이므로
# DEBUG 이상이면 NEUTRAL로 넘어가 다음 레벨 검사 후 출력 가능
# TRACE 이하는 DENY로 바로 차단됨

대표적인 필터 종류로는

  • ThresholdFilter: 특정 기준 이상인지 아닌지 필터링
  • BurstFilter
  • CompositeFilter
  • DynamicThresholdFilter
  • RegexFilter: 정규표현식을 사용한 필터링
  • MapFilter, MarkerFilter, TimeFilter 등등 등이 있다.
Appenders 구성하기
# ▼ 콘솔 Appender 구성

# 사용할 Appender 나열
appenders = console, rolling

# Appender 의 타입 지정, 여기서는 Console로, System.out, System.err 같은 출력 형식으로 내보냄
appender.console.type = Console

# Appender 에 식별자를 붙임
appender.console.name = STDOUT

# Appender 에 로그 메시지를 어떤 형식으로 변환할지 정함
appender.console.layout.type = PatternLayout

# Appender 의 로그 출력 포맷을 지정함
appender.console.layout.pattern = %d{yyyy-MM-dd HH:mm:ss.SSS} [%t] %-5p %c{1}:%L - %m%n

로그 출력 포맷

  • %d{…}: 날짜/시간의 포맷 지정
  • %t: 스레드 이름(main 등)
  • %-5p: 고정 폭 5자리의 로그 레벨(INFO, DEBUG 등)
  • %c{1}: 로거 이름의 마지막 컴포넌트, 일반적으로 클래스 명
  • %L: 로그 발생 코드 라인 번호
  • %m: 실제 메시지
  • %n: 줄바꿈
# ▼ 롤링 파일 Appender 구성

# 로그 파일 내용을 어디 저장할지 지정
appender.rolling.fileName = logs/app.log

# 파일명을 어떻게 쓸건지 설정
appender.rolling.filePattern = logs/app-%d{yyyy-MM-dd}-%i.log.gz

# Policies 라는 롤오버 조건 그룹(wrapper)를 지정
appender.rolling.policies.type = Policies

# 시간 기반 트리거 정책 사용, 특정 주기마다 로그 파일이 자동으로 Rollover
appender.rolling.policies.time.type = TimeBasedTriggeringPolicy

# Rollover 간격을 1단위로 지정
appender.rolling.policies.time.interval = 1

# 파일 크기 기반 트리거 정책 지정
appender.rolling.policies.size.type = SizeBasedTriggeringPolicy

# 파일이 10MB를 초과 시 Rollover 실행
appender.rolling.policies.size.size = 10 MB

# Rollover 전 후의 처리 전략을 지정
appender.rolling.strategy.type = DefaultRolloverStrategy

# Rollover 파일의 최대 개수를 7개로 설정
appender.rolling.strategy.max = 7
루트 로거 설정 (기본 레벨 및 Appender 연결)
# ▼ 루트 로그 레벨 및 Appender 참조

# 로그 레벨 설정
rootLogger.level = info

# 기본 로그 레벨과 appender 매핑 설정
rootLogger.appenderRefs = stdout, rolling
rootLogger.appenderRef.stdout.ref = STDOUT
rootLogger.appenderRef.rolling.ref = ROLLING

Logger 객체를 가져와 출력해보기

Logging 메시지를 출력할 클래스 컨텍스트에 다음을 입력한다:

import org.slf4j.*;
...
    private static final Logger logger =
            LoggerFactory.getLogger(StudyApplication.class);

출력할 메시지가 살아있는 function 의 context 에서 다음을 입력한다.

ConfigurableApplicationContext applicationContext =
        springApplication.run(args);
logger.info("The application is completely running");

다음이 출력됨을 볼 수 있다.

[restartedMain] INFO  StudyApplication:73 - The application is completely running

✒️ 용어

Logger

한 개 이상의 Appender 를 사용하여 로그 메시지 표시를 담당하는 로깅 프레임워크의 컴포넌트

Appender

어펜더를 사용하여 로그가 출력되는 대상과 로깅 포맷을 지정할 수 있다. 로그 메시지가 출력되는 매체에 따라 다양한 어펜더가 있고, 콘솔 어펜더는 어플리케이션의 콘솔에 로그를 출력하고, 파일 어펜더는 로그 메시지를 파일에 출력한다.

  • RollingFileAppender: 시간과 날짜 기반으로 별도의 파일에 로그를 출력하게 된다.
  • SMTP Appender: 정해진 이메일 주소로 로그를 출력한다.