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.UNBOUNDED_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 rollover to be made based on time. It is possible to
37   * specify that the rollover occur once per day, per week or per month.
38   * 
39   * <p>
40   * For more information, please refer to the online manual at
41   * http://logback.qos.ch/manual/appenders.html#TimeBasedRollingPolicy
42   * 
43   * @author Ceki G&uuml;lc&uuml;
44   */
45  public class TimeBasedRollingPolicy<E> extends RollingPolicyBase implements TriggeringPolicy<E> {
46      static final String FNP_NOT_SET = "The FileNamePattern option must be set before using TimeBasedRollingPolicy. ";
47      // WCS: without compression suffix
48      FileNamePattern fileNamePatternWithoutCompSuffix;
49  
50      private Compressor compressor;
51      private RenameUtil renameUtil = new RenameUtil();
52      Future<?> compressionFuture;
53      Future<?> cleanUpFuture;
54  
55      private int maxHistory = UNBOUNDED_HISTORY;
56      protected FileSize totalSizeCap = new FileSize(UNBOUNDED_TOTAL_SIZE_CAP);
57  
58      private ArchiveRemover archiveRemover;
59  
60      TimeBasedFileNamingAndTriggeringPolicy<E> timeBasedFileNamingAndTriggeringPolicy;
61  
62      boolean cleanHistoryOnStart = false;
63  
64      public void start() {
65          // set the LR for our utility object
66          renameUtil.setContext(this.context);
67  
68          // find out period from the filename pattern
69          if (fileNamePatternStr != null) {
70              fileNamePattern = new FileNamePattern(fileNamePatternStr, this.context);
71              determineCompressionMode();
72          } else {
73              addWarn(FNP_NOT_SET);
74              addWarn(CoreConstants.SEE_FNP_NOT_SET);
75              throw new IllegalStateException(FNP_NOT_SET + CoreConstants.SEE_FNP_NOT_SET);
76          }
77  
78          compressor = new Compressor(compressionMode);
79          compressor.setContext(context);
80  
81          // wcs : without compression suffix
82          fileNamePatternWithoutCompSuffix = new FileNamePattern(
83                  Compressor.computeFileNameStrWithoutCompSuffix(fileNamePatternStr, compressionMode), this.context);
84  
85          addInfo("Will use the pattern " + fileNamePatternWithoutCompSuffix + " for the active file");
86  
87          if (compressionMode == CompressionMode.ZIP) {
88              String zipEntryFileNamePatternStr = transformFileNamePattern2ZipEntry(fileNamePatternStr);
89              zipEntryFileNamePattern = new FileNamePattern(zipEntryFileNamePatternStr, context);
90          }
91  
92          if (timeBasedFileNamingAndTriggeringPolicy == null) {
93              timeBasedFileNamingAndTriggeringPolicy = new DefaultTimeBasedFileNamingAndTriggeringPolicy<>();
94          }
95          timeBasedFileNamingAndTriggeringPolicy.setContext(context);
96          timeBasedFileNamingAndTriggeringPolicy.setTimeBasedRollingPolicy(this);
97          timeBasedFileNamingAndTriggeringPolicy.start();
98  
99          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 }