在log4j的大多数appender中 ,都有maxBackupIndex属性 ,但是这个DailyRollingFileAppender没有 ,也就是说它会每天滚一个文件 ,却没有办法控制文件总个数 。这绝对是系统的一个“着火点 ” ,下面就开始动手改造了:
一 。研究整个log4j的appender结构:
对框架的一个模块进行扩展 ,并非总是直接继承某个类就好了 ,如果不进一步深入研究就有可能掉入某些陷阱 。(比如扩展log4j的Logger类 ,直接继承它并不能得到任何好处 ,具体解释清参考官方文档 。) ,还好log4j对level ,appender ,layerout都扩展有很好支持的 。
然后就是看log4j的配置文件了 。 配置文件是可以直接配置扩展appender属性的,这样就替我们节省了一堆定义 、解析 、处理的过程
<spanstyle="color:#ff0000;">#给自己的类取个对应的名</span>
log4j.appender.appenderName=fully.qualified.name.of.appender.class
<spanstyle="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 。
/**
Rolloverthecurrentfiletoanewfile.
*/
voidrollOver()throwsIOException{
/*Computefilename,butonlyifdatePatternisspecified*/
if(datePattern==null){
errorHandler.error("MissingDatePatternoptioninrollOver().");
return;
}
StringdatedFilename=fileName+sdf.format(now);
//Itistooearlytorolloverbecausewearestillwithinthe
//boundsofthecurrentinterval.Rolloverwilloccuroncethe
//nextintervalisreached.
if(scheduledFilename.equals(datedFilename)){
return;
}
//closecurrentfile,andrenameittodatedFilename
this.closeFile();
Filetarget=newFile(scheduledFilename);
if(target.exists()){
target.delete();
}
Filefile=newFile(fileName);
booleanresult=file.renameTo(target);
if(result){
LogLog.debug(fileName+"->"+scheduledFilename);
}else{
LogLog.error("Failedtorename["+fileName+"]to["+scheduledFilename+"].");
}
try{
//Thiswillalsoclosethefile.ThisisOKsincemultiple
//closeoperationsaresafe.
this.setFile(fileName,false,this.bufferedIO,this.bufferSize);
}
catch(IOExceptione){
errorHandler.error("setFile("+fileName+",false)callfailed.");
}
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如下:
packagecxxxxxxxj;
importjava.io.File;
importjava.io.IOException;
importjava.text.SimpleDateFormat;
importjava.util.ArrayList;
importjava.util.Calendar;
importjava.util.Date;
importjava.util.List;
importorg.apache.log4j.FileAppender;
importorg.apache.log4j.Layout;
importorg.apache.log4j.helpers.LogLog;
importorg.apache.log4j.spi.LoggingEvent;
/**
*扩展的一个按天滚动的appender类
*暂时不支持datePattern设置 ,但是可以配置maxBackupIndex
*@authorweisong
*
*/
publicclassDayRollingFileAppenderextendsFileAppender{
/**不允许改写的datepattern*/
privatefinalStringdatePattern=".yyyy-MM-dd";
/**最多文件增长个数*/
privateintmaxBackupIndex=2;
/**"文件名+上次最后更新时间"*/
privateStringscheduledFilename;
/**
Thenexttimeweestimatearollovershouldoccur.*/
privatelongnextCheck=System.currentTimeMillis()-1;
Datenow=newDate();
SimpleDateFormatsdf;
/**
Thedefaultconstructordoesnothing.*/
publicDayRollingFileAppender(){
}
/**
改造过的构造器
*/
publicDayRollingFileAppender(Layoutlayout,Stringfilename,
intmaxBackupIndex)throwsIOException{
super(layout,filename,true);
this.maxBackupIndex=maxBackupIndex;
activateOptions();
}
/**
*初始化本Appender对象的时候调用一次
*/
publicvoidactivateOptions(){
super.activateOptions();
if(fileName!=null){//perf.log
now.setTime(System.currentTimeMillis());
sdf=newSimpleDateFormat(datePattern);
Filefile=newFile(fileName);
//获取最后更新时间拼成的文件名
scheduledFilename=fileName+sdf.format(newDate(file.lastModified()));
}else{
LogLog.error("Fileisnotsetforappender["+name+"].");
}
if(maxBackupIndex<=0){
LogLog.error("maxBackupIndexresettodefaultvalue[2],orignalvalueis:"+maxBackupIndex);
maxBackupIndex=2;
}
}
/**
滚动文件的函数:
1.对文件名带的时间戳进行比较 ,确定是否更新
2.if需要更新 ,当前文件rename到文件名+日期 ,重新开始写文件
3.针对配置的maxBackupIndex,删除过期的文件
*/
voidrollOver()throwsIOException{
StringdatedFilename=fileName+sdf.format(now);
//如果上次写的日期跟当前日期相同 ,不需要换文件
if(scheduledFilename.equals(datedFilename)){
return;
}
//closecurrentfile,andrenameittodatedFilename
this.closeFile();
Filetarget=newFile(scheduledFilename);
if(target.exists()){
target.delete();
}
Filefile=newFile(fileName);
booleanresult=file.renameTo(target);
if(result){
LogLog.debug(fileName+"->"+scheduledFilename);
}else{
LogLog.error("Failedtorename["+fileName+"]to["
+scheduledFilename+"].");
}
//删除过期文件
if(maxBackupIndex>0){
Filefolder=newFile(file.getParent());
List<String>maxBackupIndexDates=getMaxBackupIndexDates();
for(Fileff:folder.listFiles()){//遍历目录 ,将日期不在备份范围内的日志删掉
if(ff.getName().startsWith(file.getName())&&!ff.getName().equals(file.getName())){
//获取文件名带的日期时间戳
StringmarkedDate=ff.getName().substring(file.getName().length());
if(!maxBackupIndexDates.contains(markedDate)){
result=ff.delete();
}
if(result){
LogLog.debug(ff.getName()+"->deleted");
}else{
LogLog.error("FailedtodeletedoldDayRollingFileAppenderfile:"+ff.getName());
}
}
}
}
try{
//Thiswillalsoclosethefile.ThisisOKsincemultiple
//closeoperationsaresafe.
this.setFile(fileName,false,this.bufferedIO,this.bufferSize);
}catch(IOExceptione){
errorHandler.error("setFile("+fileName+",false)callfailed.");
}
scheduledFilename=datedFilename;//更新最后更新日期戳
}
/**
*Actualwritingoccurshere.这个方法是写操作真正的执行过程!
**/
protectedvoidsubAppend(LoggingEventevent){
longn=System.currentTimeMillis();
if(n>=nextCheck){//在每次写操作前判断一下是否需要滚动文件
now.setTime(n);
nextCheck=getNextDayCheckPoint(now);
try{
rollOver();
}catch(IOExceptionioe){
LogLog.error("rollOver()failed.",ioe);
}
}
super.subAppend(event);
}
/**
*获取下一天的时间变更点
*@paramnow
*@return
*/
longgetNextDayCheckPoint(Datenow){
Calendarcalendar=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);
returncalendar.getTimeInMillis();
}
/**
*根据maxBackupIndex配置的备份文件个数,获取要保留log文件的日期范围集合
*@returnlist<fileName+yyyy-MM-dd>
*/
List<String>getMaxBackupIndexDates(){
List<String>result=newArrayList<String>();
if(maxBackupIndex>0){
for(inti=1;i<=maxBackupIndex;i++){
Calendarcalendar=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()));
}
}
returnresult;
}
publicintgetMaxBackupIndex(){
returnmaxBackupIndex;
}
publicvoidsetMaxBackupIndex(intmaxBackupIndex){
this.maxBackupIndex=maxBackupIndex;
}
publicStringgetDatePattern(){
returndatePattern;
}
//publicstaticvoidmain(String[]args){
//DayRollingFileAppenderda=newDayRollingFileAppender();
//da.setMaxBackupIndex(2);
//da.sdf=newSimpleDateFormat(da.getDatePattern());
//System.out.println(da.getMaxBackupIndexDates());
//
//Filef=newFile("e:/log/b2c/perf.log");
//System.out.println("f.name="+f.getName());
//Filep=newFile(f.getParent());
//for(Fileff:p.listFiles()){
//System.out.println(ff);
//}
//}
}
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
查看图片附件
声明:本站所有文章 ,如无特殊说明或标注 ,均为本站原创发布 。任何个人或组织 ,在未征得本站同意时 ,禁止复制 、盗用 、采集 、发布本站内容到任何网站 、书籍等各类媒体平台 。如若本站内容侵犯了原著者的合法权益 ,可联系我们进行处理。