1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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ülcü */
135 public class DailyRollingFileAppender extends FileAppender {
136
137
138
139
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
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
264
265
266
267
268
269
270
271
272 int computeCheckPeriod() {
273 RollingCalendar rollingCalendar = new RollingCalendar(gmtTimeZone, Locale.ENGLISH);
274
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);
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
285 if(r0 != null && r1 != null && !r0.equals(r1)) {
286 return i;
287 }
288 }
289 }
290 return TOP_OF_TROUBLE;
291 }
292
293 /***
294 Rollover the current file to a new file.
295 */
296 void rollOver() throws IOException {
297
298
299 if (datePattern == null) {
300 errorHandler.error("Missing DatePattern option in rollOver().");
301 return;
302 }
303
304 String datedFilename = fileName+sdf.format(now);
305
306
307
308 if (scheduledFilename.equals(datedFilename)) {
309 return;
310 }
311
312
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
330
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 }