001/** 002 * Logback: the reliable, generic, fast and flexible logging framework. 003 * Copyright (C) 1999-2015, QOS.ch. All rights reserved. 004 * 005 * This program and the accompanying materials are dual-licensed under 006 * either the terms of the Eclipse Public License v1.0 as published by 007 * the Eclipse Foundation 008 * 009 * or (per the licensee's choosing) 010 * 011 * under the terms of the GNU Lesser General Public License version 2.1 012 * as published by the Free Software Foundation. 013 */ 014package ch.qos.logback.core.rolling; 015 016import static ch.qos.logback.core.CoreConstants.UNBOUNDED_HISTORY; 017import static ch.qos.logback.core.CoreConstants.UNBOUNDED_TOTAL_SIZE_CAP; 018 019import java.io.File; 020import java.util.Date; 021import java.util.concurrent.Future; 022import java.util.concurrent.TimeUnit; 023import java.util.concurrent.TimeoutException; 024 025import ch.qos.logback.core.CoreConstants; 026import ch.qos.logback.core.rolling.helper.ArchiveRemover; 027import ch.qos.logback.core.rolling.helper.CompressionMode; 028import ch.qos.logback.core.rolling.helper.Compressor; 029import ch.qos.logback.core.rolling.helper.FileFilterUtil; 030import ch.qos.logback.core.rolling.helper.FileNamePattern; 031import ch.qos.logback.core.rolling.helper.RenameUtil; 032import ch.qos.logback.core.util.FileSize; 033 034/** 035 * <code>TimeBasedRollingPolicy</code> is both easy to configure and quite 036 * powerful. It allows the rollover to be made based on time. It is possible to 037 * specify that the rollover occur once per day, per week or per month. 038 * 039 * <p> 040 * For more information, please refer to the online manual at 041 * http://logback.qos.ch/manual/appenders.html#TimeBasedRollingPolicy 042 * 043 * @author Ceki Gülcü 044 */ 045public class TimeBasedRollingPolicy<E> extends RollingPolicyBase implements TriggeringPolicy<E> { 046 static final String FNP_NOT_SET = "The FileNamePattern option must be set before using TimeBasedRollingPolicy. "; 047 // WCS: without compression suffix 048 FileNamePattern fileNamePatternWithoutCompSuffix; 049 050 private Compressor compressor; 051 private RenameUtil renameUtil = new RenameUtil(); 052 Future<?> compressionFuture; 053 Future<?> cleanUpFuture; 054 055 private int maxHistory = UNBOUNDED_HISTORY; 056 protected FileSize totalSizeCap = new FileSize(UNBOUNDED_TOTAL_SIZE_CAP); 057 058 private ArchiveRemover archiveRemover; 059 060 TimeBasedFileNamingAndTriggeringPolicy<E> timeBasedFileNamingAndTriggeringPolicy; 061 062 boolean cleanHistoryOnStart = false; 063 064 public void start() { 065 // set the LR for our utility object 066 renameUtil.setContext(this.context); 067 068 // find out period from the filename pattern 069 if (fileNamePatternStr != null) { 070 fileNamePattern = new FileNamePattern(fileNamePatternStr, this.context); 071 determineCompressionMode(); 072 } else { 073 addWarn(FNP_NOT_SET); 074 addWarn(CoreConstants.SEE_FNP_NOT_SET); 075 throw new IllegalStateException(FNP_NOT_SET + CoreConstants.SEE_FNP_NOT_SET); 076 } 077 078 compressor = new Compressor(compressionMode); 079 compressor.setContext(context); 080 081 // wcs : without compression suffix 082 fileNamePatternWithoutCompSuffix = new FileNamePattern( 083 Compressor.computeFileNameStrWithoutCompSuffix(fileNamePatternStr, compressionMode), this.context); 084 085 addInfo("Will use the pattern " + fileNamePatternWithoutCompSuffix + " for the active file"); 086 087 if (compressionMode == CompressionMode.ZIP) { 088 String zipEntryFileNamePatternStr = transformFileNamePattern2ZipEntry(fileNamePatternStr); 089 zipEntryFileNamePattern = new FileNamePattern(zipEntryFileNamePatternStr, context); 090 } 091 092 if (timeBasedFileNamingAndTriggeringPolicy == null) { 093 timeBasedFileNamingAndTriggeringPolicy = new DefaultTimeBasedFileNamingAndTriggeringPolicy<>(); 094 } 095 timeBasedFileNamingAndTriggeringPolicy.setContext(context); 096 timeBasedFileNamingAndTriggeringPolicy.setTimeBasedRollingPolicy(this); 097 timeBasedFileNamingAndTriggeringPolicy.start(); 098 099 if (!timeBasedFileNamingAndTriggeringPolicy.isStarted()) { 100 addWarn("Subcomponent did not start. TimeBasedRollingPolicy will not start."); 101 return; 102 } 103 104 // the maxHistory property is given to TimeBasedRollingPolicy instead of to 105 // the TimeBasedFileNamingAndTriggeringPolicy. This makes it more convenient 106 // for the user at the cost of inconsistency here. 107 if (maxHistory != UNBOUNDED_HISTORY) { 108 archiveRemover = timeBasedFileNamingAndTriggeringPolicy.getArchiveRemover(); 109 archiveRemover.setMaxHistory(maxHistory); 110 archiveRemover.setTotalSizeCap(totalSizeCap.getSize()); 111 if (cleanHistoryOnStart) { 112 addInfo("Cleaning on start up"); 113 Date now = new Date(timeBasedFileNamingAndTriggeringPolicy.getCurrentTime()); 114 cleanUpFuture = archiveRemover.cleanAsynchronously(now); 115 } 116 } else if (!isUnboundedTotalSizeCap()) { 117 addWarn("'maxHistory' is not set, ignoring 'totalSizeCap' option with value [" + totalSizeCap + "]"); 118 } 119 120 super.start(); 121 } 122 123 protected boolean isUnboundedTotalSizeCap() { 124 return totalSizeCap.getSize() == UNBOUNDED_TOTAL_SIZE_CAP; 125 } 126 127 @Override 128 public void stop() { 129 if (!isStarted()) 130 return; 131 waitForAsynchronousJobToStop(compressionFuture, "compression"); 132 waitForAsynchronousJobToStop(cleanUpFuture, "clean-up"); 133 super.stop(); 134 } 135 136 private void waitForAsynchronousJobToStop(Future<?> aFuture, String jobDescription) { 137 if (aFuture != null) { 138 try { 139 aFuture.get(CoreConstants.SECONDS_TO_WAIT_FOR_COMPRESSION_JOBS, TimeUnit.SECONDS); 140 } catch (TimeoutException e) { 141 addError("Timeout while waiting for " + jobDescription + " job to finish", e); 142 } catch (Exception e) { 143 addError("Unexpected exception while waiting for " + jobDescription + " job to finish", e); 144 } 145 } 146 } 147 148 private String transformFileNamePattern2ZipEntry(String fileNamePatternStr) { 149 String slashified = FileFilterUtil.slashify(fileNamePatternStr); 150 return FileFilterUtil.afterLastSlash(slashified); 151 } 152 153 public void setTimeBasedFileNamingAndTriggeringPolicy( 154 TimeBasedFileNamingAndTriggeringPolicy<E> timeBasedTriggering) { 155 this.timeBasedFileNamingAndTriggeringPolicy = timeBasedTriggering; 156 } 157 158 public TimeBasedFileNamingAndTriggeringPolicy<E> getTimeBasedFileNamingAndTriggeringPolicy() { 159 return timeBasedFileNamingAndTriggeringPolicy; 160 } 161 162 public void rollover() throws RolloverFailure { 163 164 // when rollover is called the elapsed period's file has 165 // been already closed. This is a working assumption of this method. 166 167 String elapsedPeriodsFileName = timeBasedFileNamingAndTriggeringPolicy.getElapsedPeriodsFileName(); 168 169 String elapsedPeriodStem = FileFilterUtil.afterLastSlash(elapsedPeriodsFileName); 170 171 if (compressionMode == CompressionMode.NONE) { 172 if (getParentsRawFileProperty() != null) { 173 renameUtil.rename(getParentsRawFileProperty(), elapsedPeriodsFileName); 174 } // else { nothing to do if CompressionMode == NONE and parentsRawFileProperty == 175 // null } 176 } else { 177 if (getParentsRawFileProperty() == null) { 178 compressionFuture = compressor.asyncCompress(elapsedPeriodsFileName, elapsedPeriodsFileName, 179 elapsedPeriodStem); 180 } else { 181 compressionFuture = renameRawAndAsyncCompress(elapsedPeriodsFileName, elapsedPeriodStem); 182 } 183 } 184 185 if (archiveRemover != null) { 186 Date now = new Date(timeBasedFileNamingAndTriggeringPolicy.getCurrentTime()); 187 this.cleanUpFuture = archiveRemover.cleanAsynchronously(now); 188 } 189 } 190 191 Future<?> renameRawAndAsyncCompress(String nameOfCompressedFile, String innerEntryName) throws RolloverFailure { 192 String parentsRawFile = getParentsRawFileProperty(); 193 String tmpTarget = nameOfCompressedFile + System.nanoTime() + ".tmp"; 194 renameUtil.rename(parentsRawFile, tmpTarget); 195 return compressor.asyncCompress(tmpTarget, nameOfCompressedFile, innerEntryName); 196 } 197 198 /** 199 * 200 * The active log file is determined by the value of the parent's filename 201 * option. However, in case the file name is left blank, then, the active log 202 * file equals the file name for the current period as computed by the 203 * <b>FileNamePattern</b> option. 204 * 205 * <p> 206 * The RollingPolicy must know whether it is responsible for changing the name 207 * of the active file or not. If the active file name is set by the user via the 208 * configuration file, then the RollingPolicy must let it like it is. If the 209 * user does not specify an active file name, then the RollingPolicy generates 210 * one. 211 * 212 * <p> 213 * To be sure that the file name used by the parent class has been generated by 214 * the RollingPolicy and not specified by the user, we keep track of the last 215 * generated name object and compare its reference to the parent file name. If 216 * they match, then the RollingPolicy knows it's responsible for the change of 217 * the file name. 218 * 219 */ 220 public String getActiveFileName() { 221 String parentsRawFileProperty = getParentsRawFileProperty(); 222 if (parentsRawFileProperty != null) { 223 return parentsRawFileProperty; 224 } else { 225 return timeBasedFileNamingAndTriggeringPolicy.getCurrentPeriodsFileNameWithoutCompressionSuffix(); 226 } 227 } 228 229 public boolean isTriggeringEvent(File activeFile, final E event) { 230 return timeBasedFileNamingAndTriggeringPolicy.isTriggeringEvent(activeFile, event); 231 } 232 233 /** 234 * Get the number of archive files to keep. 235 * 236 * @return number of archive files to keep 237 */ 238 public int getMaxHistory() { 239 return maxHistory; 240 } 241 242 /** 243 * Set the maximum number of archive files to keep. 244 * 245 * @param maxHistory number of archive files to keep 246 */ 247 public void setMaxHistory(int maxHistory) { 248 this.maxHistory = maxHistory; 249 } 250 251 public boolean isCleanHistoryOnStart() { 252 return cleanHistoryOnStart; 253 } 254 255 /** 256 * Should archive removal be attempted on application start up? Default is 257 * false. 258 * 259 * @since 1.0.1 260 * @param cleanHistoryOnStart 261 */ 262 public void setCleanHistoryOnStart(boolean cleanHistoryOnStart) { 263 this.cleanHistoryOnStart = cleanHistoryOnStart; 264 } 265 266 @Override 267 public String toString() { 268 return "c.q.l.core.rolling.TimeBasedRollingPolicy@" + this.hashCode(); 269 } 270 271 public void setTotalSizeCap(FileSize totalSizeCap) { 272 addInfo("setting totalSizeCap to " + totalSizeCap.toString()); 273 this.totalSizeCap = totalSizeCap; 274 } 275}