您的位置:首页 > 移动开发

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

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

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

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



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

Java代码



<SPAN style="COLOR: #ff0000"># 给自己的类取个对应的名</SPAN>

log4j.appender.appenderName=fully.qualified.name.of.appender.class

<SPAN style="COLOR: #ff0000">#还可以给自己的类property设置值,也就是说扩展的maxBackupIndex属性可以配置</SPAN>

log4j.appender.appenderName.option1=value1

...

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代码



/**

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;

}

/**
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代码



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);

// }

// }

}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: