扩展log4j系列[一]为DailyRollingFileAppender加上maxBackupIndex属性

    博客分类:

  • 技术点滴
log4jApache框架F#脚本

在log4j的大多数appender中,都有maxBackupIndex属性,但是这个DailyRollingFileAppender没有,也就是说它会每天滚一个文件,却没有办法控制文件总个数。这绝对是系统的一个“着火点”,下面就开始动手改造了:

一。研究整个log4j的appender结构:

对框架的一个模块进行扩展,并非总是直接继承某个类就好了,如果不进一步深入研究就有可能掉入某些陷阱。(比如扩展log4j的Logger类,直接继承它并不能得到任何好处,具体解释清参考官方文档。),还好log4j对level,appender,layerout都扩展有很好支持的。

然后就是看log4j的配置文件了。 配置文件是可以直接配置扩展appender属性的,这样就替我们节省了一堆定义、解析、处理的过程

Java代码
  1. <spanstyle="color:#ff0000;">#给自己的类取个对应的名</span>
  2. log4j.appender.appenderName=fully.qualified.name.of.appender.class
  3. <spanstyle="color:#ff0000;">#还可以给自己的类property设置值,也就是说扩展的maxBackupIndex属性可以配置</span>
  4. log4j.appender.appenderName.option1=value1
  5. ...
  6. log4j.appender.appenderName.optionN=valueN
# 给自己的类取个对应的名
log4j.appender.appenderName=fully.qualified.name.of.appender.class
#还可以给自己的类property设置值,也就是说扩展的maxBackupIndex属性可以配置
log4j.appender.appenderName.option1=value1
...
log4j.appender.appenderName.optionN=valueN  

二。大致胸有成竹后,可以开始看DailyRollingFileAppender的源码了。

直接看属性跟方法结构

大致可以猜出这个类做了如下几个事情:继承了根类appender、支持DatePattern解析并针对DatePattern设置的滚动条件组装filename、实现“监听”方法,到时间点切换logfile。。。 大部分的工作都给我们做好了:)

现在唯一需要改动的就是,“切换文件”方法,在切换新文件的同时,删除掉最老的n个log。

Java代码
  1. /**
  2. Rolloverthecurrentfiletoanewfile.
  3. */
  4. voidrollOver()throwsIOException{
  5. /*Computefilename,butonlyifdatePatternisspecified*/
  6. if(datePattern==null){
  7. errorHandler.error("MissingDatePatternoptioninrollOver().");
  8. return;
  9. }
  10. StringdatedFilename=fileName+sdf.format(now);
  11. //Itistooearlytorolloverbecausewearestillwithinthe
  12. //boundsofthecurrentinterval.Rolloverwilloccuroncethe
  13. //nextintervalisreached.
  14. if(scheduledFilename.equals(datedFilename)){
  15. return;
  16. }
  17. //closecurrentfile,andrenameittodatedFilename
  18. this.closeFile();
  19. Filetarget=newFile(scheduledFilename);
  20. if(target.exists()){
  21. target.delete();
  22. }
  23. Filefile=newFile(fileName);
  24. booleanresult=file.renameTo(target);
  25. if(result){
  26. LogLog.debug(fileName+"->"+scheduledFilename);
  27. }else{
  28. LogLog.error("Failedtorename["+fileName+"]to["+scheduledFilename+"].");
  29. }
  30. try{
  31. //Thiswillalsoclosethefile.ThisisOKsincemultiple
  32. //closeoperationsaresafe.
  33. this.setFile(fileName,false,this.bufferedIO,this.bufferSize);
  34. }
  35. catch(IOExceptione){
  36. errorHandler.error("setFile("+fileName+",false)callfailed.");
  37. }
  38. scheduledFilename=datedFilename;
  39. }
  /**
Rollover the current file to a new file.
*/
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;
}

看到这里就发现问题了,由于DatePattern格式可配置,那么产生的滚动的文件名也是不同的,也没有什么规律可循。

