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