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

轉自:http://hellojavaer.iteye.com/blog/977599

一、背景:後臺有很多任務,每個任務都是一個main函數(JVM或進程),但是所有的任務都加載同一個log4j.xml文件,即往同一份文件中輸出日誌。

二、原因追蹤

在 log4j 的 DailyRollingFileAppender 類中:

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

中間部分代碼意思是:如果備份文件不存在,則備份,同時創建新日誌文件;如果存在,則先刪除掉,再備份;

 

好,問題在這個時刻就出現了:(假設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.   
  3.     /* Compute filename, but only if datePattern is specified */  
  4.     if (datePattern == null) {  
  5.       errorHandler.error("Missing DatePattern option in rollOver().");  
  6.       return;  
  7.     }  
  8.   
  9.     String datedFilename = fileName+sdf.format(now);  
  10.     // It is too early to roll over because we are still within the  
  11.     // bounds of the current interval. Rollover will occur once the  
  12.     // next interval is reached.  
  13.     if (scheduledFilename.equals(datedFilename)) {  
  14.       return;  
  15.     }  
  16.   
  17.     // close current file, and rename it to datedFilename  
  18.     this.closeFile();  
  19.   
  20.     File target  = new File(scheduledFilename);  
  21.     if (!target.exists()) {  
  22.         File file = new File(fileName);  
  23.         boolean result = file.renameTo(target);  
  24.         if (result) {  
  25.             LogLog.debug(fileName + " -> " + scheduledFilename);  
  26.         } else {  
  27.             LogLog.error("Failed to rename [" + fileName + "] to [" + scheduledFilename + "].");  
  28.         }  
  29.     }  
  30.   
  31.     try {  
  32.         // This will also close the file. This is OK since multiple  
  33.         // close operations are safe.  
  34.         this.setFile(fileName, truethis.bufferedIO, this.bufferSize);  
  35.     }  
  36.     catch(IOException e) {  
  37.       errorHandler.error("setFile("+fileName+", false) call failed.");  
  38.     }  
  39.     scheduledFilename = datedFilename;  
  40. }  
改進後的 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.   
  7.     super.setFile(file);  
  8. }  
 c. 這樣實現後,對於不同的任務,日誌文件名會以.taskName結尾,對於沒有指定 taskName 的任務,不受影響;

發佈了144 篇原創文章 · 獲贊 75 · 訪問量 182萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章