比如".yyyy-ww",是按周滚动,当配置改成".yyyy-MM "按月滚动之后,通过文件名匹配删除旧文件将会导致错误。

另外,日志文件的切换不是定时轮询而是事件促发机制,只有在进行写操作的时候才会去判断是否需要滚动文件!那么写操作在跨过一个滚动周期执行的时候,文件名会产生空缺而不保证连续性。

也许这就是log4j本身没有对这个appender做文件个数限制的原因吧。

三。妥协吧。

框架的功能总是尽量强大的,但使用总是最简单的功能!在IDC环境中通常是不允许按时间滚动记log的,主要是防止日志文件撑爆硬盘成为着火点。 这里考虑启用按时间滚动,主要是性能日志的统计脚本需要日志文件以日期为名按天存储,并且只需要备份前一天的即可.

那么我的需求就简单了:简化功能!

仿造DailyRollingFileAppender实现1.仅支持按天滚动的

、2.格式写死的DatePattern

,3.最大备份文件个数为n的appender

。(备份数可配考虑灵活性,但一定要有参数检查预防万一!)

限制datepattern,一方面可以防止配错,弄成按月滚动肯定死翘翘;另一方面也容易处理MaxBackupIndex删除历史文件。 more,既然知道是按天滚动,check的方法当然可以简化了:

最终修改版的按天滚动appender如下:

