/**
* Logback: the reliable, generic, fast and flexible logging framework.
* Copyright (C) 1999-2013, QOS.ch. All rights reserved.
*
* This program and the accompanying materials are dual-licensed under
* either the terms of the Eclipse Public License v1.0 as published by
* the Eclipse Foundation
*
* or (per the licensee's choosing)
*
* under the terms of the GNU Lesser General Public License version 2.1
* as published by the Free Software Foundation.
*/
package ch.qos.logback.core.rolling;
import ch.qos.logback.core.Context;
import ch.qos.logback.core.ContextBase;
import ch.qos.logback.core.encoder.EchoEncoder;
import ch.qos.logback.core.rolling.helper.RollingCalendar;
import ch.qos.logback.core.testUtil.RandomUtil;
import ch.qos.logback.core.util.CoreTestConstants;
import ch.qos.logback.core.util.StatusPrinter;
import org.junit.Before;
import org.junit.Test;
import java.io.File;
import java.io.FileFilter;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import static ch.qos.logback.core.CoreConstants.DAILY_DATE_PATTERN;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
public class TimeBasedRollingWithArchiveRemoval_Test extends ScaffoldingForRollingTests {
String MONTHLY_DATE_PATTERN = "yyyy-MM";
String MONTHLY_CRONOLOG_DATE_PATTERN = "yyyy/MM";
final String DAILY_CRONOLOG_DATE_PATTERN = "yyyy/MM/dd";
RollingFileAppender<Object> rfa = new RollingFileAppender<Object>();
TimeBasedRollingPolicy<Object> tbrp = new TimeBasedRollingPolicy<Object>();
// by default tbfnatp is an instance of DefaultTimeBasedFileNamingAndTriggeringPolicy
TimeBasedFileNamingAndTriggeringPolicy<Object> tbfnatp = new DefaultTimeBasedFileNamingAndTriggeringPolicy<Object>();
long MILLIS_IN_MINUTE = 60 * 1000;
long MILLIS_IN_HOUR = 60 * MILLIS_IN_MINUTE;
long MILLIS_IN_DAY = 24 * MILLIS_IN_HOUR;
long MILLIS_IN_MONTH = (long) ((365.242199 / 12) * MILLIS_IN_DAY);
int MONTHS_IN_YEAR = 12;
int slashCount = 0;
@Before
public void setUp() {
super.setUp();
}
private int computeSlashCount(String datePattern) {
if (datePattern == null)
return 0;
else {
int count = 0;
for (int i = 0; i < datePattern.length(); i++) {
char c = datePattern.charAt(i);
if (c == '/')
count++;
}
return count;
}
}
// test that the number of files at the end of the test is same as the expected number taking into account end dates
// near the beginning of a new year. This test has been run in a loop with start date varying over a two years
// with success.
@Test
public void monthlyRolloverOverManyPeriods() {
slashCount = computeSlashCount(MONTHLY_CRONOLOG_DATE_PATTERN);
int numPeriods = 40;
int maxHistory = 2;
String fileNamePattern = randomOutputDir + "/%d{" + MONTHLY_CRONOLOG_DATE_PATTERN + "}/clean.txt.zip";
long startTime = currentTime;
long endTime = logOverMultiplePeriodsContinuously(currentTime, fileNamePattern, MILLIS_IN_MONTH, maxHistory,
numPeriods);
System.out.println("randomOutputDir:" + randomOutputDir);
System.out.println("start:" + startTime + ", end=" + endTime);
int differenceInMonths = RollingCalendar.diffInMonths(startTime, endTime);
System.out.println("differenceInMonths:" + differenceInMonths);
Calendar startTimeAsCalendar = Calendar.getInstance();
startTimeAsCalendar.setTimeInMillis(startTime);
int indexOfStartPeriod = startTimeAsCalendar.get(Calendar.MONTH);
boolean withExtraFolder = extraFolder(differenceInMonths, MONTHS_IN_YEAR, indexOfStartPeriod, maxHistory);
check(expectedCountWithFolders(maxHistory, withExtraFolder));
}
void generateDailyRollover(long now, int maxHistory, int simulatedNumberOfPeriods, int startInactivity,
int numInactivityPeriods) {
slashCount = computeSlashCount(DAILY_DATE_PATTERN);
logOverMultiplePeriods(now, randomOutputDir + "clean-%d{" + DAILY_DATE_PATTERN + "}.txt", MILLIS_IN_DAY, maxHistory, simulatedNumberOfPeriods, startInactivity, numInactivityPeriods);
check(expectedCountWithoutFoldersWithInactivity(maxHistory, simulatedNumberOfPeriods, startInactivity + numInactivityPeriods));
}
@Test
public void basicDailyRollover() {
int maxHistory = 20;
int simulatedNumberOfPeriods = 20 * 3;
int startInactivity = 0;
int numInactivityPeriods = 0;
generateDailyRollover(currentTime, maxHistory, simulatedNumberOfPeriods, startInactivity, numInactivityPeriods);
}
// Since the duration of a month (in seconds) varies from month to month, tests with inactivity period must
// be conducted with daily rollover not monthly
@Test
public void dailyRollover15() {
int maxHistory = 5;
int simulatedNumberOfPeriods = 15;
int startInactivity = 6;
int numInactivityPeriods = 3;
generateDailyRollover(currentTime, maxHistory, simulatedNumberOfPeriods, startInactivity, numInactivityPeriods);
}
@Test
public void dailyRolloverWithInactivity70() {
int maxHistory = 6;
int simulatedNumberOfPeriods = 70;
int startInactivity = 30;
int numInactivityPeriods = 1;
generateDailyRollover(currentTime, maxHistory, simulatedNumberOfPeriods, startInactivity, numInactivityPeriods);
}
@Test
public void dailyRolloverWithInactivity10() {
int maxHistory = 6;
int simulatedNumberOfPeriods = 10;
int startInactivity = 3;
int numInactivityPeriods = 4;
generateDailyRollover(currentTime, maxHistory, simulatedNumberOfPeriods, startInactivity, numInactivityPeriods);
}
@Test
public void dailyRolloverWithSecondPhase() {
slashCount = computeSlashCount(DAILY_DATE_PATTERN);
int maxHistory = 5;
long endTime = logOverMultiplePeriodsContinuously(currentTime, randomOutputDir + "clean-%d{" + DAILY_DATE_PATTERN + "}.txt",
MILLIS_IN_DAY, maxHistory, maxHistory * 2);
logOverMultiplePeriodsContinuously(endTime + MILLIS_IN_DAY * 10, randomOutputDir + "clean-%d{" + DAILY_DATE_PATTERN + "}.txt",
MILLIS_IN_DAY, maxHistory, maxHistory);
check(expectedCountWithoutFolders(maxHistory));
}
@Test
public void dailyCronologRollover() {
slashCount = computeSlashCount(DAILY_CRONOLOG_DATE_PATTERN);
logOverMultiplePeriodsContinuously(currentTime, randomOutputDir + "/%d{" + DAILY_CRONOLOG_DATE_PATTERN + "}/clean.txt.zip",
MILLIS_IN_DAY, 8, 8 * 3);
int expectedDirMin = 9 + slashCount;
int expectDirMax = expectedDirMin + 1 + 1;
expectedFileAndDirCount(9, expectedDirMin, expectDirMax);
}
@Test
public void dailySizeBasedRollover() {
SizeAndTimeBasedFNATP<Object> sizeAndTimeBasedFNATP = new SizeAndTimeBasedFNATP<Object>();
sizeAndTimeBasedFNATP.setMaxFileSize("10000");
tbfnatp = sizeAndTimeBasedFNATP;
slashCount = computeSlashCount(DAILY_DATE_PATTERN);
logOverMultiplePeriodsContinuously(currentTime, randomOutputDir + "/%d{" + DAILY_DATE_PATTERN + "}-clean.%i.zip", MILLIS_IN_DAY,
5, 5 * 4);
checkPatternCompliance(5 + 1 + slashCount, "\\d{4}-\\d{2}-\\d{2}-clean(\\.\\d)(.zip)?");
}
@Test
public void dailyChronologSizeBasedRollover() {
SizeAndTimeBasedFNATP<Object> sizeAndTimeBasedFNATP = new SizeAndTimeBasedFNATP<Object>();
sizeAndTimeBasedFNATP.setMaxFileSize("10000");
tbfnatp = sizeAndTimeBasedFNATP;
slashCount = 1;
logOverMultiplePeriodsContinuously(currentTime, randomOutputDir + "/%d{" + DAILY_DATE_PATTERN + "}/clean.%i.zip", MILLIS_IN_DAY,
5, 5 * 4);
checkDirPatternCompliance(6);
}
@Test
public void dailyChronologSizeBasedRolloverWithSecondPhase() {
SizeAndTimeBasedFNATP<Object> sizeAndTimeBasedFNATP = new SizeAndTimeBasedFNATP<Object>();
sizeAndTimeBasedFNATP.setMaxFileSize("10000");
tbfnatp = sizeAndTimeBasedFNATP;
slashCount = 1;
int maxHistory = 5;
int simulatedNumberOfPeriods = maxHistory * 4;
long endTime = logOverMultiplePeriodsContinuously(currentTime, randomOutputDir + "/%d{" + DAILY_DATE_PATTERN + "}/clean.%i", MILLIS_IN_DAY,
maxHistory, 3);
logOverMultiplePeriodsContinuously(endTime + MILLIS_IN_DAY * 7, randomOutputDir + "/%d{" + DAILY_DATE_PATTERN + "}/clean.%i",
MILLIS_IN_DAY, maxHistory, simulatedNumberOfPeriods);
checkDirPatternCompliance(maxHistory + 1);
}
void logOncePeriod(long currentTime, String fileNamePattern, int maxHistory) {
buildRollingFileAppender(currentTime, fileNamePattern, maxHistory, DO_CLEAN_HISTORY_ON_START);
rfa.doAppend("Hello ----------------------------------------------------------" + new Date(currentTime));
rfa.stop();
}
@Test
public void cleanHistoryOnStart() {
long now = this.currentTime;
String fileNamePattern = randomOutputDir + "clean-%d{" + DAILY_DATE_PATTERN + "}.txt";
int maxHistory = 3;
for (int i = 0; i <= 5; i++) {
logOncePeriod(now, fileNamePattern, maxHistory);
now = now + MILLIS_IN_DAY;
}
StatusPrinter.print(context);
check(expectedCountWithoutFolders(maxHistory));
}
int expectedCountWithoutFolders(int maxHistory) {
return maxHistory + 1;
}
int expectedCountWithFolders(int maxHistory, boolean withExtraFolder) {
int numLogFiles = (maxHistory + 1);
int numLogFilesAndFolders = numLogFiles * 2;
int result = numLogFilesAndFolders + slashCount;
if (withExtraFolder) result += 1;
return result;
}
void buildRollingFileAppender(long currentTime, String fileNamePattern, int maxHistory,
boolean cleanHistoryOnStart) {
rfa.setContext(context);
rfa.setEncoder(encoder);
tbrp.setContext(context);
tbrp.setFileNamePattern(fileNamePattern);
tbrp.setMaxHistory(maxHistory);
tbrp.setParent(rfa);
tbrp.setCleanHistoryOnStart(cleanHistoryOnStart);
tbrp.timeBasedFileNamingAndTriggeringPolicy = tbfnatp;
tbrp.timeBasedFileNamingAndTriggeringPolicy.setCurrentTime(currentTime);
tbrp.start();
rfa.setRollingPolicy(tbrp);
rfa.start();
}
boolean DO_CLEAN_HISTORY_ON_START = true;
boolean DO_NOT_CLEAN_HISTORY_ON_START = false;
long logOverMultiplePeriodsContinuously(long simulatedTime, String fileNamePattern, long periodDurationInMillis, int maxHistory,
int simulatedNumberOfPeriods) {
return logOverMultiplePeriods(simulatedTime, fileNamePattern, periodDurationInMillis, maxHistory,
simulatedNumberOfPeriods, 0, 0);
}
long logOverMultiplePeriods(long simulatedTime, String fileNamePattern, long periodDurationInMillis, int maxHistory,
int simulatedNumberOfPeriods, int startInactivity,
int numInactivityPeriods) {
buildRollingFileAppender(simulatedTime, fileNamePattern, maxHistory, DO_NOT_CLEAN_HISTORY_ON_START);
int ticksPerPeriod = 512;
int runLength = simulatedNumberOfPeriods * ticksPerPeriod;
int startInactivityIndex = 1 + startInactivity * ticksPerPeriod;
int endInactivityIndex = startInactivityIndex + numInactivityPeriods * ticksPerPeriod;
long tickDuration = periodDurationInMillis / ticksPerPeriod;
for (int i = 0; i <= runLength; i++) {
if (i < startInactivityIndex || i > endInactivityIndex) {
rfa.doAppend("Hello ----------------------------------------------------------" + i);
}
tbrp.timeBasedFileNamingAndTriggeringPolicy.setCurrentTime(addTime(tbrp.timeBasedFileNamingAndTriggeringPolicy.getCurrentTime(),
tickDuration));
add(tbrp.future);
waitForJobsToComplete();
}
rfa.stop();
return tbrp.timeBasedFileNamingAndTriggeringPolicy.getCurrentTime();
}
boolean extraFolder(int numPeriods, int periodsPerEra, int beginPeriod, int maxHistory) {
int valueOfLastMonth = ((beginPeriod) + numPeriods) % periodsPerEra;
return (valueOfLastMonth < maxHistory);
}
long addTime(long time, long timeToWait) {
return time + timeToWait;
}
void expectedFileAndDirCount(int expectedFileAndDirCount, int expectedDirCountMin, int expectedDirCountMax) {
File dir = new File(randomOutputDir);
List<File> fileList = new ArrayList<File>();
findFilesInFolderRecursivelyByPatterMatch(dir, fileList, "clean");
List<File> dirList = new ArrayList<File>();
findAllFoldersInFolderRecursively(dir, dirList);
String msg = "expectedDirCountMin=" + expectedDirCountMin + ", expectedDirCountMax=" + expectedDirCountMax + " actual value=" + dirList.size();
assertTrue(msg, expectedDirCountMin <= dirList.size() && dirList.size() <= expectedDirCountMax);
}
void check(int expectedCount) {
File dir = new File(randomOutputDir);
List<File> fileList = new ArrayList<File>();
findAllDirsOrStringContainsFilesRecursively(dir, fileList, "clean");
assertEquals(expectedCount, fileList.size());
}
int expectedCountWithoutFoldersWithInactivity(int maxHistory, int totalPeriods, int endOfInactivity) {
int availableHistory = (totalPeriods + 1) - endOfInactivity;
int actualHistory = Math.min(availableHistory, maxHistory + 1);
return actualHistory;
}
void genericFindMatching(final FileMatchFunction matchFunc, File dir, List<File> fileList, final String pattern, boolean includeDirs) {
if (dir.isDirectory()) {
File[] matchArray = dir.listFiles(new FileFilter() {
public boolean accept(File f) {
return f.isDirectory() || matchFunc.match(f, pattern);
}
});
for (File f : matchArray) {
if (f.isDirectory()) {
if (includeDirs) fileList.add(f);
genericFindMatching(matchFunc, f, fileList, pattern, includeDirs);
} else
fileList.add(f);
}
}
}
private void findAllFoldersInFolderRecursively(File dir, List<File> fileList) {
FileMatchFunction alwaysFalse = new FileMatchFunction() {
public boolean match(File f, String pattern) {
return false;
}
};
genericFindMatching(alwaysFalse, dir, fileList, null, true);
}
private void findAllDirsOrStringContainsFilesRecursively(File dir, List<File> fileList, String pattern) {
FileMatchFunction matchFunction = new FileMatchFunction() {
public boolean match(File f, String pattern) {
return f.getName().contains(pattern);
}
};
genericFindMatching(matchFunction, dir, fileList, pattern, true);
}
void findFilesInFolderRecursivelyByPatterMatch(File dir, List<File> fileList, String pattern) {
FileMatchFunction matchByPattern = new FileMatchFunction() {
public boolean match(File f, String pattern) {
return f.getName().matches(pattern);
}
};
genericFindMatching(matchByPattern, dir, fileList, pattern, false);
}
Set<String> groupByClass(List<File> fileList, String regex) {
Pattern p = Pattern.compile(regex);
Set<String> set = new HashSet<String>();
for (File f : fileList) {
String n = f.getName();
Matcher m = p.matcher(n);
m.matches();
int begin = m.start(1);
String reduced = n.substring(0, begin);
set.add(reduced);
}
return set;
}
void checkPatternCompliance(int expectedClassCount, String regex) {
File dir = new File(randomOutputDir);
List<File> fileList = new ArrayList<File>();
findFilesInFolderRecursivelyByPatterMatch(dir, fileList, regex);
Set<String> set = groupByClass(fileList, regex);
assertEquals(expectedClassCount, set.size());
}
void checkDirPatternCompliance(int expectedClassCount) {
File dir = new File(randomOutputDir);
List<File> fileList = new ArrayList<File>();
findAllFoldersInFolderRecursively(dir, fileList);
for (File f : fileList) {
assertTrue(f.list().length >= 1);
}
assertEquals(expectedClassCount, fileList.size());
}
}