001/* 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with 004 * this work for additional information regarding copyright ownership. 005 * The ASF licenses this file to You under the Apache License, Version 2.0 006 * (the "License"); you may not use this file except in compliance with 007 * the License. You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017 018 019 020package org.apache.log4j; 021 022import java.io.IOException; 023import java.io.File; 024import java.io.InterruptedIOException; 025import java.text.SimpleDateFormat; 026import java.util.Date; 027import java.util.GregorianCalendar; 028import java.util.Calendar; 029import java.util.TimeZone; 030import java.util.Locale; 031 032import org.apache.log4j.helpers.LogLog; 033import org.apache.log4j.spi.LoggingEvent; 034 035/** 036 DailyRollingFileAppender extends {@link FileAppender} so that the 037 underlying file is rolled over at a user chosen frequency. 038 039 DailyRollingFileAppender has been observed to exhibit 040 synchronization issues and data loss. The log4j extras 041 companion includes alternatives which should be considered 042 for new deployments and which are discussed in the documentation 043 for org.apache.log4j.rolling.RollingFileAppender. 044 045 <p>The rolling schedule is specified by the <b>DatePattern</b> 046 option. This pattern should follow the {@link SimpleDateFormat} 047 conventions. In particular, you <em>must</em> escape literal text 048 within a pair of single quotes. A formatted version of the date 049 pattern is used as the suffix for the rolled file name. 050 051 <p>For example, if the <b>File</b> option is set to 052 <code>/foo/bar.log</code> and the <b>DatePattern</b> set to 053 <code>'.'yyyy-MM-dd</code>, on 2001-02-16 at midnight, the logging 054 file <code>/foo/bar.log</code> will be copied to 055 <code>/foo/bar.log.2001-02-16</code> and logging for 2001-02-17 056 will continue in <code>/foo/bar.log</code> until it rolls over 057 the next day. 058 059 <p>Is is possible to specify monthly, weekly, half-daily, daily, 060 hourly, or minutely rollover schedules. 061 062 <p><table border="1" cellpadding="2"> 063 <tr> 064 <th>DatePattern</th> 065 <th>Rollover schedule</th> 066 <th>Example</th> 067 068 <tr> 069 <td><code>'.'yyyy-MM</code> 070 <td>Rollover at the beginning of each month</td> 071 072 <td>At midnight of May 31st, 2002 <code>/foo/bar.log</code> will be 073 copied to <code>/foo/bar.log.2002-05</code>. Logging for the month 074 of June will be output to <code>/foo/bar.log</code> until it is 075 also rolled over the next month. 076 077 <tr> 078 <td><code>'.'yyyy-ww</code> 079 080 <td>Rollover at the first day of each week. The first day of the 081 week depends on the locale.</td> 082 083 <td>Assuming the first day of the week is Sunday, on Saturday 084 midnight, June 9th 2002, the file <i>/foo/bar.log</i> will be 085 copied to <i>/foo/bar.log.2002-23</i>. Logging for the 24th week 086 of 2002 will be output to <code>/foo/bar.log</code> until it is 087 rolled over the next week. 088 089 <tr> 090 <td><code>'.'yyyy-MM-dd</code> 091 092 <td>Rollover at midnight each day.</td> 093 094 <td>At midnight, on March 8th, 2002, <code>/foo/bar.log</code> will 095 be copied to <code>/foo/bar.log.2002-03-08</code>. Logging for the 096 9th day of March will be output to <code>/foo/bar.log</code> until 097 it is rolled over the next day. 098 099 <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ülcü*/ 142public 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 * */ 378class 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}