Java代码
  1. packagecxxxxxxxj;
  2. importjava.io.File;
  3. importjava.io.IOException;
  4. importjava.text.SimpleDateFormat;
  5. importjava.util.ArrayList;
  6. importjava.util.Calendar;
  7. importjava.util.Date;
  8. importjava.util.List;
  9. importorg.apache.log4j.FileAppender;
  10. importorg.apache.log4j.Layout;
  11. importorg.apache.log4j.helpers.LogLog;
  12. importorg.apache.log4j.spi.LoggingEvent;
  13. /**
  14. *扩展的一个按天滚动的appender类
  15. *暂时不支持datePattern设置,但是可以配置maxBackupIndex
  16. *@authorweisong
  17. *
  18. */
  19. publicclassDayRollingFileAppenderextendsFileAppender{
  20. /**不允许改写的datepattern*/
  21. privatefinalStringdatePattern="'.'yyyy-MM-dd";
  22. /**最多文件增长个数*/
  23. privateintmaxBackupIndex=2;
  24. /**"文件名+上次最后更新时间"*/
  25. privateStringscheduledFilename;
  26. /**
  27. Thenexttimeweestimatearollovershouldoccur.*/
  28. privatelongnextCheck=System.currentTimeMillis()-1;
  29. Datenow=newDate();
  30. SimpleDateFormatsdf;
  31. /**
  32. Thedefaultconstructordoesnothing.*/
  33. publicDayRollingFileAppender(){
  34. }
  35. /**
  36. 改造过的构造器
  37. */
  38. publicDayRollingFileAppender(Layoutlayout,Stringfilename,
  39. intmaxBackupIndex)throwsIOException{
  40. super(layout,filename,true);
  41. this.maxBackupIndex=maxBackupIndex;
  42. activateOptions();
  43. }
  44. /**
  45. *初始化本Appender对象的时候调用一次
  46. */
  47. publicvoidactivateOptions(){
  48. super.activateOptions();
  49. if(fileName!=null){//perf.log
  50. now.setTime(System.currentTimeMillis());
  51. sdf=newSimpleDateFormat(datePattern);
  52. Filefile=newFile(fileName);
  53. //获取最后更新时间拼成的文件名
  54. scheduledFilename=fileName+sdf.format(newDate(file.lastModified()));
  55. }else{
  56. LogLog.error("Fileisnotsetforappender["+name+"].");
  57. }
  58. if(maxBackupIndex<=0){
  59. LogLog.error("maxBackupIndexresettodefaultvalue[2],orignalvalueis:"+maxBackupIndex);
  60. maxBackupIndex=2;
  61. }
  62. }
  63. /**
  64. 滚动文件的函数:
  65. 1.对文件名带的时间戳进行比较,确定是否更新
  66. 2.if需要更新,当前文件rename到文件名+日期,重新开始写文件
  67. 3.针对配置的maxBackupIndex,删除过期的文件
  68. */
  69. voidrollOver()throwsIOException{
  70. StringdatedFilename=fileName+sdf.format(now);
  71. //如果上次写的日期跟当前日期相同,不需要换文件
  72. if(scheduledFilename.equals(datedFilename)){
  73. return;
  74. }
  75. //closecurrentfile,andrenameittodatedFilename
  76. this.closeFile();
  77. Filetarget=newFile(scheduledFilename);
  78. if(target.exists()){
  79. target.delete();
  80. }
  81. Filefile=newFile(fileName);
  82. booleanresult=file.renameTo(target);
  83. if(result){
  84. LogLog.debug(fileName+"->"+scheduledFilename);
  85. }else{
  86. LogLog.error("Failedtorename["+fileName+"]to["
  87. +scheduledFilename+"].");
  88. }
  89. //删除过期文件
  90. if(maxBackupIndex>0){
  91. Filefolder=newFile(file.getParent());
  92. List<String>maxBackupIndexDates=getMaxBackupIndexDates();
  93. for(Fileff:folder.listFiles()){//遍历目录,将日期不在备份范围内的日志删掉
  94. if(ff.getName().startsWith(file.getName())&&!ff.getName().equals(file.getName())){
  95. //获取文件名带的日期时间戳
  96. StringmarkedDate=ff.getName().substring(file.getName().length());
  97. if(!maxBackupIndexDates.contains(markedDate)){
  98. result=ff.delete();
  99. }
  100. if(result){
  101. LogLog.debug(ff.getName()+"->deleted");
  102. }else{
  103. LogLog.error("FailedtodeletedoldDayRollingFileAppenderfile:"+ff.getName());
  104. }
  105. }
  106. }
  107. }
  108. try{
  109. //Thiswillalsoclosethefile.ThisisOKsincemultiple
  110. //closeoperationsaresafe.
  111. this.setFile(fileName,false,this.bufferedIO,this.bufferSize);
  112. }catch(IOExceptione){
  113. errorHandler.error("setFile("+fileName+",false)callfailed.");
  114. }
  115. scheduledFilename=datedFilename;//更新最后更新日期戳
  116. }
  117. /**
  118. *Actualwritingoccurshere.这个方法是写操作真正的执行过程!
  119. **/
  120. protectedvoidsubAppend(LoggingEventevent){
  121. longn=System.currentTimeMillis();
  122. if(n>=nextCheck){//在每次写操作前判断一下是否需要滚动文件
  123. now.setTime(n);
  124. nextCheck=getNextDayCheckPoint(now);
  125. try{
  126. rollOver();
  127. }catch(IOExceptionioe){
  128. LogLog.error("rollOver()failed.",ioe);
  129. }
  130. }
  131. super.subAppend(event);
  132. }
  133. /**
  134. *获取下一天的时间变更点
  135. *@paramnow
  136. *@return
  137. */
  138. longgetNextDayCheckPoint(Datenow){
  139. Calendarcalendar=Calendar.getInstance();
  140. calendar.setTime(now);
  141. calendar.set(Calendar.HOUR_OF_DAY,0);
  142. calendar.set(Calendar.MINUTE,0);
  143. calendar.set(Calendar.SECOND,0);
  144. calendar.set(Calendar.MILLISECOND,0);//注意MILLISECOND,毫秒也要置0.。。否则错了也找不出来的
  145. calendar.add(Calendar.DATE,1);
  146. returncalendar.getTimeInMillis();
  147. }
  148. /**
  149. *根据maxBackupIndex配置的备份文件个数,获取要保留log文件的日期范围集合
  150. *@returnlist<'fileName+yyyy-MM-dd'>
  151. */
  152. List<String>getMaxBackupIndexDates(){
  153. List<String>result=newArrayList<String>();
  154. if(maxBackupIndex>0){
  155. for(inti=1;i<=maxBackupIndex;i++){
  156. Calendarcalendar=Calendar.getInstance();
  157. calendar.setTime(now);
  158. calendar.set(Calendar.HOUR_OF_DAY,0);
  159. calendar.set(Calendar.MINUTE,0);
  160. calendar.set(Calendar.SECOND,0);
  161. calendar.set(Calendar.MILLISECOND,0);//注意MILLISECOND,毫秒也要置0.。。否则错了也找不出来的
  162. calendar.add(Calendar.DATE,-i);
  163. result.add(sdf.format(calendar.getTime()));
  164. }
  165. }
  166. returnresult;
  167. }
  168. publicintgetMaxBackupIndex(){
  169. returnmaxBackupIndex;
  170. }
  171. publicvoidsetMaxBackupIndex(intmaxBackupIndex){
  172. this.maxBackupIndex=maxBackupIndex;
  173. }
  174. publicStringgetDatePattern(){
  175. returndatePattern;
  176. }
  177. //publicstaticvoidmain(String[]args){
  178. //DayRollingFileAppenderda=newDayRollingFileAppender();
  179. //da.setMaxBackupIndex(2);
  180. //da.sdf=newSimpleDateFormat(da.getDatePattern());
  181. //System.out.println(da.getMaxBackupIndexDates());
  182. //
  183. //Filef=newFile("e:/log/b2c/perf.log");
  184. //System.out.println("f.name="+f.getName());
  185. //Filep=newFile(f.getParent());
  186. //for(Fileff:p.listFiles()){
  187. //System.out.println(ff);
  188. //}
  189. //}
  190. }
