View Javadoc

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