로그를 남기자 - logback(4)

후니·2023년 6월 16일
0

Logback

목록 보기
4/5
post-thumbnail

# 관련 소스는 여기에서 확인하실 수 있습니다.

# 2023-06-18 업데이트

현재 포스팅까지 구현된 코드에서 잘못된 점을 발견하여 수정 중에 있습니다. 수정사항은 다음 포스팅에서 다루어질 예정입니다.

# 2023-06-23 업데이트

2023-06-18 발견된 문제점을 수정하여 포스팅 완료, Github 소스 업데이트 완료 했습니다

지난 포스팅에서 예고했던 대로 구현 코드를 공유하고 설명하고자 한다.

1. CustomTimeBasedFileNamingAndTriggeringPolicy 클래스

이 클래스는 DefaultTimeBasedFileNamingAndTriggeringPolicy 클래스를 상속받아 메서드를 오버라이딩한 클래스이다.

@NoAutoStart
public class CustomTimeBasedFileNamingAndTriggeringPolicy<E> extends DefaultTimeBasedFileNamingAndTriggeringPolicy<E> {

	private final int LOG_CREATION_CYCLE = 5;

	@Override
	public boolean isTriggeringEvent(File activeFile, E event) {
		long time = this.getCurrentTime();
		if (time >= this.nextCheck) {
			Date dateOfElapsedPeriod = this.dateInCurrentPeriod;
			this.addInfo("Elapsed period: " + dateOfElapsedPeriod);

			this.setDateInCurrentPeriod(this.nextCheck);

			this.elapsedPeriodsFileName = this.getCurrentPeriodsFileNameWithoutCompressionSuffix();

			this.setDateInCurrentPeriod(time);
			this.computeNextCheck();
			return true;
		} else {
			return false;
		}
	}

	@Override
	protected void computeNextCheck() {
		this.nextCheck = this.rc.getEndOfNextNthPeriod(this.dateInCurrentPeriod, LOG_CREATION_CYCLE).getTime();
	}
}

이 클래스에서는 롤링 시간을 체크하고 이벤트를 발생시키는 isTriggeringEvent 메서드와 다음 롤링 시간을 계산하는 computeNextCheck 메서드를 오버라이딩하여 구현한다. 오버라이딩한 각 메서드를 설명하자면 아래와 같다.

isTriggeringEvent 메서드

DefaultTimeBasedFileNamingAndTriggeringPolicy 클래스의 메서드로 로그 이벤트가 발생할 때마다 RollingFileAppender 클래스에서 해당 메서드를 호출하는 것을 볼 수 있다.

public class RollingFileAppender<E> extends FileAppender<E> {

...
...
...

