개발 배경
Spring Boot는 Slf4J의 기본 구현체로 Logback을 지원한다. 하지만 대부분은 성능, 편의상 이유로 기본 의존성 Logback을 제거하고 Log4J2를 사용할 것이다.
로깅에는 Rolling Policy라는 것이 존재한다. 간단히 말하자면 하나의 로그 파일이 너무 커지는 것을 방지하기 위해 시간, 크기, cron 등의 기준으로 log파일을 끊어서 새로 작성하는 것이다.
Logback은 이러한 Rolling Policy를 지정하는 것을 단순히 Spring Boot의 properties(yml) 파일의 속성으로 지정할 수 있다. 하지만 Log4J2에는 Rolling Policy를 지정할 수 없어 이러한 문제를 해결하고자 PR를 작성하게 되었다. 기본적인 것들은 지원해주는데 Rolling Policy는 지원해주지 않는다,, 왤까..? 어차피 대부분은 xml 파일로 설정하여 사용하겠지만... 그래도 Spring Boot에 PR도 올려볼 겸 작업해보았다!
개발 후 글 작성을 미루다가... 정리할 겸 뒤늦게 작성한거라 아직 병합되지 않은 main 브랜치랑 비교하면서 작성해보았습니다..!
코드는 인텔리제이로 보는게 더 가독성이 좋은 것 같아서 코드는 캡쳐 사진으로 넣겠습니다!
문제 인식
지금 작업의 목표는 "Spring Boot에 새로운 Log4J2의 프로퍼티를 추가하는 것"이다. 이를 위해 Spring Boot 코드에서 관련된 부분을 뜯어보면서 어떻게 프로퍼티를 읽어서 Log4J2의 설정으로 적용하는지 확인해보았다.
Logging 관련 클래스들은 core/spring-boot/java/org/springframework/boot/logging 내부에서 찾을 수 있었다.(사실 작업하는 도중에 Spring Boot에서 패키지 관련 리팩토링이 있었는지 변경돼서 조금 헤맸다....)


Log4J2 설정을 로드하는 진입점을 열심히 찾아보았다. 그 중 LogginApplicationListener와 Log4J2LoggingSystem에서 진입점을 찾을 수 있었다.

LoggingApplicationListener에서 호출하는 getLoggingSystemProperties는 구현체에 종속되지 않기 때문에 이쪽을 수정하기보단 구현체 쪽을 수정하는 것이 더 낫다고 판단하여 Log4J2LoggingSystem 클래스를 보기로 했다.


Log4J2LoggingSystem의 initialize 메서드가 logFile을 가져와서 추가적으로 호출하는 부분이니 Log4J2LoggingSystem를 살펴보자. 여기서는 super.initialize를 호출하고 있다. Log4J2LoggingSystem는 AbstractLoggingSystem를 상속하고 있고, AbstractLoggingSystem에서 initialize 메서드는 loadDefaults, loadConfiguration을 호출하고 있다.
이제 두 load 메서드들의 핵심 진입점인 private로 선언된 load 메서드를 따라가면서 확인해보자.
private void load(LoggingInitializationContext initializationContext, String location, @Nullable LogFile logFile) {
List<String> overrides = getOverrides(initializationContext);
Environment environment = initializationContext.getEnvironment();
Assert.state(environment != null, "'environment' must not be null");
applySystemProperties(environment, logFile);
loadConfiguration(location, logFile, overrides);
}
1. getOverrides() 호출
getOverrides()메서드는 다음과 같이 정의되어있다.

