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
9 * http://www.apache.org/licenses/LICENSE-2.0
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.
20 package org.apache.log4j;
22 import java.io.IOException;
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;
32 import org.apache.log4j.helpers.LogLog;
33 import org.apache.log4j.spi.LoggingEvent;
36 DailyRollingFileAppender extends {@link FileAppender} so that the
37 underlying file is rolled over at a user chosen frequency.
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.
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.
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
59 <p>Is is possible to specify monthly, weekly, half-daily, daily,
60 hourly, or minutely rollover schedules.
62 <p><table border="1" cellpadding="2">
65 <th>Rollover schedule</th>
69 <td><code>'.'yyyy-MM</code>
70 <td>Rollover at the beginning of each month</td>
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.
78 <td><code>'.'yyyy-ww</code>
80 <td>Rollover at the first day of each week. The first day of the
81 week depends on the locale.</td>
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.
90 <td><code>'.'yyyy-MM-dd</code>
92 <td>Rollover at midnight each day.</td>
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.
100 <td><code>'.'yyyy-MM-dd-a</code>
102 <td>Rollover at midnight and midday of each day.</td>
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.
110 <td><code>'.'yyyy-MM-dd-HH</code>
112 <td>Rollover at the top of every hour.</td>
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.
122 <td><code>'.'yyyy-MM-dd-HH-mm</code>
124 <td>Rollover at the beginning of every minute.</td>
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.
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
141 @author Ceki Gülcü*/
142 public class DailyRollingFileAppender extends FileAppender {
145 // The code assumes that the following constants are in a increasing
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;
157 The date pattern. By default, the pattern is set to
158 "'.'yyyy-MM-dd" meaning daily rollover.
160 private String datePattern = "'.'yyyy-MM-dd";
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
169 The precise time when a rollover occurs depends on logging
172 private String scheduledFilename;
175 The next time we estimate a rollover should occur. */
176 private long nextCheck = System.currentTimeMillis () - 1;
178 Date now = new Date();
180 SimpleDateFormat sdf;
182 RollingCalendar rc = new RollingCalendar();
184 int checkPeriod = TOP_OF_TROUBLE;
186 // The gmtTimeZone is used only in computeCheckPeriod() method.
187 static final TimeZone gmtTimeZone = TimeZone.getTimeZone("GMT");
191 The default constructor does nothing. */
192 public DailyRollingFileAppender() {
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.
201 public DailyRollingFileAppender (Layout layout, String filename,
202 String datePattern) throws IOException {
203 super(layout, filename, true);
204 this.datePattern = datePattern;
209 The <b>DatePattern</b> takes a string in the same format as
210 expected by {@link SimpleDateFormat}. This options determines the
213 public void setDatePattern(String pattern) {
214 datePattern = pattern;
217 /** Returns the value of the <b>DatePattern</b> option. */
218 public String getDatePattern() {
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);
230 File file = new File(fileName);
231 scheduledFilename = fileName+sdf.format(new Date(file.lastModified()));
234 LogLog.error("Either File or DatePattern options are not set for appender ["
239 void printPeriodicity(int type) {
242 LogLog.debug("Appender ["+name+"] to be rolled every minute.");
245 LogLog.debug("Appender ["+name
246 +"] to be rolled on top of every hour.");
249 LogLog.debug("Appender ["+name
250 +"] to be rolled at midday and midnight.");
253 LogLog.debug("Appender ["+name
254 +"] to be rolled at midnight.");
257 LogLog.debug("Appender ["+name
258 +"] to be rolled at start of week.");
261 LogLog.debug("Appender ["+name
262 +"] to be rolled at start of every month.");
265 LogLog.warn("Unknown periodicity for appender ["+name+"].");
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
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)) {
297 return TOP_OF_TROUBLE; // Deliberately head for trouble...
301 Rollover the current file to a new file.
303 void rollOver() throws IOException {
305 /* Compute filename, but only if datePattern is specified */
306 if (datePattern == null) {
307 errorHandler.error("Missing DatePattern option in rollOver().");
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)) {
319 // close current file, and rename it to datedFilename
322 File target = new File(scheduledFilename);
323 if (target.exists()) {
327 File file = new File(fileName);
328 boolean result = file.renameTo(target);
330 LogLog.debug(fileName +" -> "+ scheduledFilename);
332 LogLog.error("Failed to rename ["+fileName+"] to ["+scheduledFilename+"].");
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);
340 catch(IOException e) {
341 errorHandler.error("setFile("+fileName+", true) call failed.");
343 scheduledFilename = datedFilename;
347 * This method differentiates DailyRollingFileAppender from its
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.
354 protected void subAppend(LoggingEvent event) {
355 long n = System.currentTimeMillis();
356 if (n >= nextCheck) {
358 nextCheck = rc.getNextCheckMillis(now);
362 catch(IOException ioe) {
363 if (ioe instanceof InterruptedIOException) {
364 Thread.currentThread().interrupt();
366 LogLog.error("rollOver() failed.", ioe);
369 super.subAppend(event);
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.
378 class RollingCalendar extends GregorianCalendar {
379 private static final long serialVersionUID = -3560331770601814177L;
381 int type = DailyRollingFileAppender.TOP_OF_TROUBLE;
387 RollingCalendar(TimeZone tz, Locale locale) {
391 void setType(int type) {
395 public long getNextCheckMillis(Date now) {
396 return getNextCheckDate(now).getTime();
399 public Date getNextCheckDate(Date now) {
403 case DailyRollingFileAppender.TOP_OF_MINUTE:
404 this.set(Calendar.SECOND, 0);
405 this.set(Calendar.MILLISECOND, 0);
406 this.add(Calendar.MINUTE, 1);
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);
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);
420 this.set(Calendar.HOUR_OF_DAY, 12);
422 this.set(Calendar.HOUR_OF_DAY, 0);
423 this.add(Calendar.DAY_OF_MONTH, 1);
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);
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);
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);
450 throw new IllegalStateException("Unknown periodicity type.");