79a3b5c6b6612e411dd14239e2fcd2f852e43a3c
[jalview.git] / srcjar / org / apache / log4j / DailyRollingFileAppender.java
1 /*
2  * Licensed to the Apache Software Foundation (ASF) under one or more
3  * contributor license agreements.  See the NOTICE file distributed with
4  * this work for additional information regarding copyright ownership.
5  * The ASF licenses this file to You under the Apache License, Version 2.0
6  * (the "License"); you may not use this file except in compliance with
7  * the License.  You may obtain a copy of the License at
8  * 
9  *      http://www.apache.org/licenses/LICENSE-2.0
10  * 
11  * Unless required by applicable law or agreed to in writing, software
12  * distributed under the License is distributed on an "AS IS" BASIS,
13  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14  * See the License for the specific language governing permissions and
15  * limitations under the License.
16  */
17
18
19
20 package org.apache.log4j;
21
22 import java.io.IOException;
23 import java.io.File;
24 import java.io.InterruptedIOException;
25 import java.text.SimpleDateFormat;
26 import java.util.Date;
27 import java.util.GregorianCalendar;
28 import java.util.Calendar;
29 import java.util.TimeZone;
30 import java.util.Locale;
31
32 import org.apache.log4j.helpers.LogLog;
33 import org.apache.log4j.spi.LoggingEvent;
34
35 /**
36    DailyRollingFileAppender extends {@link FileAppender} so that the
37    underlying file is rolled over at a user chosen frequency.
38    
39    DailyRollingFileAppender has been observed to exhibit 
40    synchronization issues and data loss.  The log4j extras
41    companion includes alternatives which should be considered
42    for new deployments and which are discussed in the documentation
43    for org.apache.log4j.rolling.RollingFileAppender.
44
45    <p>The rolling schedule is specified by the <b>DatePattern</b>
46    option. This pattern should follow the {@link SimpleDateFormat}
47    conventions. In particular, you <em>must</em> escape literal text
48    within a pair of single quotes. A formatted version of the date
49    pattern is used as the suffix for the rolled file name.
50
51    <p>For example, if the <b>File</b> option is set to
52    <code>/foo/bar.log</code> and the <b>DatePattern</b> set to
53    <code>'.'yyyy-MM-dd</code>, on 2001-02-16 at midnight, the logging
54    file <code>/foo/bar.log</code> will be copied to
55    <code>/foo/bar.log.2001-02-16</code> and logging for 2001-02-17
56    will continue in <code>/foo/bar.log</code> until it rolls over
57    the next day.
58
59    <p>Is is possible to specify monthly, weekly, half-daily, daily,
60    hourly, or minutely rollover schedules.
61
62    <p><table border="1" cellpadding="2">
63    <tr>
64    <th>DatePattern</th>
65    <th>Rollover schedule</th>
66    <th>Example</th>
67
68    <tr>
69    <td><code>'.'yyyy-MM</code>
70    <td>Rollover at the beginning of each month</td>
71
72    <td>At midnight of May 31st, 2002 <code>/foo/bar.log</code> will be
73    copied to <code>/foo/bar.log.2002-05</code>. Logging for the month
74    of June will be output to <code>/foo/bar.log</code> until it is
75    also rolled over the next month.
76
77    <tr>
78    <td><code>'.'yyyy-ww</code>
79
80    <td>Rollover at the first day of each week. The first day of the
81    week depends on the locale.</td>
82
83    <td>Assuming the first day of the week is Sunday, on Saturday
84    midnight, June 9th 2002, the file <i>/foo/bar.log</i> will be
85    copied to <i>/foo/bar.log.2002-23</i>.  Logging for the 24th week
86    of 2002 will be output to <code>/foo/bar.log</code> until it is
87    rolled over the next week.
88
89    <tr>
90    <td><code>'.'yyyy-MM-dd</code>
91
92    <td>Rollover at midnight each day.</td>
93
94    <td>At midnight, on March 8th, 2002, <code>/foo/bar.log</code> will
95    be copied to <code>/foo/bar.log.2002-03-08</code>. Logging for the
96    9th day of March will be output to <code>/foo/bar.log</code> until
97    it is rolled over the next day.
98
99    <tr>
100    <td><code>'.'yyyy-MM-dd-a</code>
101
102    <td>Rollover at midnight and midday of each day.</td>
103
104    <td>At noon, on March 9th, 2002, <code>/foo/bar.log</code> will be
105    copied to <code>/foo/bar.log.2002-03-09-AM</code>. Logging for the
106    afternoon of the 9th will be output to <code>/foo/bar.log</code>
107    until it is rolled over at midnight.
108
109    <tr>
110    <td><code>'.'yyyy-MM-dd-HH</code>
111
112    <td>Rollover at the top of every hour.</td>
113
114    <td>At approximately 11:00.000 o'clock on March 9th, 2002,
115    <code>/foo/bar.log</code> will be copied to
116    <code>/foo/bar.log.2002-03-09-10</code>. Logging for the 11th hour
117    of the 9th of March will be output to <code>/foo/bar.log</code>
118    until it is rolled over at the beginning of the next hour.
119
120
121    <tr>
122    <td><code>'.'yyyy-MM-dd-HH-mm</code>
123
124    <td>Rollover at the beginning of every minute.</td>
125
126    <td>At approximately 11:23,000, on March 9th, 2001,
127    <code>/foo/bar.log</code> will be copied to
128    <code>/foo/bar.log.2001-03-09-10-22</code>. Logging for the minute
129    of 11:23 (9th of March) will be output to
130    <code>/foo/bar.log</code> until it is rolled over the next minute.
131
132    </table>
133
134    <p>Do not use the colon ":" character in anywhere in the
135    <b>DatePattern</b> option. The text before the colon is interpeted
136    as the protocol specificaion of a URL which is probably not what
137    you want.
138
139
140    @author Eirik Lygre
141    @author Ceki G&uuml;lc&uuml;*/
142 public class DailyRollingFileAppender extends FileAppender {
143
144
145   // The code assumes that the following constants are in a increasing
146   // sequence.
147   static final int TOP_OF_TROUBLE=-1;
148   static final int TOP_OF_MINUTE = 0;
149   static final int TOP_OF_HOUR   = 1;
150   static final int HALF_DAY      = 2;
151   static final int TOP_OF_DAY    = 3;
152   static final int TOP_OF_WEEK   = 4;
153   static final int TOP_OF_MONTH  = 5;
154
155
156   /**
157      The date pattern. By default, the pattern is set to
158      "'.'yyyy-MM-dd" meaning daily rollover.
159    */
160   private String datePattern = "'.'yyyy-MM-dd";
161
162   /**
163      The log file will be renamed to the value of the
164      scheduledFilename variable when the next interval is entered. For
165      example, if the rollover period is one hour, the log file will be
166      renamed to the value of "scheduledFilename" at the beginning of
167      the next hour. 
168
169      The precise time when a rollover occurs depends on logging
170      activity. 
171   */
172   private String scheduledFilename;
173
174   /**
175      The next time we estimate a rollover should occur. */
176   private long nextCheck = System.currentTimeMillis () - 1;
177
178   Date now = new Date();
179
180   SimpleDateFormat sdf;
181
182   RollingCalendar rc = new RollingCalendar();
183
184   int checkPeriod = TOP_OF_TROUBLE;
185
186   // The gmtTimeZone is used only in computeCheckPeriod() method.
187   static final TimeZone gmtTimeZone = TimeZone.getTimeZone("GMT");
188
189
190   /**
191      The default constructor does nothing. */
192   public DailyRollingFileAppender() {
193   }
194
195   /**
196     Instantiate a <code>DailyRollingFileAppender</code> and open the
197     file designated by <code>filename</code>. The opened filename will
198     become the ouput destination for this appender.
199
200     */
201   public DailyRollingFileAppender (Layout layout, String filename,
202                                    String datePattern) throws IOException {
203     super(layout, filename, true);
204     this.datePattern = datePattern;
205     activateOptions();
206   }
207
208   /**
209      The <b>DatePattern</b> takes a string in the same format as
210      expected by {@link SimpleDateFormat}. This options determines the
211      rollover schedule.
212    */
213   public void setDatePattern(String pattern) {
214     datePattern = pattern;
215   }
216
217   /** Returns the value of the <b>DatePattern</b> option. */
218   public String getDatePattern() {
219     return datePattern;
220   }
221
222   public void activateOptions() {
223     super.activateOptions();
224     if(datePattern != null && fileName != null) {
225       now.setTime(System.currentTimeMillis());
226       sdf = new SimpleDateFormat(datePattern);
227       int type = computeCheckPeriod();
228       printPeriodicity(type);
229       rc.setType(type);
230       File file = new File(fileName);
231       scheduledFilename = fileName+sdf.format(new Date(file.lastModified()));
232
233     } else {
234       LogLog.error("Either File or DatePattern options are not set for appender ["
235                    +name+"].");
236     }
237   }
238
239   void printPeriodicity(int type) {
240     switch(type) {
241     case TOP_OF_MINUTE:
242       LogLog.debug("Appender ["+name+"] to be rolled every minute.");
243       break;
244     case TOP_OF_HOUR:
245       LogLog.debug("Appender ["+name
246                    +"] to be rolled on top of every hour.");
247       break;
248     case HALF_DAY:
249       LogLog.debug("Appender ["+name
250                    +"] to be rolled at midday and midnight.");
251       break;
252     case TOP_OF_DAY:
253       LogLog.debug("Appender ["+name
254                    +"] to be rolled at midnight.");
255       break;
256     case TOP_OF_WEEK:
257       LogLog.debug("Appender ["+name
258                    +"] to be rolled at start of week.");
259       break;
260     case TOP_OF_MONTH:
261       LogLog.debug("Appender ["+name
262                    +"] to be rolled at start of every month.");
263       break;
264     default:
265       LogLog.warn("Unknown periodicity for appender ["+name+"].");
266     }
267   }
268
269
270   // This method computes the roll over period by looping over the
271   // periods, starting with the shortest, and stopping when the r0 is
272   // different from from r1, where r0 is the epoch formatted according
273   // the datePattern (supplied by the user) and r1 is the
274   // epoch+nextMillis(i) formatted according to datePattern. All date
275   // formatting is done in GMT and not local format because the test
276   // logic is based on comparisons relative to 1970-01-01 00:00:00
277   // GMT (the epoch).
278
279   int computeCheckPeriod() {
280     RollingCalendar rollingCalendar = new RollingCalendar(gmtTimeZone, Locale.getDefault());
281     // set sate to 1970-01-01 00:00:00 GMT
282     Date epoch = new Date(0);
283     if(datePattern != null) {
284       for(int i = TOP_OF_MINUTE; i <= TOP_OF_MONTH; i++) {
285         SimpleDateFormat simpleDateFormat = new SimpleDateFormat(datePattern);
286         simpleDateFormat.setTimeZone(gmtTimeZone); // do all date formatting in GMT
287         String r0 = simpleDateFormat.format(epoch);
288         rollingCalendar.setType(i);
289         Date next = new Date(rollingCalendar.getNextCheckMillis(epoch));
290         String r1 =  simpleDateFormat.format(next);
291         //System.out.println("Type = "+i+", r0 = "+r0+", r1 = "+r1);
292         if(r0 != null && r1 != null && !r0.equals(r1)) {
293           return i;
294         }
295       }
296     }
297     return TOP_OF_TROUBLE; // Deliberately head for trouble...
298   }
299
300   /**
301      Rollover the current file to a new file.
302   */
303   void rollOver() throws IOException {
304
305     /* Compute filename, but only if datePattern is specified */
306     if (datePattern == null) {
307       errorHandler.error("Missing DatePattern option in rollOver().");
308       return;
309     }
310
311     String datedFilename = fileName+sdf.format(now);
312     // It is too early to roll over because we are still within the
313     // bounds of the current interval. Rollover will occur once the
314     // next interval is reached.
315     if (scheduledFilename.equals(datedFilename)) {
316       return;
317     }
318
319     // close current file, and rename it to datedFilename
320     this.closeFile();
321
322     File target  = new File(scheduledFilename);
323     if (target.exists()) {
324       target.delete();
325     }
326
327     File file = new File(fileName);
328     boolean result = file.renameTo(target);
329     if(result) {
330       LogLog.debug(fileName +" -> "+ scheduledFilename);
331     } else {
332       LogLog.error("Failed to rename ["+fileName+"] to ["+scheduledFilename+"].");
333     }
334
335     try {
336       // This will also close the file. This is OK since multiple
337       // close operations are safe.
338       this.setFile(fileName, true, this.bufferedIO, this.bufferSize);
339     }
340     catch(IOException e) {
341       errorHandler.error("setFile("+fileName+", true) call failed.");
342     }
343     scheduledFilename = datedFilename;
344   }
345
346   /**
347    * This method differentiates DailyRollingFileAppender from its
348    * super class.
349    *
350    * <p>Before actually logging, this method will check whether it is
351    * time to do a rollover. If it is, it will schedule the next
352    * rollover time and then rollover.
353    * */
354   protected void subAppend(LoggingEvent event) {
355     long n = System.currentTimeMillis();
356     if (n >= nextCheck) {
357       now.setTime(n);
358       nextCheck = rc.getNextCheckMillis(now);
359       try {
360         rollOver();
361       }
362       catch(IOException ioe) {
363           if (ioe instanceof InterruptedIOException) {
364               Thread.currentThread().interrupt();
365           }
366               LogLog.error("rollOver() failed.", ioe);
367       }
368     }
369     super.subAppend(event);
370    }
371 }
372
373 /**
374  *  RollingCalendar is a helper class to DailyRollingFileAppender.
375  *  Given a periodicity type and the current time, it computes the
376  *  start of the next interval.  
377  * */
378 class RollingCalendar extends GregorianCalendar {
379   private static final long serialVersionUID = -3560331770601814177L;
380
381   int type = DailyRollingFileAppender.TOP_OF_TROUBLE;
382
383   RollingCalendar() {
384     super();
385   }  
386
387   RollingCalendar(TimeZone tz, Locale locale) {
388     super(tz, locale);
389   }  
390
391   void setType(int type) {
392     this.type = type;
393   }
394
395   public long getNextCheckMillis(Date now) {
396     return getNextCheckDate(now).getTime();
397   }
398
399   public Date getNextCheckDate(Date now) {
400     this.setTime(now);
401
402     switch(type) {
403     case DailyRollingFileAppender.TOP_OF_MINUTE:
404         this.set(Calendar.SECOND, 0);
405         this.set(Calendar.MILLISECOND, 0);
406         this.add(Calendar.MINUTE, 1);
407         break;
408     case DailyRollingFileAppender.TOP_OF_HOUR:
409         this.set(Calendar.MINUTE, 0);
410         this.set(Calendar.SECOND, 0);
411         this.set(Calendar.MILLISECOND, 0);
412         this.add(Calendar.HOUR_OF_DAY, 1);
413         break;
414     case DailyRollingFileAppender.HALF_DAY:
415         this.set(Calendar.MINUTE, 0);
416         this.set(Calendar.SECOND, 0);
417         this.set(Calendar.MILLISECOND, 0);
418         int hour = get(Calendar.HOUR_OF_DAY);
419         if(hour < 12) {
420           this.set(Calendar.HOUR_OF_DAY, 12);
421         } else {
422           this.set(Calendar.HOUR_OF_DAY, 0);
423           this.add(Calendar.DAY_OF_MONTH, 1);
424         }
425         break;
426     case DailyRollingFileAppender.TOP_OF_DAY:
427         this.set(Calendar.HOUR_OF_DAY, 0);
428         this.set(Calendar.MINUTE, 0);
429         this.set(Calendar.SECOND, 0);
430         this.set(Calendar.MILLISECOND, 0);
431         this.add(Calendar.DATE, 1);
432         break;
433     case DailyRollingFileAppender.TOP_OF_WEEK:
434         this.set(Calendar.DAY_OF_WEEK, getFirstDayOfWeek());
435         this.set(Calendar.HOUR_OF_DAY, 0);
436         this.set(Calendar.MINUTE, 0);
437         this.set(Calendar.SECOND, 0);
438         this.set(Calendar.MILLISECOND, 0);
439         this.add(Calendar.WEEK_OF_YEAR, 1);
440         break;
441     case DailyRollingFileAppender.TOP_OF_MONTH:
442         this.set(Calendar.DATE, 1);
443         this.set(Calendar.HOUR_OF_DAY, 0);
444         this.set(Calendar.MINUTE, 0);
445         this.set(Calendar.SECOND, 0);
446         this.set(Calendar.MILLISECOND, 0);
447         this.add(Calendar.MONTH, 1);
448         break;
449     default:
450         throw new IllegalStateException("Unknown periodicity type.");
451     }
452     return getTime();
453   }
454 }