Issue
In the 'Discard old builds' setting for a Jenkins job, you can enter a value for 'Days to keep builds' and also 'Max # of builds to keep.' How does Jenkins respond to having both of these set? Does it just choose one, or does it combine the behavior so that the build history must both be older than the time limit and exceed the max # of builds?
Solution
Jenkins Log Rotate by Example
Consider the following example
buildDiscarder(logRotator(daysToKeepStr: '30', numToKeepStr: '100'))
This log rotate policy will run after every build and it will delete any build that is over 100, as well as any build that is over 30 days ( even if there are less than 100 builds ). Jenkins will look at each specific build and if it meets just one of those criterion it will be deleted
Pseudocode
if( build.age > 30 || build.number > 100 ) {
delete build
}
Keep in mind that this will override any global log rotate settings you have configured
Here is the LogRotator class that is used by Jenkins core. Here is the exact code used by Jenkins to execute log rotate
if(numToKeep!=-1) {
// Note that RunList.size is deprecated, and indeed here we are loading all the builds of the job.
// However we would need to load the first numToKeep anyway, just to skip over them;
// and we would need to load the rest anyway, to delete them.
// (Using RunMap.headMap would not suffice, since we do not know if some recent builds have been deleted for other reasons,
// so simply subtracting numToKeep from the currently last build number might cause us to delete too many.)
RunList<? extends Run<?,?>> builds = job.getBuilds();
for (Run r : builds.subList(Math.min(builds.size(), numToKeep), builds.size())) {
if (shouldKeepRun(r, lsb, lstb)) {
continue;
}
LOGGER.log(FINE, "{0} is to be removed", r);
try { r.delete(); }
catch (IOException ex) { exceptionMap.computeIfAbsent(r, key -> new HashSet<>()).add(ex); }
}
}
if(daysToKeep!=-1) {
Calendar cal = new GregorianCalendar();
cal.add(Calendar.DAY_OF_YEAR,-daysToKeep);
Run r = job.getFirstBuild();
while (r != null) {
if (tooNew(r, cal)) {
break;
}
if (!shouldKeepRun(r, lsb, lstb)) {
LOGGER.log(FINE, "{0} is to be removed", r);
try { r.delete(); }
catch (IOException ex) { exceptionMap.computeIfAbsent(r, key -> new HashSet<>()).add(ex); }
}
r = r.getNextBuild();
}
}
Answered By - Chris Maggiulli