1. 程式人生 > >多程序log4j日誌丟失問題分析

多程序log4j日誌丟失問題分析

二、原因追蹤

在 log4j 的 DailyRollingFileAppender 類中:

Java程式碼  收藏程式碼
  1. void rollOver() throws IOException {  
  2.     /* Compute filename, but only if datePattern is specified */  
  3.     if (datePattern == null) {  
  4.       errorHandler.error("Missing DatePattern option in rollOver().");  
  5.       return;  
  6.     }  
  7.     String datedFilename = fileName+sdf.format(now);  
  8.     // It is too early to roll over because we are still within the  
  9.     // bounds of the current interval. Rollover will occur once the  
  10.     // next interval is reached.  
  11.     if (scheduledFilename.equals(datedFilename)) {  
  12.       return;  
  13.     }  
  14.     // close current file, and rename it to datedFilename
      
  15.     this.closeFile();  
  16.     File target  = new File(scheduledFilename);  
  17.     if (target.exists()) {  
  18.       target.delete();  
  19.     }  
  20.     File file = new File(fileName);  
  21.     boolean result = file.renameTo(target);  
  22.     if(result) {  
  23.       LogLog.debug(fileName +" -> "+ scheduledFilename);  
  24.     } else {  
  25.       LogLog.error("Failed to rename ["+fileName+"] to ["+scheduledFilename+"].");  
  26.     }  
  27.     try {  
  28.       // This will also close the file. This is OK since multiple  
  29.       // close operations are safe.  
  30.       this.setFile(fileName, falsethis.bufferedIO, this.bufferSize);  
  31.     }  
  32.     catch(IOException e) {  
  33.       errorHandler.error("setFile("+fileName+", false) call failed.");  
  34.     }  
  35.     scheduledFilename = datedFilename;  
  36. }  
該方法的作用是:在滾動備份時間間隔到的時刻,將前一時間間隔的日誌備份,同時以非追加模式將新日誌打到新日誌檔案中;

中間部分程式碼意思是:如果備份檔案不存在,則備份,同時建立新日誌檔案;如果存在,則先刪除掉,再備份;

好,問題在這個時刻就出現了:(假設A程序先進行滾動備份)

1、對於A程序:
a. 先將project.log備份(renameTo())為project.log.2009.08.18,然後建立project.log檔案,並將日誌寫在project.log中;
b. 此時A程序持有project.log的檔案控制代碼;而B程序仍然持有project.log.2009.08.18的檔案控制代碼(儘管被重新命名,但控制代碼不變);

2、對於B程序:發現以project.log.2009.08.18為檔名的檔案已經存在,則將其刪除(前一時間段的所有日誌全沒了),並將以project.log為檔名的檔案重新命名為project.log.2009.08.18,然後建立project.log檔案;

3、此時A程序持有project.log.2009.08.18的檔案控制代碼(被B程序重新命名過的),而B程序持有最新建立的project.log

4、結果導致:前一時間段日誌丟失,A、B程序在不同的檔案裡打日誌;

三、解決方案

1、改變 rollOver() 方法的實現方式:定義 TaskDailyRollingFileAppender 類,該類繼承至 FileAppender ,它與 DailyRollingFileAppender 的主要區別在於以下方法:

Java程式碼  收藏程式碼
  1. void rollOver() throws IOException {  
  2.     /* Compute filename, but only if datePattern is specified */  
  3.     if (datePattern == null) {  
  4.       errorHandler.error("Missing DatePattern option in rollOver().");  
  5.       return;  
  6.     }  
  7.     String datedFilename = fileName+sdf.format(now);  
  8.     // It is too early to roll over because we are still within the  
  9.     // bounds of the current interval. Rollover will occur once the  
  10.     // next interval is reached.  
  11.     if (scheduledFilename.equals(datedFilename)) {  
  12.       return;  
  13.     }  
  14.     // close current file, and rename it to datedFilename  
  15.     this.closeFile();  
  16.     File target  = new File(scheduledFilename);  
  17.     if (!target.exists()) {  
  18.         File file = new File(fileName);  
  19.         boolean result = file.renameTo(target);  
  20.         if (result) {  
  21.             LogLog.debug(fileName + " -> " + scheduledFilename);  
  22.         } else {  
  23.             LogLog.error("Failed to rename [" + fileName + "] to [" + scheduledFilename + "].");  
  24.         }  
  25.     }  
  26.     try {  
  27.         // This will also close the file. This is OK since multiple  
  28.         // close operations are safe.  
  29.         this.setFile(fileName, truethis.bufferedIO, this.bufferSize);  
  30.     }  
  31.     catch(IOException e) {  
  32.       errorHandler.error("setFile("+fileName+", false) call failed.");  
  33.     }  
  34.     scheduledFilename = datedFilename;  
  35. }  
改進後的 rollOver() 方法主要作用是:A程序先將日誌重新命名,然後建立新日誌檔案,B程序發現已經存在,則直接以追加模式切換到新的日誌檔案上去;

2、如果是任務,根據啟動引數taskName 屬性區分日誌檔案:

a. 目前所有後臺任務在啟動腳本里都加上了 -DtaskName 屬性;
b. 定義 TaskDailyRollingFileAppender 類,該類繼承 DailyRollingFileAppender,並覆蓋其 setFile(String file) 方法:

Java程式碼  收藏程式碼
  1. public void setFile(String file) {  
  2.     String taskName = System.getProperty("taskName");  
  3.     if (!StringUtil.isEmpty(taskName)) {  
  4.         file = file + "." + taskName;  
  5.     }  
  6.     super.setFile(file);  
  7. }  
 c. 這樣實現後,對於不同的任務,日誌檔名會以.taskName結尾,對於沒有指定 taskName 的任務,不受影響;