本文主要研究一下logback的TimeBasedRollingPolicy
public class TimeBasedRollingPolicy<E> extends RollingPolicyBase implements TriggeringPolicy<E> {
static final String FNP_NOT_SET = "The FileNamePattern option must be set before using TimeBasedRollingPolicy. ";
// WCS: without compression suffix
FileNamePattern fileNamePatternWithoutCompSuffix;
private Compressor compressor;
private RenameUtil renameUtil = new RenameUtil();
Future<?> compressionFuture;
Future<?> cleanUpFuture;
private int maxHistory = UNBOUNDED_HISTORY;
protected FileSize totalSizeCap = new FileSize(UNBOUNDED_TOTAL_SIZE_CAP);
private ArchiveRemover archiveRemover;
TimeBasedFileNamingAndTriggeringPolicy<E> timeBasedFileNamingAndTriggeringPolicy;
boolean cleanHistoryOnStart = false;
//......
}
TimeBasedRollingPolicy继承了RollingPolicyBase,它定义了maxHistory、cleanHistoryOnStart、timeBasedFileNamingAndTriggeringPolicy等属性
public void start() {
// set the LR for our utility object
renameUtil.setContext(this.context);
// find out period from the filename pattern
if (fileNamePatternStr != null) {
fileNamePattern = new FileNamePattern(fileNamePatternStr, this.context);
determineCompressionMode();
} else {
addWarn(FNP_NOT_SET);
addWarn(CoreConstants.SEE_FNP_NOT_SET);
throw new IllegalStateException(FNP_NOT_SET + CoreConstants.SEE_FNP_NOT_SET);
}
compressor = new Compressor(compressionMode);
compressor.setContext(context);
// wcs : without compression suffix
fileNamePatternWithoutCompSuffix = new FileNamePattern(
Compressor.computeFileNameStrWithoutCompSuffix(fileNamePatternStr, compressionMode), this.context);
addInfo("Will use the pattern " + fileNamePatternWithoutCompSuffix + " for the active file");
if (compressionMode == CompressionMode.ZIP) {
String zipEntryFileNamePatternStr = transformFileNamePattern2ZipEntry(fileNamePatternStr);
zipEntryFileNamePattern = new FileNamePattern(zipEntryFileNamePatternStr, context);
}
if (timeBasedFileNamingAndTriggeringPolicy == null) {
timeBasedFileNamingAndTriggeringPolicy = new DefaultTimeBasedFileNamingAndTriggeringPolicy<>();
}
timeBasedFileNamingAndTriggeringPolicy.setContext(context);
timeBasedFileNamingAndTriggeringPolicy.setTimeBasedRollingPolicy(this);
timeBasedFileNamingAndTriggeringPolicy.start();
if (!timeBasedFileNamingAndTriggeringPolicy.isStarted()) {
addWarn("Subcomponent did not start. TimeBasedRollingPolicy will not start.");
return;
}
// the maxHistory property is given to TimeBasedRollingPolicy instead of to
// the TimeBasedFileNamingAndTriggeringPolicy. This makes it more convenient
// for the user at the cost of inconsistency here.
if (maxHistory != UNBOUNDED_HISTORY) {
archiveRemover = timeBasedFileNamingAndTriggeringPolicy.getArchiveRemover();
archiveRemover.setMaxHistory(maxHistory);
archiveRemover.setTotalSizeCap(totalSizeCap.getSize());
if (cleanHistoryOnStart) {
addInfo("Cleaning on start up");
Instant now = Instant.ofEpochMilli(timeBasedFileNamingAndTriggeringPolicy.getCurrentTime());
cleanUpFuture = archiveRemover.cleanAsynchronously(now);
}
} else if (!isUnboundedTotalSizeCap()) {
addWarn("'maxHistory' is not set, ignoring 'totalSizeCap' option with value [" + totalSizeCap + "]");
}
super.start();
}
start方法根据fileNamePatternStr创建FileNamePattern,根据compressionMode创建Compressor,对于zip压缩的创建zipEntryFileNamePattern,另外默认设置了DefaultTimeBasedFileNamingAndTriggeringPolicy,然后执行其start,对于maxHistory不为0的,则设置archiveRemover
public void stop() {
if (!isStarted())
return;
waitForAsynchronousJobToStop(compressionFuture, "compression");
waitForAsynchronousJobToStop(cleanUpFuture, "clean-up");
super.stop();
}
private void waitForAsynchronousJobToStop(Future<?> aFuture, String jobDescription) {
if (aFuture != null) {
try {
aFuture.get(CoreConstants.SECONDS_TO_WAIT_FOR_COMPRESSION_JOBS, TimeUnit.SECONDS);
} catch (TimeoutException e) {
addError("Timeout while waiting for " + jobDescription + " job to finish", e);
} catch (Exception e) {
addError("Unexpected exception while waiting for " + jobDescription + " job to finish", e);
}
}
}
stop方法执行waitForAsynchronousJobToStop,主要是等待compressionFuture及cleanUpFuture
public void rollover() throws RolloverFailure {
// when rollover is called the elapsed period's file has
// been already closed. This is a working assumption of this method.
String elapsedPeriodsFileName = timeBasedFileNamingAndTriggeringPolicy.getElapsedPeriodsFileName();
String elapsedPeriodStem = FileFilterUtil.afterLastSlash(elapsedPeriodsFileName);
if (compressionMode == CompressionMode.NONE) {
if (getParentsRawFileProperty() != null) {
renameUtil.rename(getParentsRawFileProperty(), elapsedPeriodsFileName);
} // else { nothing to do if CompressionMode == NONE and parentsRawFileProperty ==
// null }
} else {
if (getParentsRawFileProperty() == null) {
compressionFuture = compressor.asyncCompress(elapsedPeriodsFileName, elapsedPeriodsFileName,
elapsedPeriodStem);
} else {
compressionFuture = renameRawAndAsyncCompress(elapsedPeriodsFileName, elapsedPeriodStem);
}
}
if (archiveRemover != null) {
Instant now = Instant.ofEpochMilli(timeBasedFileNamingAndTriggeringPolicy.getCurrentTime());
this.cleanUpFuture = archiveRemover.cleanAsynchronously(now);
}
}
rollover方法通过timeBasedFileNamingAndTriggeringPolicy获取elapsedPeriodsFileName,然后将当前文件重命名为elapsedPeriodsFileName,对于archiveRemover不为null的则执行cleanAsynchronously
ch/qos/logback/core/rolling/helper/ArchiveRemover.java
public interface ArchiveRemover extends ContextAware {
void clean(Instant instant);
void setMaxHistory(int maxHistory);
void setTotalSizeCap(long totalSizeCap);
Future<?> cleanAsynchronously(Instant now);
}
ArchiveRemover定义了clean、setMaxHistory、setTotalSizeCap、cleanAsynchronously方法
ch/qos/logback/core/rolling/helper/TimeBasedArchiveRemover.java
public class TimeBasedArchiveRemover extends ContextAwareBase implements ArchiveRemover {
static protected final long UNINITIALIZED = -1;
// aim for 32 days, except in case of hourly rollover, see
// MAX_VALUE_FOR_INACTIVITY_PERIODS
static protected final long INACTIVITY_TOLERANCE_IN_MILLIS = 32L * (long) CoreConstants.MILLIS_IN_ONE_DAY;
static final int MAX_VALUE_FOR_INACTIVITY_PERIODS = 14 * 24; // 14 days in case of hourly rollover
final FileNamePattern fileNamePattern;
final RollingCalendar rc;
private int maxHistory = CoreConstants.UNBOUNDED_HISTORY;
private long totalSizeCap = CoreConstants.UNBOUNDED_TOTAL_SIZE_CAP;
final boolean parentClean;
long lastHeartBeat = UNINITIALIZED;
public TimeBasedArchiveRemover(FileNamePattern fileNamePattern, RollingCalendar rc) {
this.fileNamePattern = fileNamePattern;
this.rc = rc;
this.parentClean = computeParentCleaningFlag(fileNamePattern);
}
//......
}
TimeBasedArchiveRemover定义了fileNamePattern、rollingCalendar、maxHistory、totalSizeCap属性
public void clean(Instant now) {
long nowInMillis = now.toEpochMilli();
// for a live appender periodsElapsed is expected to be 1
int periodsElapsed = computeElapsedPeriodsSinceLastClean(nowInMillis);
lastHeartBeat = nowInMillis;
if (periodsElapsed > 1) {
addInfo("Multiple periods, i.e. " + periodsElapsed
+ " periods, seem to have elapsed. This is expected at application start.");
}
for (int i = 0; i < periodsElapsed; i++) {
int offset = getPeriodOffsetForDeletionTarget() - i;
Instant instantOfPeriodToClean = rc.getEndOfNextNthPeriod(now, offset);
cleanPeriod(instantOfPeriodToClean);
}
}
public void cleanPeriod(Instant instantOfPeriodToClean) {
File[] matchingFileArray = getFilesInPeriod(instantOfPeriodToClean);
for (File f : matchingFileArray) {
addInfo("deleting " + f);
f.delete();
}
if (parentClean && matchingFileArray.length > 0) {
File parentDir = getParentDir(matchingFileArray[0]);
removeFolderIfEmpty(parentDir);
}
}
clean方法主要是计算periodsElapsed,然后通过rollingCalendar获取instantOfPeriodToClean,再执行cleanPeriod方法;cleanPeriod方法则通过getFilesInPeriod获取对应的文件,然后挨个执行delete,最后再判断下parentDir是否为空,为空则删除
public Future<?> cleanAsynchronously(Instant now) {
ArhiveRemoverRunnable runnable = new ArhiveRemoverRunnable(now);
ExecutorService executorService = context.getExecutorService();
Future<?> future = executorService.submit(runnable);
return future;
}
public class ArhiveRemoverRunnable implements Runnable {
Instant now;
ArhiveRemoverRunnable(Instant now) {
this.now = now;
}
@Override
public void run() {
clean(now);
if (totalSizeCap != UNBOUNDED_TOTAL_SIZE_CAP && totalSizeCap > 0) {
capTotalSize(now);
}
}
}
cleanAsynchronously主要是创建ArhiveRemoverRunnable,然后提交到context的executorService;ArhiveRemoverRunnable实现了Runnable接口,其run方法执行clean,对于totalSizeCap大于0的执行capTotalSize
TimeBasedRollingPolicy包含了RollingPolicy及TriggeringPolicy,其rollover方法主要是委托给timeBasedFileNamingAndTriggeringPolicy获取elapsedPeriodsFileName然后去rename,对于maxHistory不是无限制的设置timeBasedFileNamingAndTriggeringPolicy的archiveRemover的maxHistory及totalSizeCap,执行其cleanAsynchronously方法;其isTriggeringEvent方法也是委托给了timeBasedFileNamingAndTriggeringPolicy。