여기서 매개변수로 넘어오는 LoggingInitializationContext는 로깅 시스템 초기화 시 필요한 컨텍스트 정보를 담는 간단한 컨테이너 클래스이다. 주요 기능으로는 프로필(Profile) 관리, 프로퍼티 소스 관리, 설정 병합 등이 있다.
LoggingInitializationContext 세팅 과정에 대해서 설명하자면 너무 길고 코드가 많아지기 때문에... notion에 간략하게 흐름을 정리한 문서를 첨부해두겠습니다. 궁금하신 분은 확인해보세요!
https://www.notion.so/Environment-29181f531dcb80a0bbaec587d4b2f67f
이렇게 받아온 Environment로 모든 설정 정보에 접근이 가능하게 된다.
이후 Binder로 logging.log4j2.config.override 속성 바인딩을 진행한다.
Binder란?
- Spring Boot의 타입 안전 설정 바인딩 도구
- 복잡한 타입(List, Map, 객체 등)을 자동으로 변환
여기까지 확인해봤는데, getOverrides() 메서드는 우리가 찾는 진입점과는 좀 다른 성격이라는 것을 눈치챌 수 있다! 사실 이 메서드는 Log4J2에서 logging.log4j2.config.override 기능을 사용하는 경우를 지원하기 위한 메서드이다. 그렇다면 다음 단계로 넘어가보자.
2. Environment 가져오기 및 null 체크
근데 load를 호출할 때 getEnvironment를 가져와서 넘겨주면 안되나? 싶긴했지만 코드 내에서 전체적인 메서드의 매개변수로 LoggingInitializationContext를 넘겨주고 코드 일관성 측면에서 이렇게 냅둔 것 같다. (뇌피셜)
3. applySystemProperties() 호출
뭔가 네이밍부터 프로퍼티 관련한 메서드를 찾은 것 같다!
먼저 간략하게 설명하자면 메서드 네이밍처럼 application.properties(yml)에 정의된 로깅 관련 속성들을 시스템 프로퍼티로 변환하여 설정한다.
applySystemProperties는 부모 클래스인 AbstractLoggingSystem에 정의되어있다.


단순하게 LoggingSystemProperties라는 클래스를 생성하여 apply라는 메서드를 호출한다.


여기서 두 번째 생성자 인자로 넘어가는 getDefaultValueResolver는 LoggingSystemProperty.CORRELATION_PATTERN 하나만 처리한다. 람다 내부에서 correlation pattern 프로퍼티인지 확인한 뒤, 속성에 logging.expect-correlation-id가 true로 설정되어 있을 때만 getDefaultLogCorrelationPattern() 값(Log4j2에서는 %correlationId)을 반환하고, 나머지는 모두 null을 반환한다. 다른 기본값은 주입하지 않으니 크게 신경쓰지 않아도 된다.
세 번째 생성자 인자인 setter는 시스템 프로퍼티를 어떻게 기록할지를 제어하는 BiConsumer<String, String>이다. “이름과 값”을 어디에, 어떻게 기록할지 결정하는 함수이다. 기본적으로 null이 넘어가는데, 이 경우엔 시스템 프로퍼티에 값을 작성하게 된다. setSystemProperty가 호출될 때 실행되어 시스템 프로퍼티에 값을 작성한다. 키가 아직 시스템 프로퍼티에 설정되지 않았고, 넣을 값이 있을 때만 System.setProperty(name, value)로 기록한다.


이후 apply 메서드를 호출하며 LoggingSystemProperty라는 Enum에 정의된 모든 값들을 확인하며 설정 파일에 작성되어있는지 확인하고 시스템 프로퍼티로 작성한다. (일부 값들은 기본 값으로 세팅)
applySystemProperty는 여러 시그니처들로 오버로딩되어있는데, 결국 아래 메서드로 수렴하게 된다.

이렇게 시스템 프로퍼티로 올려서 참조하는 이유는 Log4j2, Logback 같은 로깅 프레임워크는 자체 설정 xml 파일을 사용하는데, 이 파일들은 시스템 프로퍼티를 통해 외부 값을 참조하기 때문이다.
4. loadConfiguration() 호출
실제 Log4j2 설정 파일을 로드하는 메서드이다.(여기의 loadConfiguration은 아까 메서드랑 다른 오버로딩된 메서드이다!) Log4j2 설정 파일(xml)을 읽어 LoggerContext에 적용한다. override가 활성화되어있으면 여러 설정을 합쳐 하나의 구성으로 만든다.

