Log4j現在已經被大家熟知了,所有細節都可以在網上查到,Log4j支持Appender,其中DailyRollingFileAppender是被經常用到的Appender之一。在討論今天的主題之前,我們先看下另外一個Appender。
最常用的Appender——RollingFileAppender
下面是RollingFileAppender的一個Log4j配置樣例(配置1):
log4j.appender.R=org.apache.log4j.RollingFileAppender log4j.appender.R.Threshold=DEBUG log4j.appender.R.File=test.log log4j.appender.R.layout=org.apache.log4j.PatternLayout log4j.appender.R.layout.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss} [%5p] - %c -%F(%L) -%m%n log4j.appender.R.MaxFileSize=20MB log4j.appender.R.MaxBackupIndex=10
RollingFileAppender使用MaxFileSize設置一個日志文件的最大大小,當產生多個日志時,會在日志名稱后面加上".1"、".2"、……這樣的后綴,我們可以看到RollingFileAppender有個屬性MaxBackupIndex,這個屬性通過限制日志文件名后綴".n"中的n大小來限制日志數量,比如上面MaxBackupIndex=10,其實最大日志數量為11。我們知道這個有這個限制是很必要的,當我們的程序在服務器上運行時,隨著時間的遷移,日志會越來越多,如果對日志數量沒有限制,日志大小會越來越大,最后甚至占滿整個硬盤。
可以按照周期時間來滾動日志文件的Appender——DailyRollingFileAppender
下面是DailyRollingFileAppender的一個Log4j配置樣例(配置2):
log4j.appender.logfile=org.apache.log4j.DailyRollingFileAppender log4j.appender.logfile.File=test.log log4j.appender.logfile.DataPattern='.'yyyy-MM-dd-HH-mm log4j.appender.logfile.Threshold=debug log4j.appender.logfile.encoding=UTF-8 log4j.appender.logfile.Append=false log4j.appender.logfile.layout=org.apache.log4j.PatternLayout log4j.appender.logfile.layout.ConversionPattern= [%d{yyyy-MM-dd HH\:mm\:ss}]%-5p %c(line\:%L) %x-%m%n
?
DailyRollingFileAppender特點是固定周期時間生成一個日志文件,比如,默認情況是每天生成一個文件。這種日志可以方便根據時間來定位日志位置,使日志清晰易查。但是這種日志有個不好地方是,不能限制日志數量,MaxBackupIndex屬性和MaxFileSize在DailyRollingFileAppender中是無效的,我們上面已經提到限制日志數量的必要性。這里有兩個解決辦法:
- linux上crontab+shell
- java進程里面起一個線程,定期掃描日志文件夾。
但是這兩種方法都不是很方便,有沒有更好的辦法呢?
重寫DailyRollingFileAppender——MyDailyRollingFileAppender
查看DailyRollingFileAppender源代碼,發現rollOver()方法是用來生成文件的,當調用subAppend()方法時會根據判斷當前時間是否大于應該生成新文件的時間了(具體實現可以查看源碼,邏輯還是比較清晰的),如果大于,就生成。首先把當前日志重命名,命名格式為test.log.yyyy-MM-dd-HH-mm,然后重新建test.log文件。看到這里我們就可以想,在rollOver()方法里面加上刪除過多的日志就不行了嗎,的確可以這么做:
?
1 package org.apache.log4j; 2 3 import org.slf4j.Logger; 4 import org.slf4j.LoggerFactory; 5 6 import java.io.File; 7 import java.io.FileFilter; 8 import java.io.IOException; 9 import java.text.ParseException; 10 import java.util.*; 11 12 public class MyDailyRollingFileAppender extends DailyRollingFileAppender { 13 private static Logger logger = LoggerFactory.getLogger(MyDailyRollingFileAppender.class); 14 private int maxFileSize = 60; 15 16 17 void rollOver() throws IOException { 18 super.rollOver(); 19 20 logger.debug("保留文件數量" + maxFileSize + ",日志文件名稱為:" + fileName); 21 List<File> fileList = getAllLogs(); 22 sortFiles(fileList); 23 logger.debug(fileList.toString()); 24 deleteOvermuch(fileList); 25 } 26 27 /** 28 * 刪除過多的文件 29 * @param fileList 所有日志文件 30 */ 31 private void deleteOvermuch(List<File> fileList) { 32 if (fileList.size() > maxFileSize) { 33 for (int i = 0;i < fileList.size() - maxFileSize;i++) { 34 fileList.get(i).delete(); 35 logger.debug("刪除日志" + fileList.get(i)); 36 } 37 } 38 } 39 40 /** 41 * 根據文件名稱上的特定格式的時間排序日志文件 42 * @param fileList 43 */ 44 private void sortFiles(List<File> fileList) { 45 Collections.sort(fileList, new Comparator<File>() { 46 public int compare(File o1, File o2) { 47 try { 48 if (getDateStr(o1).isEmpty()) { 49 return 1; 50 } 51 Date date1 = sdf.parse(getDateStr(o1)); 52 53 if (getDateStr(o2).isEmpty()) { 54 return -1; 55 } 56 Date date2 = sdf.parse(getDateStr(o2)); 57 58 if (date1.getTime() > date2.getTime()) { 59 return 1; 60 } else if (date1.getTime() < date2.getTime()) { 61 return -1; 62 } 63 } catch (ParseException e) { 64 logger.error("", e); 65 } 66 return 0; 67 } 68 }); 69 } 70 71 private String getDateStr(File file) { 72 if (file == null) { 73 return "null"; 74 } 75 return file.getName().replaceAll(new File(fileName).getName(), ""); 76 } 77 78 /** 79 * 獲取所有日志文件,只有文件名符合DatePattern格式的才為日志文件 80 * @return 81 */ 82 private List<File> getAllLogs() { 83 final File file = new File(fileName); 84 File logPath = file.getParentFile(); 85 if (logPath == null) { 86 logPath = new File("."); 87 } 88 89 File files[] = logPath.listFiles(new FileFilter() { 90 public boolean accept(File pathname) { 91 try { 92 if (getDateStr(pathname).isEmpty()) { 93 return true; 94 } 95 sdf.parse(getDateStr(pathname)); 96 return true; 97 } catch (ParseException e) { 98 logger.error("", e); 99 return false; 100 } 101 } 102 }); 103 return Arrays.asList(files); 104 } 105 public int getMaxFileSize() { 106 return maxFileSize; 107 } 108 109 public void setMaxFileSize(int maxFileSize) { 110 this.maxFileSize = maxFileSize; 111 } 112 }
首先,要注意的就是怎么判斷日志文件夾中的日志是否是日志還是另外不相關的文件,比如備份的日志、控制臺日志等。我使用的方法就是判斷sdf.parse(name.replaceAll(file.getName(), ""))是否報異常,如果不報異常就說明這個文件是日志,當然不排除有的文件命名恰好符合這個格式,但是這樣的文件在日志文件夾下,我們認為它就是一個日志文件也是合理的。然后我們根據sdf.parse(name.replaceAll(file.getName(), ""))解析出來的Date為所有日志進行升序排序放到一個隊列中,再保留這個隊列最后maxFileSize個文件的情況下,刪除多余的日志文件。
然后,我們注意到我們上面的邏輯中用了maxFileSize這個變量,這個變量在MyDailyRollingFileAppender中,這個變量是怎么賦值的呢?
log4j.appender.logfile=org.apache.log4j.MyDailyRollingFileAppender log4j.appender.logfile.File=test.log log4j.appender.logfile.DatePattern='.'yyyy-MM-dd-HH-m log4j.appender.logfile.MaxFileSize=5 log4j.appender.logfile.Append=false log4j.appender.logfile.layout=org.apache.log4j.PatternLayout log4j.appender.logfile.layout.ConversionPattern= [%d{yyyy-MM-dd HH\:mm\:ss}]%-5p %c(line\:%L) %x-%m%n
其實Log4j支持這種通用的配置方法,注意上面配置第四行,不用另外添加其他任何代碼。
?