View Javadoc

1   /**
2    * Logback: the reliable, generic, fast and flexible logging framework.
3    * 
4    * Copyright (C) 1999-2006, QOS.ch
5    * 
6    * This library is free software, you can redistribute it and/or modify it under
7    * the terms of the GNU Lesser General Public License as published by the Free
8    * Software Foundation.
9    */
10  package ch.qos.logback.core.rolling;
11  
12  import java.io.File;
13  import java.util.Date;
14  import java.util.concurrent.Future;
15  
16  import ch.qos.logback.core.rolling.helper.AsynchronousCompressor;
17  import ch.qos.logback.core.rolling.helper.CompressionMode;
18  import ch.qos.logback.core.rolling.helper.Compressor;
19  import ch.qos.logback.core.rolling.helper.DateTokenConverter;
20  import ch.qos.logback.core.rolling.helper.FileNamePattern;
21  import ch.qos.logback.core.rolling.helper.RenameUtil;
22  import ch.qos.logback.core.rolling.helper.RollingCalendar;
23  import ch.qos.logback.core.rolling.helper.TimeBasedCleaner;
24  
25  /**
26   * <code>TimeBasedRollingPolicy</code> is both easy to configure and quite
27   * powerful. It allows the roll over to be made based on time. It is possible to
28   * specify that the roll over occur once per day, per week or per month.
29   * 
30   * <p>For more information, please refer to the online manual at
31   * http://logback.qos.ch/manual/appenders.html#TimeBasedRollingPolicy
32   * 
33   * @author Ceki G&uuml;lc&uuml;
34   */
35  public class TimeBasedRollingPolicy<E> extends RollingPolicyBase implements
36      TriggeringPolicy<E> {
37    static final String FNP_NOT_SET = "The FileNamePattern option must be set before using TimeBasedRollingPolicy. ";
38    static final String SEE_FNP_NOT_SET = "See also http://logback.qos.ch/codes.html#tbr_fnp_not_set";
39    static final int NO_DELETE_HISTORY = 0;
40  
41    RollingCalendar rc;
42    long currentTime;
43    long nextCheck;
44    // indicate whether the time has been forced or not
45    boolean isTimeForced = false;
46    Date lastCheck = null;
47    String elapsedPeriodsFileName;
48    FileNamePattern activeFileNamePattern;
49    RenameUtil util = new RenameUtil();
50    String latestActiveFileName;
51    Future<?> future;
52  
53    int maxHistory = NO_DELETE_HISTORY;
54    TimeBasedCleaner tbCleaner;
55  
56    public void setCurrentTime(long timeInMillis) {
57      currentTime = timeInMillis;
58      isTimeForced = true;
59    }
60  
61    public long getCurrentTime() {
62      // if time is forced return the time set by user
63      if (isTimeForced) {
64        return currentTime;
65      } else {
66        return System.currentTimeMillis();
67      }
68    }
69  
70    public void start() {
71      // set the LR for our utility object
72      util.setContext(this.context);
73  
74      // find out period from the filename pattern
75      if (fileNamePatternStr != null) {
76        fileNamePattern = new FileNamePattern(fileNamePatternStr, this.context);
77        determineCompressionMode();
78      } else {
79        addWarn(FNP_NOT_SET);
80        addWarn(SEE_FNP_NOT_SET);
81        throw new IllegalStateException(FNP_NOT_SET + SEE_FNP_NOT_SET);
82      }
83  
84      DateTokenConverter dtc = fileNamePattern.getDateTokenConverter();
85  
86      if (dtc == null) {
87        throw new IllegalStateException("FileNamePattern ["
88            + fileNamePattern.getPattern()
89            + "] does not contain a valid DateToken");
90      }
91  
92      int len = fileNamePatternStr.length();
93      switch (compressionMode) {
94      case GZ:
95        activeFileNamePattern = new FileNamePattern(fileNamePatternStr.substring(
96            0, len - 3), this.context);
97  
98        break;
99      case ZIP:
100       activeFileNamePattern = new FileNamePattern(fileNamePatternStr.substring(
101           0, len - 4), this.context);
102       break;
103     case NONE:
104       activeFileNamePattern = fileNamePattern;
105     }
106     addInfo("Will use the pattern " + activeFileNamePattern
107         + " for the active file");
108 
109     rc = new RollingCalendar();
110     rc.init(dtc.getDatePattern());
111     addInfo("The date pattern is '" + dtc.getDatePattern()
112         + "' from file name pattern '" + fileNamePattern.getPattern() + "'.");
113     rc.printPeriodicity(this);
114 
115     // lastCheck can be set by test classes
116     // if it has not been set, we set it here
117     if (lastCheck == null) {
118       lastCheck = new Date();
119       lastCheck.setTime(getCurrentTime());
120       if (getParentsRawFileProperty() != null) {
121         File currentFile = new File(getParentsRawFileProperty());
122         if (currentFile.exists() && currentFile.canRead()) {
123           lastCheck.setTime(currentFile.lastModified());
124         }
125       }
126     }
127     nextCheck = rc.getNextTriggeringMillis(lastCheck);
128 
129     if (maxHistory != NO_DELETE_HISTORY) {
130       tbCleaner = new TimeBasedCleaner(fileNamePattern, rc, maxHistory);
131     }
132   }
133 
134   
135   public CompressionMode getCompressionMode() {
136     return compressionMode;
137   }
138 
139   
140   // allow Test classes to act on the lastCheck field to simulate old
141   // log files needing rollover
142   void setLastCheck(Date _lastCheck) {
143     this.lastCheck = _lastCheck;
144   }
145 
146   boolean rolloverTargetIsParentFile() {
147     return (getParentsRawFileProperty() != null && getParentsRawFileProperty()
148         .equals(elapsedPeriodsFileName));
149   }
150 
151   public void rollover() throws RolloverFailure {
152 
153     // when rollover is called the elapsed period's file has
154     // been already closed. This is a working assumption of this method.
155     
156     if(compressionMode == CompressionMode.NONE) {
157       if (getParentsRawFileProperty() != null) {
158         util.rename(getParentsRawFileProperty(), elapsedPeriodsFileName);
159       }
160     } else {
161       if(getParentsRawFileProperty() == null) {
162         doCompression(false, elapsedPeriodsFileName, elapsedPeriodsFileName);
163       } else {
164         doCompression(true, elapsedPeriodsFileName, elapsedPeriodsFileName);
165       }
166     }
167     
168     if (tbCleaner != null) {
169       tbCleaner.clean(new Date(getCurrentTime()));
170     }
171   }
172 
173   void doCompression(boolean renameToTempFile, String nameOfFile2Compress,
174       String nameOfCompressedFile) throws RolloverFailure {
175     Compressor compressor = null;
176 
177     if (renameToTempFile) {
178       String tmpTarget = nameOfFile2Compress + System.nanoTime() + ".tmp";
179       util.rename(getParentsRawFileProperty(), tmpTarget);
180       nameOfFile2Compress = tmpTarget;
181     }
182 
183     switch (compressionMode) {
184     case GZ:
185       addInfo("GZIP compressing [" + nameOfFile2Compress + "].");
186       compressor = new Compressor(CompressionMode.GZ, nameOfFile2Compress,
187           nameOfCompressedFile);
188       compressor.setContext(this.context);
189       break;
190     case ZIP:
191       addInfo("ZIP compressing [" + nameOfFile2Compress + "]");
192       compressor = new Compressor(CompressionMode.ZIP, nameOfFile2Compress,
193           nameOfCompressedFile);
194       compressor.setContext(this.context);
195       break;
196     }
197 
198     AsynchronousCompressor ac = new AsynchronousCompressor(compressor);
199     future = ac.compressAsynchronously();
200 
201   }
202 
203   /**
204    * 
205    * The active log file is determined by the value of the parent's filename
206    * option. However, in case the file name is left blank, then, the active log
207    * file equals the file name for the current period as computed by the
208    * <b>FileNamePattern</b> option.
209    * 
210    * <p>The RollingPolicy must know whether it is responsible for changing the
211    * name of the active file or not. If the active file name is set by the user
212    * via the configuration file, then the RollingPolicy must let it like it is.
213    * If the user does not specify an active file name, then the RollingPolicy
214    * generates one.
215    * 
216    * <p> To be sure that the file name used by the parent class has been
217    * generated by the RollingPolicy and not specified by the user, we keep track
218    * of the last generated name object and compare its reference to the parent
219    * file name. If they match, then the RollingPolicy knows it's responsible for
220    * the change of the file name.
221    * 
222    */
223   public String getActiveFileName() {
224     if (getParentsRawFileProperty() == null) {
225       String newName = activeFileNamePattern.convertDate(lastCheck);
226       latestActiveFileName = newName;
227       return newName;
228     } else {
229       return getParentsRawFileProperty();
230     }
231   }
232 
233   public boolean isTriggeringEvent(File activeFile, final E event) {
234     long time = getCurrentTime();
235 
236     if (time >= nextCheck) {
237       // We set the elapsedPeriodsFileName before we set the 'lastCheck'
238       // variable
239       // The elapsedPeriodsFileName corresponds to the file name of the period
240       // that just elapsed.
241       elapsedPeriodsFileName = activeFileNamePattern.convertDate(lastCheck);
242 
243       lastCheck.setTime(time);
244       nextCheck = rc.getNextTriggeringMillis(lastCheck);
245       return true;
246     } else {
247       return false;
248     }
249   }
250 
251   /**
252    * Get the number of archive files to keep.
253    * 
254    * @return number of archive files to keep
255    */
256   public int getMaxHistory() {
257     return maxHistory;
258   }
259 
260   /**
261    * Set the maximum number of archive files to keep.
262    * 
263    * @param maxHistory
264    *                number of archive files to keep
265    */
266   public void setMaxHistory(int maxHistory) {
267     this.maxHistory = maxHistory;
268   }
269 
270   @Override
271   public String toString() {
272     return "c.q.l.core.rolling.TimeBasedRollingPolicy";
273   }
274 
275 }