총 세 개의 파라미터를 가지고 있지만 실제로는 logFile을 제외하고 두 개의 인자만 넘어온다. 그 중 overrides는 위에서 살펴봤던 것처럼 logging.log4j2.config.override를 지원하는 내용이기 때문에 슬쩍 넘어가보자.
여기서도 load 메서드가 보이는데, 위에서 확인한 메서드와는 다른 오버로딩된 load 메서드이다.

이 메서드는 파일/URL 처리를 하고있다.
- 파일이면 InputStream + ConfigurationSource(file)로 파싱
- URL이면 인증/SSL 구성 후 연결 열어 파싱
- 구현은 Log4j2 ConfigurationFactory 사용
초기화 직전 시스템/환경 기반 프로퍼티를 적용하고, 기본 설정 + 선택적 오버라이드들을 읽어 하나의 Log4j2 구성으로 합친 뒤 LoggerContext를 시작한다.
LoggerContext란 Log4j2의 핵심 런타임 컨텍스트로, 활성화된 로깅 구성(Configuration)과 모든 로거(appenders, filters, logger configs)의 생명주기를 관리한다. 일반적으로 애플리케이션(또는 클래스로더)당 하나가 존재한다.
이렇게 우리가 properties(yml)에 작성하는 값들이 log4j2에 어떻게 적용되는지 분석해보았다. 전체적인 흐름은 properties, yml 파일의 설정이 시스템 프로퍼티로 올라가고, 시스템 프로퍼티의 값들을 xml 파일에서 소비하는 형태라는 것을 알 수 있다.
* 위의 loadDefault 메서드가 바로 스프링에서 제공해주는 기본 xml 파일을 사용할지 말지 결정하는 부분이다. 우리가 명시적으로 지정해주지 않으면 로깅은 파일로 따로 저장되지 않는다. 따라서 log4j2.xml 파일은 콘솔창 전용 xml 설정 파일이고, log4j2-file.xml은 파일로 저장되는 xml 설정 파일이다. 별도의 xml 파일을 사용하도록 설정하지 않는다면 기본적인 log4j2-file.xml의 포맷대로 로그 파일이 기록되게 된다.

내부를 간단히 살펴보면 이렇게 시스템 프로퍼티에서 값을 소비하는 것을 확인할 수 있다. 여기에 Rolling Policy도 적용할 수 있다. 구현에 대한 내용까지 하나의 글에 담기엔 너무 길어져서 나누어서 작성할 예정이다! 다음편에 계속,,,
Log4J2 Properties 개발기 2편
지난 번엔 기존 Spring Boot의 Logging 관련 설정들이 어떻게 적용되는지 알아보았다. 이제 그 흐름 사이에 추가하려고 하는 Log4J2의 Rolling Policy 속성들을 읽을 수 있도록 끼워넣으면 된다! 직접 구현
bird-j.tistory.com
Log4J2 Properties 개발기 3편
이제 마지막으로 새로 추가한 Rolling Policy를 Spring Boot의 기본 xml 파일에서 어떻게 읽는지 확인해보도록 하자. 대부분 비슷한 내용이지만 블럭에 새로운 Rolling Policy를 적용한 것을 확인할 수 있다.
bird-j.tistory.com
'Computer Science > Spring Boot' 카테고리의 다른 글
| Log4J2 Properties 개발기 3편 (0) | 2025.10.22 |
|---|---|
| Log4J2 Properties 개발기 2편 (0) | 2025.10.21 |
| Spring Security - 인증/인가와 FilterChain (w. JWT, Authorization) (0) | 2025.09.12 |
| 재고 관리에서 발생한 동시성 문제 해결 (0) | 2025.09.03 |
| N+1 해결을 통한 성능 개선기 (0) | 2025.09.02 |