웹 애플리케이션 개발 시, 로그 관리는 시스템 운영 및 문제 해결에 필수적입니다. 종종 log4net과 같은 로깅 라이브러리를 사용하여 애플리케이션 이벤트를 기록하게 됩니다. 이때 INFO 레벨의 일반 정보성 로그와 ERROR 레벨의 심각한 오류 로그가 하나의 파일에 혼합되어 저장되면, 나중에 오류를 분석하거나 특정 이벤트를 추적하기가 매우 어려워집니다.
예를 들어, 초기 log4net 설정이 다음과 같을 경우, 모든 레벨의 로그가 지정된 Appender로 전달되어 하나의 로그 파일에 기록됩니다.
<root>
<level value="ALL"/>
<appender-ref ref="RollingFile"/>
<appender-ref ref="RollingFileTracer"/>
</root>
이러한 상황에서, Fluent NHibernate와 같은 라이브러리에서 발생하는 수많은 INFO 레벨 로그가 오류 로그를 쉽게 가려버릴 수 있습니다. 이 문제를 해결하기 위해, 특정 레벨의 로그(예: ERROR 레벨 이상)만 별도의 파일로 분리하여 기록하는 방법을 모색할 수 있습니다.
로그 레벨별 파일 분리 구현
log4net은 <Filter> 기능을 통해 이러한 요구사항을 충족할 수 있습니다. 각 Appender에 LevelRangeFilter를 추가하여 특정 로그 레벨 범위만 해당 Appender로 전달되도록 설정할 수 있습니다.
다음은 오류 레벨(ERROR ~ FATAL) 로그를 별도의 파일로 분리하는 log4net 설정 예시입니다.
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<configSections>
<section name="log4net" type="log4net.Config.Log4NetConfigurationSectionHandler,log4net"/>
</configSections>
<log4net>
<!-- 콘솔 Appender 설정 -->
<appender name="ConsoleAppender" type="log4net.Appender.ConsoleAppender">
<layout type="log4net.Layout.PatternLayout">
<conversionPattern value="[%p %t] [%date{yyyy-MM-dd HH:mm:ss,fff}] %-5l - %m%n"/>
</layout>
</appender>
<!-- 모든 로그를 위한 롤링 파일 Appender -->
<appender name="AllLogsRollingFileAppender" type="log4net.Appender.RollingFileAppender">
<file value="App_Data/application.log"/>
<appendToFile value="true"/>
<maximumFileSize value="5MB"/>
<maxSizeRollBackups value="50"/>
<CountDirection value="1"/>
<RollingStyle value="Size"/>
<layout type="log4net.Layout.PatternLayout">
<conversionPattern value="[%p %t] [%date{yyyy-MM-dd HH:mm:ss,fff}] %-5logger - %m%n"/>
</layout>
</appender>
<!-- 오류 로그를 위한 롤링 파일 Appender -->
<appender name="ErrorLogRollingFileAppender" type="log4net.Appender.RollingFileAppender">
<file value="App_Data/errors.log"/>
<appendToFile value="true"/>
<maximumFileSize value="5MB"/>
<maxSizeRollBackups value="50"/>
<CountDirection value="1"/>
<RollingStyle value="Size"/>
<layout type="log4net.Layout.PatternLayout">
<conversionPattern value="[%p] %m%n"/>
</layout>
<!-- ERROR 및 FATAL 레벨만 기록하도록 필터 설정 -->
<filter type="log4net.Filter.LevelRangeFilter">
<levelMin value="ERROR" />
<levelMax value="FATAL" />
</filter>
</appender>
<!-- 루트 로거 설정: 모든 Appender 참조 및 ALL 레벨 설정 -->
<root>
<level value="ALL"/>
<appender-ref ref="AllLogsRollingFileAppender"/>
<appender-ref ref="ErrorLogRollingFileAppender"/>
</root>
</log4net>
</configuration>
위 설정에서 ErrorLogRollingFileAppender는 <filter type="log4net.Filter.LevelRangeFilter">를 사용하여 ERROR에서 FATAL 레벨 사이의 로그만 기록하도록 제한됩니다. 반면 AllLogsRollingFileAppender는 필터가 없어 모든 로그를 기록합니다. 루트 로거는 두 Appender를 모두 참조하며 ALL 레벨로 설정되어 있어 모든 로그 이벤트가 먼저 발생하지만, 각 Appender의 필터에 의해 최종 기록 여부가 결정됩니다.
로거 계층 구조와 레벨
log4net의 로거는 이름을 가진 엔티티이며, 이름은 대소문자를 구분합니다. 로거 이름은 점(.)으로 구분되어 계층 구조를 형성합니다.
- 어떤 로거
A의 이름이 로거B이름의 접두사라면 (예: "Foo.Bar"와 "Foo.Bar.Baz"),A는B의 조상(ancestor) 로거입니다. - 특히,
A와B이름 사이에 다른 로거가 없다면,A는B의 부모(parent) 로거입니다.
루트 로거(Root logger)는 로거 계층의 최상위에 있습니다. 루트 로거는 항상 존재하며, 이름을 통해 접근할 수 없고, 기본적으로 DEBUG 레벨 값을 가집니다.
로거 인스턴스는 log4net.LogManager 클래스의 정적 메서드 GetLogger를 통해 얻을 수 있습니다. 예를 들어 log4net.LogManager.GetLogger("MyApplication")과 같이 사용합니다.
로그 레벨의 종류 및 상속
log4net에서 지원하는 로그 레벨은 다음과 같습니다: ALL, DEBUG, INFO, WARN, ERROR, FATAL, OFF.
레벨 상속 규칙: 특정 로거에 레벨이 명시적으로 정의되어 있지 않다면, 해당 로거의 레벨 값은 가장 가까운 조상 로거(레벨이 정의된)로부터 상속받습니다.
로그 기록 요청은 로거 인스턴스의 출력 메서드(예: Debug(), Info(), Warn(), Error(), Fatal())를 호출함으로써 이루어집니다. 이 출력 메서드가 호출될 때 해당 로그 기록 요청의 레벨이 결정됩니다. 예를 들어, logger.Info("메시지")는 INFO 레벨의 로그 요청을 생성합니다.
로그 기록 가능 여부: 로그 기록 요청의 레벨을 L이라고 하고, 해당 로거가 정의하거나 상속받은 유효 레벨을 K라고 할 때, L >= K일 경우에만 로그 기록 요청이 실행됩니다. 그렇지 않으면 해당 로그는 기록되지 않습니다.
로그 레벨의 우선순위는 다음과 같습니다: DEBUG < INFO < WARN < ERROR < FATAL. 즉, FATAL이 가장 높은 심각도 레벨입니다.
log4net.LogManager.GetLogger 메서드를 이름으로 호출하면 언제나 동일한 로거 인스턴스에 대한 참조를 얻을 수 있습니다. 이는 코드 내에서 로거 객체를 전달할 필요 없이, 설정된 로거를 어디서든 참조할 수 있도록 합니다.