package cxxxxxxxj;
import java.io.File;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
import java.util.List;
import org.apache.log4j.FileAppender;
import org.apache.log4j.Layout;
import org.apache.log4j.helpers.LogLog;
import org.apache.log4j.spi.LoggingEvent;
/**
* 扩展的一个按天滚动的appender类
* 暂时不支持datePattern设置,但是可以配置maxBackupIndex
* @author weisong
*
*/
public class DayRollingFileAppender extends FileAppender {
/**不允许改写的datepattern */
private final String datePattern = "'.'yyyy-MM-dd";
/**最多文件增长个数*/
private int  maxBackupIndex = 2;
/**"文件名+上次最后更新时间"*/
private String scheduledFilename;
/**
The next time we estimate a rollover should occur. */
private long nextCheck = System.currentTimeMillis () - 1;
Date now = new Date();
SimpleDateFormat sdf;
/**
The default constructor does nothing. */
public DayRollingFileAppender() {
}
/**
改造过的构造器
*/
public DayRollingFileAppender (Layout layout, String filename,
int  maxBackupIndex) throws IOException {
super(layout, filename, true);
this.maxBackupIndex = maxBackupIndex;
activateOptions();
}
/**
* 初始化本Appender对象的时候调用一次
*/
public void activateOptions() {
super.activateOptions();
if(fileName != null) { //perf.log
now.setTime(System.currentTimeMillis());
sdf = new SimpleDateFormat(datePattern);
File file = new File(fileName);
//获取最后更新时间拼成的文件名
scheduledFilename = fileName+sdf.format(new Date(file.lastModified()));
} else {
LogLog.error("File is not set for appender ["+name+"].");
}
if(maxBackupIndex<=0) {
LogLog.error("maxBackupIndex reset to default value[2],orignal value is:" + maxBackupIndex);
maxBackupIndex=2;
}
}
/**
滚动文件的函数:
1.对文件名带的时间戳进行比较,确定是否更新
2.if需要更新,当前文件rename到文件名+日期, 重新开始写文件
3. 针对配置的maxBackupIndex,删除过期的文件
*/
void rollOver() throws IOException {
String datedFilename = fileName + sdf.format(now);
// 如果上次写的日期跟当前日期相同,不需要换文件
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 + "].");
}
// 删除过期文件
if (maxBackupIndex > 0) {
File folder = new File(file.getParent());
List<String> maxBackupIndexDates = getMaxBackupIndexDates();
for (File ff : folder.listFiles()) { //遍历目录,将日期不在备份范围内的日志删掉
if (ff.getName().startsWith(file.getName()) && !ff.getName().equals(file.getName())) {
//获取文件名带的日期时间戳
String markedDate = ff.getName().substring(file.getName().length());
if (!maxBackupIndexDates.contains(markedDate)) {
result = ff.delete();
}
if (result) {
LogLog.debug(ff.getName() + " ->deleted ");
} else {
LogLog.error("Failed to deleted old DayRollingFileAppender file :" + ff.getName());
}
}
}
}
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; // 更新最后更新日期戳
}
/**
* Actual writing occurs here. 这个方法是写操作真正的执行过程!
* */
protected void subAppend(LoggingEvent event) {
long n = System.currentTimeMillis();
if (n >= nextCheck) { //在每次写操作前判断一下是否需要滚动文件
now.setTime(n);
nextCheck = getNextDayCheckPoint(now);
try {
rollOver();
} catch (IOException ioe) {
LogLog.error("rollOver() failed.", ioe);
}
}
super.subAppend(event);
}
/**
* 获取下一天的时间变更点
* @param now
* @return
*/
long getNextDayCheckPoint(Date now) {
Calendar calendar = Calendar.getInstance();
calendar.setTime(now);
calendar.set(Calendar.HOUR_OF_DAY, 0);
calendar.set(Calendar.MINUTE, 0);
calendar.set(Calendar.SECOND, 0);
calendar.set(Calendar.MILLISECOND, 0);//注意MILLISECOND,毫秒也要置0.。。否则错了也找不出来的
calendar.add(Calendar.DATE, 1);
return calendar.getTimeInMillis();
}
/**
* 根据maxBackupIndex配置的备份文件个数,获取要保留log文件的日期范围集合
* @return list<'fileName+yyyy-MM-dd'>
*/
List<String> getMaxBackupIndexDates() {
List<String> result = new ArrayList<String>();
if(maxBackupIndex>0) {
for (int i = 1; i <= maxBackupIndex; i++) {
Calendar calendar = Calendar.getInstance();
calendar.setTime(now);
calendar.set(Calendar.HOUR_OF_DAY, 0);
calendar.set(Calendar.MINUTE, 0);
calendar.set(Calendar.SECOND, 0);
calendar.set(Calendar.MILLISECOND, 0);//注意MILLISECOND,毫秒也要置0.。。否则错了也找不出来的
calendar.add(Calendar.DATE, -i);
result.add(sdf.format(calendar.getTime()));
}
}
return result;
}
public int getMaxBackupIndex() {
return maxBackupIndex;
}
public void setMaxBackupIndex(int maxBackupIndex) {
this.maxBackupIndex = maxBackupIndex;
}
public String getDatePattern() {
return datePattern;
}
//	public static void main(String[] args) {
//		DayRollingFileAppender da = new DayRollingFileAppender();
//		da.setMaxBackupIndex(2);
//		da.sdf = new SimpleDateFormat(da.getDatePattern());
//		System.out.println(da.getMaxBackupIndexDates());
//
//		File f = new File("e:/log/b2c/perf.log");
//		System.out.println("f.name=" + f.getName());
//		File p = new File(f.getParent());
//		for(File ff : p.listFiles()) {
//			System.out.println(ff);
//		}
//	}
}
  • 大小: 91.7 KB
  • 大小: 66.7 KB
  • 查看图片附件
声明:本站所有文章,如无特殊说明或标注,均为本站原创发布。任何个人或组织,在未征得本站同意时,禁止复制、盗用、采集、发布本站内容到任何网站、书籍等各类媒体平台。如若本站内容侵犯了原著者的合法权益,可联系我们进行处理。