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