/* * The MIT License * * Copyright (c) 2004-2009, Sun Microsystems, Inc., Kohsuke Kawaguchi, Martin Eigenbrodt * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ package hudson.tasks; import hudson.model.Describable; import hudson.model.Descriptor; import hudson.model.Job; import hudson.model.Run; import hudson.scm.SCM; import org.kohsuke.stapler.DataBoundConstructor; import java.io.IOException; import java.util.Calendar; import java.util.GregorianCalendar; import static java.util.logging.Level.FINE; import static java.util.logging.Level.FINER; import java.util.List; import java.util.logging.Logger; /** * Deletes old log files. * * TODO: is there any other task that follows the same pattern? * try to generalize this just like {@link SCM} or {@link BuildStep}. * * @author Kohsuke Kawaguchi */ public class LogRotator implements Describable<LogRotator> { /** * If not -1, history is only kept up to this days. */ private final int daysToKeep; /** * If not -1, only this number of build logs are kept. */ private final int numToKeep; /** * If not -1 nor null, artifacts are only kept up to this days. * Null handling is necessary to remain data compatible with old versions. * @since 1.350 */ private final Integer artifactDaysToKeep; /** * If not -1 nor null, only this number of builds have their artifacts kept. * Null handling is necessary to remain data compatible with old versions. * @since 1.350 */ private final Integer artifactNumToKeep; @DataBoundConstructor public LogRotator (String logrotate_days, String logrotate_nums, String logrotate_artifact_days, String logrotate_artifact_nums) { this (parse(logrotate_days),parse(logrotate_nums), parse(logrotate_artifact_days),parse(logrotate_artifact_nums)); } public static int parse(String p) { if(p==null) return -1; try { return Integer.parseInt(p); } catch (NumberFormatException e) { return -1; } } /** * @deprecated since 1.350. * Use {@link #LogRotator(int, int, int, int)} */ public LogRotator(int daysToKeep, int numToKeep) { this(daysToKeep, numToKeep, -1, -1); } public LogRotator(int daysToKeep, int numToKeep, int artifactDaysToKeep, int artifactNumToKeep) { this.daysToKeep = daysToKeep; this.numToKeep = numToKeep; this.artifactDaysToKeep = artifactDaysToKeep; this.artifactNumToKeep = artifactNumToKeep; } public void perform(Job<?,?> job) throws IOException, InterruptedException { LOGGER.log(FINE,"Running the log rotation for "+job.getFullDisplayName()); // keep the last successful build regardless of the status Run lsb = job.getLastSuccessfulBuild(); Run lstb = job.getLastStableBuild(); if(numToKeep!=-1) { List<? extends Run<?,?>> builds = job.getBuilds(); for (Run r : builds.subList(Math.min(builds.size(),numToKeep),builds.size())) { if (r.isKeepLog()) { LOGGER.log(FINER,r.getFullDisplayName()+" is not GC-ed because it's marked as a keeper"); continue; } if (r==lsb) { LOGGER.log(FINER,r.getFullDisplayName()+" is not GC-ed because it's the last successful build"); continue; } if (r==lstb) { LOGGER.log(FINER,r.getFullDisplayName()+" is not GC-ed because it's the last stable build"); continue; } LOGGER.log(FINER,r.getFullDisplayName()+" is to be removed"); r.delete(); } } if(daysToKeep!=-1) { Calendar cal = new GregorianCalendar(); cal.add(Calendar.DAY_OF_YEAR,-daysToKeep); // copy it to the array because we'll be deleting builds as we go. for( Run r : job.getBuilds() ) { if (r.isKeepLog()) { LOGGER.log(FINER,r.getFullDisplayName()+" is not GC-ed because it's marked as a keeper"); continue; } if (r==lsb) { LOGGER.log(FINER,r.getFullDisplayName()+" is not GC-ed because it's the last successful build"); continue; } if (r==lstb) { LOGGER.log(FINER,r.getFullDisplayName()+" is not GC-ed because it's the last stable build"); continue; } if (!r.getTimestamp().before(cal)) { LOGGER.log(FINER,r.getFullDisplayName()+" is not GC-ed because it's still new"); continue; } LOGGER.log(FINER,r.getFullDisplayName()+" is to be removed"); r.delete(); } } if(artifactNumToKeep!=null && artifactNumToKeep!=-1) { List<? extends Run<?,?>> builds = job.getBuilds(); for (Run r : builds.subList(Math.min(builds.size(),artifactNumToKeep),builds.size())) { if (r.isKeepLog()) { LOGGER.log(FINER,r.getFullDisplayName()+" is not purged of artifacts because it's marked as a keeper"); continue; } if (r==lsb) { LOGGER.log(FINER,r.getFullDisplayName()+" is not purged of artifacts because it's the last successful build"); continue; } if (r==lstb) { LOGGER.log(FINER,r.getFullDisplayName()+" is not purged of artifacts because it's the last stable build"); continue; } r.deleteArtifacts(); } } if(artifactDaysToKeep!=null && artifactDaysToKeep!=-1) { Calendar cal = new GregorianCalendar(); cal.add(Calendar.DAY_OF_YEAR,-artifactDaysToKeep); // copy it to the array because we'll be deleting builds as we go. for( Run r : job.getBuilds() ) { if (r.isKeepLog()) { LOGGER.log(FINER,r.getFullDisplayName()+" is not purged of artifacts because it's marked as a keeper"); continue; } if (r==lsb) { LOGGER.log(FINER,r.getFullDisplayName()+" is not purged of artifacts because it's the last successful build"); continue; } if (r==lstb) { LOGGER.log(FINER,r.getFullDisplayName()+" is not purged of artifacts because it's the last stable build"); continue; } if (!r.getTimestamp().before(cal)) { LOGGER.log(FINER,r.getFullDisplayName()+" is not purged of artifacts because it's still new"); continue; } r.deleteArtifacts(); } } } public int getDaysToKeep() { return daysToKeep; } public int getNumToKeep() { return numToKeep; } public int getArtifactDaysToKeep() { return unbox(artifactDaysToKeep); } public int getArtifactNumToKeep() { return unbox(artifactNumToKeep); } public String getDaysToKeepStr() { return toString(daysToKeep); } public String getNumToKeepStr() { return toString(numToKeep); } public String getArtifactDaysToKeepStr() { return toString(artifactDaysToKeep); } public String getArtifactNumToKeepStr() { return toString(artifactNumToKeep); } private int unbox(Integer i) { return i==null ? -1: i; } private String toString(Integer i) { if (i==null || i==-1) return ""; return String.valueOf(i); } public LRDescriptor getDescriptor() { return DESCRIPTOR; } public static final LRDescriptor DESCRIPTOR = new LRDescriptor(); public static final class LRDescriptor extends Descriptor<LogRotator> { public String getDisplayName() { return "Log Rotation"; } } private static final Logger LOGGER = Logger.getLogger(LogRotator.class.getName()); }