/** * ***************************************************************************** * * Copyright (c) 2004-2009 Oracle Corporation. * * All rights reserved. This program and the accompanying materials are made * available under the terms of the Eclipse Public License v1.0 which * accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * * Kohsuke Kawaguchi, Martin Eigenbrodt * * ****************************************************************************** */ package hudson.tasks; import hudson.model.BuildHistory; import hudson.model.BuildHistory.Record; import hudson.model.Describable; import hudson.model.Descriptor; import hudson.model.Job; import hudson.model.Run; import hudson.scm.SCM; import java.io.IOException; import java.util.ArrayList; import java.util.Calendar; import java.util.GregorianCalendar; import java.util.List; import static java.util.logging.Level.FINE; import static java.util.logging.Level.FINER; import java.util.logging.Logger; import org.kohsuke.stapler.DataBoundConstructor; /** * 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> { private static final Logger LOGGER = Logger.getLogger(LogRotator.class.getName()); /** * 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; } } /** * @param daysToKeep * @param numToKeep * @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 {0}", job.getFullDisplayName()); BuildHistory bh = job.getBuildHistoryData(); List<Record> allRecords = bh.allRecords(); Record lsb = bh.getLastSuccessful(); Record lstb = bh.getLastStable(); // keep the last successful build regardless of the status List<Record> subrecords = new ArrayList(allRecords); Calendar cal = null; //Delete builds if (-1 != numToKeep || -1 != daysToKeep) { if (-1 != daysToKeep) { cal = new GregorianCalendar(); cal.add(Calendar.DAY_OF_YEAR, -daysToKeep); } if (-1 != numToKeep) { subrecords = allRecords.subList(Math.min(allRecords.size(), numToKeep), allRecords.size()); } //Delete builds based on configured values. See http://issues.hudson-ci.org/browse/HUDSON-3650 deleteBuilds(subrecords, lsb, lstb, cal); } cal = null; //Delete build artifacts if (-1 != artifactNumToKeep || -1 != artifactDaysToKeep) { if (-1 != artifactDaysToKeep) { cal = new GregorianCalendar(); cal.add(Calendar.DAY_OF_YEAR, -artifactDaysToKeep); } if (-1 != artifactNumToKeep) { subrecords = allRecords.subList(Math.min(allRecords.size(), artifactNumToKeep), allRecords.size()); } //Delete build artifacts based on configured values. See http://issues.hudson-ci.org/browse/HUDSON-3650 deleteBuildArtifacts(subrecords, lsb, lstb, cal); } } /** * Performs builds deletion * * @param builds list of builds * @param lastSuccessBuild last success build * @param lastStableBuild last stable build * @param cal calendar if configured * @throws IOException if configured */ private void deleteBuilds(List<Record> subrecords, Record lastSuccessBuild, Record lastStableBuild, Calendar cal) throws IOException { for (Record currentBuild : subrecords) { if (allowDeleteBuild(lastSuccessBuild, lastStableBuild, currentBuild, cal)) { Run currentRun = currentBuild.getBuild(); if (currentRun.isKeepLog()) { LOGGER.log(FINER, "{0} is not GC-ed because it''s marked as a keeper", currentBuild.getFullDisplayName()); } else { LOGGER.log(FINER, "{0} is to be removed", currentBuild.getFullDisplayName()); currentRun.delete(); } } } } /** * Checks whether current build could be deleted. If current build equals to * last Success Build or last Stable Build or currentBuild is configured to * keep logs or currentBuild timestamp is before configured calendar value - * return false, otherwise return true. * * @param lastSuccessBuild {@link Run} * @param lastStableBuild {@link Run} * @param currentBuild {@link Run} * @param cal {@link Calendar} * @return true - if deletion is allowed, false - otherwise. */ private boolean allowDeleteBuild(Record lastSuccessBuild, Record lastStableBuild, Record currentBuild, Calendar cal) { if (currentBuild == lastSuccessBuild) { LOGGER.log(FINER, "{0} is not GC-ed because it''s the last successful build", currentBuild.getFullDisplayName()); return false; } if (currentBuild == lastStableBuild) { LOGGER.log(FINER, "{0} is not GC-ed because it''s the last stable build", currentBuild.getFullDisplayName()); return false; } if (null != cal && !currentBuild.getTimestamp().before(cal)) { LOGGER.log(FINER, "{0} is not GC-ed because it''s still new", currentBuild.getFullDisplayName()); return false; } return true; } /** * Performs build artifacts deletion * * @param builds list of builds * @param lastSuccessBuild last success build * @param lastStableBuild last stable build * @param cal calendar if configured * @throws IOException if configured */ private void deleteBuildArtifacts(List<Record> subrecords, Record lastSuccessBuild, Record lastStableBuild, Calendar cal) throws IOException { for (Record currentBuild : subrecords) { if (allowDeleteArtifact(lastSuccessBuild, lastStableBuild, currentBuild, cal)) { Run currentRun = currentBuild.getBuild(); if (currentRun.isKeepLog()) { LOGGER.log(FINER, "{0} is not purged of artifacts because it''s marked as a keeper", currentBuild.getFullDisplayName()); } else { LOGGER.log(FINER, "Artifacts of {0} to be removed", currentBuild.getFullDisplayName()); currentRun.deleteArtifacts(); } } } } /** * Checks whether artifacts from build could be deleted. If current build * equals to last Success Build or last Stable Build or currentBuild is * configured to keep logs or currentBuild timestamp is before configured * calendar value - return false, otherwise return true. * * @param lastSuccessBuild {@link Run} * @param lastStableBuild {@link Run} * @param currentBuild {@link Run} * @param cal {@link Calendar} * @return true - if deletion is allowed, false - otherwise. */ private boolean allowDeleteArtifact(Record lastSuccessBuild, Record lastStableBuild, Record currentBuild, Calendar cal) { if (currentBuild == lastSuccessBuild) { LOGGER.log(FINER, "{0} is not purged of artifacts because it''s the last successful build", currentBuild.getFullDisplayName()); return false; } if (currentBuild == lastStableBuild) { LOGGER.log(FINER, "{0} is not purged of artifacts because it''s the last stable build", currentBuild.getFullDisplayName()); return false; } if (null != cal && !currentBuild.getTimestamp().before(cal)) { LOGGER.log(FINER, "{0} is not purged of artifacts because it''s still new", currentBuild.getFullDisplayName()); return false; } return true; } 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); } @Override public LRDescriptor getDescriptor() { return DESCRIPTOR; } public static final LRDescriptor DESCRIPTOR = new LRDescriptor(); public static final class LRDescriptor extends Descriptor<LogRotator> { @Override public String getDisplayName() { return "Log Rotation"; } } @Override public boolean equals(Object o) { if (this == o) { return true; } if (o == null || getClass() != o.getClass()) { return false; } LogRotator that = (LogRotator) o; if (daysToKeep != that.daysToKeep) { return false; } if (numToKeep != that.numToKeep) { return false; } if (artifactDaysToKeep != null ? !artifactDaysToKeep.equals(that.artifactDaysToKeep) : that.artifactDaysToKeep != null) { return false; } return !(artifactNumToKeep != null ? !artifactNumToKeep.equals(that.artifactNumToKeep) : that.artifactNumToKeep != null); } @Override public int hashCode() { int result = daysToKeep; result = 31 * result + numToKeep; result = 31 * result + (artifactDaysToKeep != null ? artifactDaysToKeep.hashCode() : 0); result = 31 * result + (artifactNumToKeep != null ? artifactNumToKeep.hashCode() : 0); return result; } }