	/**
	* This method differentiates RollingFileAppender from its super class.
	*/
	@Override
	protected void subAppend(E event) {
		// The roll-over check must precede actual writing. This is the
		// only correct behavior for time driven triggers.

		// We need to synchronize on triggeringPolicy so that only one rollover
		// occurs at a time
		synchronized (triggeringPolicy) {
			if (triggeringPolicy.isTriggeringEvent(currentlyActiveFile, event)) {
				rollover();
			}
		}

		super.subAppend(event);
	}

...
...
...

이 메서드가 호출되면 애플리케이션 실행 시 logback이 초기 설정을 하면서 저장한 파일 롤링 시간 this.nextCheck를 통해 파일 롤링 여부를 결정하고, 롤링 파일 이름 패턴을 통해 파일 이름을 결정하고, 동시에 롤링 시 다음 파일 롤링 시간을 계산하는 computeNextCheck 메서드를 호출하게 된다.

오버라이딩하기 전의 원래 코드는 다음과 같다.

public boolean isTriggeringEvent(File activeFile, final E event) {
	long time = getCurrentTime();
	if (time >= nextCheck) {
		Date dateOfElapsedPeriod = dateInCurrentPeriod;
		addInfo("Elapsed period: " + dateOfElapsedPeriod);
		elapsedPeriodsFileName = tbrp.fileNamePatternWithoutCompSuffix.convert(dateOfElapsedPeriod);
		setDateInCurrentPeriod(time);
		computeNextCheck();
		return true;
	} else {
		return false;
	}
}

오버라이딩하기 전의 코드와 비교해서 다른점은 롤링 파일의 이름을 결정하는 메서드는 접근 제어자가 protected로 선언되어 있어 오버라이딩하여 호출할 수 없기 때문에 동일한 역할을 하도록 dateInCurrentPeriod 값을 원하는 값으로 설정한 뒤에 getCurrentPeriodsFileNameWithoutCompressionSuffix 메서드를 호출하였다.

computeNextCheck 메서드

isTriggeringEvent 메서드에서 호출하며, 다음 롤링 시간을 계산한다.

protected void computeNextCheck() {
	nextCheck = rc.getNextTriggeringDate(dateInCurrentPeriod).getTime();
}

본래의 소스 코드는 위와 같고 오버라이딩한 소스 코드와 다른 점은 실제 다음 롤링 시간을 계산하는 RollingCalendar 클래스의 getEndOfNextNthPeriod 메서드를 직접 호출하도록 변경하여, 인자 값으로 롤링 사이클에 대한 값을 지정할 수 있도록 했다는 것이다.

logback-spring.xml

요구사항에 맞추어 logback Custom을 마쳤다면, 이제 이 설정을 사용하도록 해주어야 한다. 기본적인 logback 설정은 생략하도록 하겠다.

<appender name="CUSTOM_APPENDER" class="ch.qos.logback.core.rolling.RollingFileAppender">
	<file>${LOG_PATH}/${LOG_FILE_NAME}.log</file>
	<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
		<fileNamePattern>${LOG_PATH}/${LOG_FILE_NAME}.%d{yyyy-MM-dd-HH-mm}.log</fileNamePattern>
		<timeBasedFileNamingAndTriggeringPolicy class="com.huning.logback.CustomTimeBasedFileNamingAndTriggeringPolicy"/>
		<maxHistory>30</maxHistory>
	</rollingPolicy>
	<encoder>
		<pattern>${LOG_PATTERN}</pattern>
	</encoder>
</appender>

<logger name="com.huning.logback.LogbackScheduler" level="DEBUG" additivity="false">
	<appender-ref ref="CUSTOM_APPENDER"/>
	<appender-ref ref="CONSOLE"/>
</logger>

먼저 appender 이름을 정하고, rollingPolicyTimeBasedRollingPolicy를 사용하도록 한다. 그리고, timeBasedFileNamingAndTriggeringPolicy에는 우리가 만든 CustomTimeBasedFileNamingAndTriggeringPolicy을 넣어주도록 하자.

이후에 logger에 해당 appender가 사용될 경로를 지정하여 사용하도록 설정해 준다.

끝!!!!! 이 아니다.

logback은 다들 아시겠지만,(나는 몰랐다) 로그 이벤트가 발생하지 않으면 동작을 하지 않는다. 이렇게 되면 우리의 요구사항 중에서 사용자의 동작이 없을 때 빈 로그 파일이라도 남겨야 한다는 요구사항을 만족시키지 못하는 것이다. 이 문제를 해결하기 위해 스프링에서 제공하는 Scheduler를 이용하여 1분마다 빈 로그 이벤트를 발생시키도록 해주었다.

LogBackScheduler 클래스

@Slf4j
@Component
@EnableScheduling
public class LogbackScheduler {

	private static int index = 1;

  @Scheduled(cron = "1 * * * * *")
  public void checkRollingLogFile() {
    LoggerContext loggerContext = (LoggerContext) LoggerFactory.getILoggerFactory();
    Appender<ILoggingEvent> appender = loggerContext.getLogger("com.huning.logback.LogbackScheduler").getAppender("CUSTOM_APPENDER");

    try {
      appender.doAppend(new LoggingEvent());
    } catch (Exception e) {
      new RuntimeException("로그 이벤트 발생 실패");
    }
  }

	@Scheduled(fixedRate = 10000) // 10초마다 실행
	public void writeLog() {
		LocalDateTime now = LocalDateTime.now();
		DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
		String nowDateTime = now.format(formatter);
		log.info("{}번째 로그입니다. 현재 시간은 {}입니다.", index, nowDateTime);
		index++;
	}
}

checkRollingLogFile 메서드에서 custom appender 객체를 가져와 1분마다 빈 로그 이벤트를 발생시켜서 사용자의 동작으로 인한 로그 이벤트가 발생하지 않아도 logback에서 파일을 롤링시킬 수 있도록 하였다.

아래의 writeLog 메서드는 10초마다 log가 기록되도록 하는 테스트용 메서드이다.

마무리

이렇게 해서 logback을 요구사항에 맞추어 Custom 해보는 시간을 가졌다. 이 시간을 통해서 다시금 자바의 위대함에 대해서 알게 되었고, 기초가 중요하다는 사실을 또 한 번 깨닫게 되는 시간이었다.

#소스코드

logback 관련 포스팅에 소개된 소스코드는 아래의 링크에 있습니다.
https://github.com/hun-ing/custom-logback/tree/main/logback

비상 비상 !!!

이제까지 잘 되는줄 알았던 logback이 여러번의 테스트를 거치면서 이상한점을 발견했다...
무엇이 잘못 되었고, 어떻게 해결했는지의 과정을 다음 포스팅에서 다루어 보겠다.

profile
-ing

1개의 댓글

comment-user-thumbnail
2023년 12월 19일

안녕하세요..

logback 커스텀 관련해서 질문이 있습니다.

TimeBasedRollingPolicy 안에 isTriggeringEvent 값이 true가 나와야 롤오버가 발생 되는건가요?

TimeBasedRollingPolicy 상속받아 커스텀으로 로그파일 삭제를 하려고 합니다.

답글 달기