轉自:http://hellojavaer.iteye.com/blog/977599
一、背景:後臺有很多任務,每個任務都是一個main函數(JVM或進程),但是所有的任務都加載同一個log4j.xml文件,即往同一份文件中輸出日誌。
二、原因追蹤:
在 log4j 的 DailyRollingFileAppender 類中:
- void rollOver() throws IOException {
- /* Compute filename, but only if datePattern is specified */
- if (datePattern == null) {
- errorHandler.error("Missing DatePattern option in rollOver().");
- return;
- }
- String datedFilename = fileName+sdf.format(now);
- // It is too early to roll over because we are still within the
- // bounds of the current interval. Rollover will occur once the
- // next interval is reached.
- if (scheduledFilename.equals(datedFilename)) {
- return;
- }
- // close current file, and rename it to datedFilename
- this.closeFile();
- File target = new File(scheduledFilename);
- if (target.exists()) {
- target.delete();
- }
- File file = new File(fileName);
- boolean result = file.renameTo(target);
- if(result) {
- LogLog.debug(fileName +" -> "+ scheduledFilename);
- } else {
- LogLog.error("Failed to rename ["+fileName+"] to ["+scheduledFilename+"].");
- }
- try {
- // This will also close the file. This is OK since multiple
- // close operations are safe.
- this.setFile(fileName, false, this.bufferedIO, this.bufferSize);
- }
- catch(IOException e) {
- errorHandler.error("setFile("+fileName+", false) call failed.");
- }
- scheduledFilename = datedFilename;
- }
中間部分代碼意思是:如果備份文件不存在,則備份,同時創建新日誌文件;如果存在,則先刪除掉,再備份;
好,問題在這個時刻就出現了:(假設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 的主要區別在於以下方法:
- void rollOver() throws IOException {
- /* Compute filename, but only if datePattern is specified */
- if (datePattern == null) {
- errorHandler.error("Missing DatePattern option in rollOver().");
- return;
- }
- String datedFilename = fileName+sdf.format(now);
- // It is too early to roll over because we are still within the
- // bounds of the current interval. Rollover will occur once the
- // next interval is reached.
- if (scheduledFilename.equals(datedFilename)) {
- return;
- }
- // close current file, and rename it to datedFilename
- this.closeFile();
- File target = new File(scheduledFilename);
- if (!target.exists()) {
- File file = new File(fileName);
- boolean result = file.renameTo(target);
- if (result) {
- LogLog.debug(fileName + " -> " + scheduledFilename);
- } else {
- LogLog.error("Failed to rename [" + fileName + "] to [" + scheduledFilename + "].");
- }
- }
- try {
- // This will also close the file. This is OK since multiple
- // close operations are safe.
- this.setFile(fileName, true, this.bufferedIO, this.bufferSize);
- }
- catch(IOException e) {
- errorHandler.error("setFile("+fileName+", false) call failed.");
- }
- scheduledFilename = datedFilename;
- }
2、如果是任務,根據啓動參數taskName 屬性區分日誌文件:
a. 目前所有後臺任務在啓動腳本里都加上了 -DtaskName 屬性;
b. 定義 TaskDailyRollingFileAppender 類,該類繼承 DailyRollingFileAppender,並覆蓋其 setFile(String file) 方法:
- public void setFile(String file) {
- String taskName = System.getProperty("taskName");
- if (!StringUtil.isEmpty(taskName)) {
- file = file + "." + taskName;
- }
- super.setFile(file);
- }