View Javadoc
1   /**
2    * Logback: the reliable, generic, fast and flexible logging framework.
3    * Copyright (C) 1999-2015, QOS.ch. All rights reserved.
4    *
5    * This program and the accompanying materials are dual-licensed under
6    * either the terms of the Eclipse Public License v1.0 as published by
7    * the Eclipse Foundation
8    *
9    *   or (per the licensee's choosing)
10   *
11   * under the terms of the GNU Lesser General Public License version 2.1
12   * as published by the Free Software Foundation.
13   */
14  package ch.qos.logback.core.rolling;
15  
16  import static ch.qos.logback.core.CoreConstants.UNBOUND_HISTORY;
17  import static ch.qos.logback.core.CoreConstants.UNBOUNDED_TOTAL_SIZE_CAP;
18  
19  import java.io.File;
20  import java.util.Date;
21  import java.util.concurrent.Future;
22  import java.util.concurrent.TimeUnit;
23  import java.util.concurrent.TimeoutException;
24  
25  import ch.qos.logback.core.CoreConstants;
26  import ch.qos.logback.core.rolling.helper.ArchiveRemover;
27  import ch.qos.logback.core.rolling.helper.CompressionMode;
28  import ch.qos.logback.core.rolling.helper.Compressor;
29  import ch.qos.logback.core.rolling.helper.FileFilterUtil;
30  import ch.qos.logback.core.rolling.helper.FileNamePattern;
31  import ch.qos.logback.core.rolling.helper.RenameUtil;
32  import ch.qos.logback.core.util.FileSize;
33  
34  /**
35   * <code>TimeBasedRollingPolicy</code> is both easy to configure and quite
36   * powerful. It allows the roll over to be made based on time. It is possible to
37   * specify that the roll over occur once per day, per week or per month.
38   * 
39   * <p>For more information, please refer to the online manual at
40   * http://logback.qos.ch/manual/appenders.html#TimeBasedRollingPolicy
41   * 
42   * @author Ceki G&uuml;lc&uuml;
43   */
44  public class TimeBasedRollingPolicy<E> extends RollingPolicyBase implements TriggeringPolicy<E> {
45      static final String FNP_NOT_SET = "The FileNamePattern option must be set before using TimeBasedRollingPolicy. ";
46      // WCS: without compression suffix
47      FileNamePattern fileNamePatternWithoutCompSuffix;
48  
49      private Compressor compressor;
50      private RenameUtil renameUtil = new RenameUtil();
51      Future<?> compressionFuture;
52      Future<?> cleanUpFuture;
53  
54      private int maxHistory = UNBOUND_HISTORY;
55      protected FileSize totalSizeCap = new FileSize(UNBOUNDED_TOTAL_SIZE_CAP);
56  
57      private ArchiveRemover archiveRemover;
58  
59      TimeBasedFileNamingAndTriggeringPolicy<E> timeBasedFileNamingAndTriggeringPolicy;
60  
61      boolean cleanHistoryOnStart = false;
62  
63      public void start() {
64          // set the LR for our utility object
65          renameUtil.setContext(this.context);
66  
67          // find out period from the filename pattern
68          if (fileNamePatternStr != null) {
69              fileNamePattern = new FileNamePattern(fileNamePatternStr, this.context);
70              determineCompressionMode();
71          } else {
72              addWarn(FNP_NOT_SET);
73              addWarn(CoreConstants.SEE_FNP_NOT_SET);
74              throw new IllegalStateException(FNP_NOT_SET + CoreConstants.SEE_FNP_NOT_SET);
75          }
76  
77          compressor = new Compressor(compressionMode);
78          compressor.setContext(context);
79  
80          // wcs : without compression suffix
81          fileNamePatternWithoutCompSuffix = new FileNamePattern(Compressor.computeFileNameStrWithoutCompSuffix(fileNamePatternStr, compressionMode), this.context);
82  
83          addInfo("Will use the pattern " + fileNamePatternWithoutCompSuffix + " for the active file");
84  
85          if (compressionMode == CompressionMode.ZIP) {
86              String zipEntryFileNamePatternStr = transformFileNamePattern2ZipEntry(fileNamePatternStr);
87              zipEntryFileNamePattern = new FileNamePattern(zipEntryFileNamePatternStr, context);
88          }
89  
90          if (timeBasedFileNamingAndTriggeringPolicy == null) {
91              timeBasedFileNamingAndTriggeringPolicy = new DefaultTimeBasedFileNamingAndTriggeringPolicy<E>();
92          }
93          timeBasedFileNamingAndTriggeringPolicy.setContext(context);
94          timeBasedFileNamingAndTriggeringPolicy.setTimeBasedRollingPolicy(this);
95          timeBasedFileNamingAndTriggeringPolicy.start();
96  
97          if (!timeBasedFileNamingAndTriggeringPolicy.isStarted()) {
98              addWarn("Subcomponent did not start. TimeBasedRollingPolicy will not start.");
99              return;
100         }
101 
102         // the maxHistory property is given to TimeBasedRollingPolicy instead of to
103         // the TimeBasedFileNamingAndTriggeringPolicy. This makes it more convenient
104         // for the user at the cost of inconsistency here.
105         if (maxHistory != UNBOUND_HISTORY) {
106             archiveRemover = timeBasedFileNamingAndTriggeringPolicy.getArchiveRemover();
107             archiveRemover.setMaxHistory(maxHistory);
108             archiveRemover.setTotalSizeCap(totalSizeCap.getSize());
109             if (cleanHistoryOnStart) {
110                 addInfo("Cleaning on start up");
111                 Date now = new Date(timeBasedFileNamingAndTriggeringPolicy.getCurrentTime());
112                 cleanUpFuture = archiveRemover.cleanAsynchronously(now);
113             }
114         } else if (!isUnboundedTotalSizeCap()) {
115             addWarn("'maxHistory' is not set, ignoring 'totalSizeCap' option with value ["+totalSizeCap+"]");
116         }
117 
118         super.start();
119     }
120 
121     protected boolean isUnboundedTotalSizeCap() {
122         return totalSizeCap.getSize() == UNBOUNDED_TOTAL_SIZE_CAP;
123     }
124 
125     @Override
126     public void stop() {
127         if (!isStarted())
128             return;
129         waitForAsynchronousJobToStop(compressionFuture, "compression");
130         waitForAsynchronousJobToStop(cleanUpFuture, "clean-up");
131         super.stop();
132     }
133 
134     private void waitForAsynchronousJobToStop(Future<?> aFuture, String jobDescription) {
135         if (aFuture != null) {
136             try {
137                 aFuture.get(CoreConstants.SECONDS_TO_WAIT_FOR_COMPRESSION_JOBS, TimeUnit.SECONDS);
138             } catch (TimeoutException e) {
139                 addError("Timeout while waiting for " + jobDescription + " job to finish", e);
140             } catch (Exception e) {
141                 addError("Unexpected exception while waiting for " + jobDescription + " job to finish", e);
142             }
143         }
144     }
145 
146     private String transformFileNamePattern2ZipEntry(String fileNamePatternStr) {
147         String slashified = FileFilterUtil.slashify(fileNamePatternStr);
148         return FileFilterUtil.afterLastSlash(slashified);
149     }
150 
151     public void setTimeBasedFileNamingAndTriggeringPolicy(TimeBasedFileNamingAndTriggeringPolicy<E> timeBasedTriggering) {
152         this.timeBasedFileNamingAndTriggeringPolicy = timeBasedTriggering;
153     }
154 
155     public TimeBasedFileNamingAndTriggeringPolicy<E> getTimeBasedFileNamingAndTriggeringPolicy() {
156         return timeBasedFileNamingAndTriggeringPolicy;
157     }
158 
159     public void rollover() throws RolloverFailure {
160 
161         // when rollover is called the elapsed period's file has
162         // been already closed. This is a working assumption of this method.
163 
164         String elapsedPeriodsFileName = timeBasedFileNamingAndTriggeringPolicy.getElapsedPeriodsFileName();
165 
166         String elapsedPeriodStem = FileFilterUtil.afterLastSlash(elapsedPeriodsFileName);
167 
168         if (compressionMode == CompressionMode.NONE) {
169             if (getParentsRawFileProperty() != null) {
170                 renameUtil.rename(getParentsRawFileProperty(), elapsedPeriodsFileName);
171             } // else { nothing to do if CompressionMode == NONE and parentsRawFileProperty == null }
172         } else {
173             if (getParentsRawFileProperty() == null) {
174                 compressionFuture = compressor.asyncCompress(elapsedPeriodsFileName, elapsedPeriodsFileName, elapsedPeriodStem);
175             } else {
176                 compressionFuture = renameRawAndAsyncCompress(elapsedPeriodsFileName, elapsedPeriodStem);
177             }
178         }
179 
180         if (archiveRemover != null) {
181             Date now = new Date(timeBasedFileNamingAndTriggeringPolicy.getCurrentTime());
182             this.cleanUpFuture = archiveRemover.cleanAsynchronously(now);
183         }
184     }
185 
186     Future<?> renameRawAndAsyncCompress(String nameOfCompressedFile, String innerEntryName) throws RolloverFailure {
187         String parentsRawFile = getParentsRawFileProperty();
188         String tmpTarget = nameOfCompressedFile + System.nanoTime() + ".tmp";
189         renameUtil.rename(parentsRawFile, tmpTarget);
190         return compressor.asyncCompress(tmpTarget, nameOfCompressedFile, innerEntryName);
191     }
192 
193     /**
194      * 
195      * The active log file is determined by the value of the parent's filename
196      * option. However, in case the file name is left blank, then, the active log
197      * file equals the file name for the current period as computed by the
198      * <b>FileNamePattern</b> option.
199      * 
200      * <p>The RollingPolicy must know whether it is responsible for changing the
201      * name of the active file or not. If the active file name is set by the user
202      * via the configuration file, then the RollingPolicy must let it like it is.
203      * If the user does not specify an active file name, then the RollingPolicy
204      * generates one.
205      * 
206      * <p> To be sure that the file name used by the parent class has been
207      * generated by the RollingPolicy and not specified by the user, we keep track
208      * of the last generated name object and compare its reference to the parent
209      * file name. If they match, then the RollingPolicy knows it's responsible for
210      * the change of the file name.
211      * 
212      */
213     public String getActiveFileName() {
214         String parentsRawFileProperty = getParentsRawFileProperty();
215         if (parentsRawFileProperty != null) {
216             return parentsRawFileProperty;
217         } else {
218             return timeBasedFileNamingAndTriggeringPolicy.getCurrentPeriodsFileNameWithoutCompressionSuffix();
219         }
220     }
221 
222     public boolean isTriggeringEvent(File activeFile, final E event) {
223         return timeBasedFileNamingAndTriggeringPolicy.isTriggeringEvent(activeFile, event);
224     }
225 
226     /**
227      * Get the number of archive files to keep.
228      * 
229      * @return number of archive files to keep
230      */
231     public int getMaxHistory() {
232         return maxHistory;
233     }
234 
235     /**
236      * Set the maximum number of archive files to keep.
237      * 
238      * @param maxHistory
239      *                number of archive files to keep
240      */
241     public void setMaxHistory(int maxHistory) {
242         this.maxHistory = maxHistory;
243     }
244 
245     public boolean isCleanHistoryOnStart() {
246         return cleanHistoryOnStart;
247     }
248 
249     /**
250      * Should archive removal be attempted on application start up? Default is false.
251      * @since 1.0.1
252      * @param cleanHistoryOnStart
253      */
254     public void setCleanHistoryOnStart(boolean cleanHistoryOnStart) {
255         this.cleanHistoryOnStart = cleanHistoryOnStart;
256     }
257 
258     @Override
259     public String toString() {
260         return "c.q.l.core.rolling.TimeBasedRollingPolicy@"+this.hashCode();
261     }
262 
263     public void setTotalSizeCap(FileSize totalSizeCap) {
264         this.totalSizeCap = totalSizeCap;
265     }
266 }