Save This Page
Home » apache-log4j-1.2.16 » org.apache » log4j » [javadoc | source]
    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   }

Save This Page
Home » apache-log4j-1.2.16 » org.apache » log4j » [javadoc | source]