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支持这种通用的配置方法,注意上面配置第四行,不用另外添加其他任何代码。