Log4j定时打印日志及添加模块名配置的Java代码实例
配置间隔时间,定时打印日志
接到个需求,通过log4j定时打印日志,需求描述如下:需要能够定时打印日志,时间间隔可配。说到定时,首先想到了DailyRollingFileAppender类,各种定时,根据datePattern,这个可以参考类SimpleDateFormat类,常见的一些定时设置如下:
- '.'yyyy-MM:每月
- '.'yyyy-ww:每周
- '.'yyyy-MM-dd:每天
- '.'yyyy-MM-dd-a:每天两次
- '.'yyyy-MM-dd-HH:每小时
- '.'yyyy-MM-dd-HH-mm:每分钟
通过观察发现没有n分钟类似的日期格式,因此,在DailyRollingFileAppender类基础上进行自定义类的编写。过程如下:
1)拷贝DailyRollingFileAppender类源码并并改名MinuteRollingAppender,为了在log4j.xml中配置,增加配置项intervalTime并添加set、get方法;
privateintintervalTime=10;
2)由于DailyRollingFileAppender类使用了RollingCalendar类来计算下一次间隔时间,而需要传递参数intervalTime,因此修改RollingCalendar类为内部类;由于其方法就是根据datePattern来计算下一次rollOver动作的时间,此时不需要其他的时间模式,修改方法如下:
publicDategetNextCheckDate(Datenow)
{
this.setTime(now);
this.set(Calendar.SECOND,0);
this.set(Calendar.MILLISECOND,0);
this.add(Calendar.MINUTE,intervalTime);
returngetTime();
}
3)按照分钟可配时,时间模式就需要禁用了,将其改为staticfinal,响应的去掉其get、set方法和MinuteRollingAppender构造函数中的datePattern参数
privatestaticStringDATEPATTERN="'.'yyyy-MM-dd-HH-mm'.log'";
同样,服务于多种datePattern的方法computeCheckPeriod()也可以删除;至此改造就完成了,成品类如下:
packagenet.csdn.blog;
importjava.io.File;
importjava.io.IOException;
importjava.io.InterruptedIOException;
importjava.text.SimpleDateFormat;
importjava.util.Calendar;
importjava.util.Date;
importjava.util.GregorianCalendar;
importorg.apache.log4j.FileAppender;
importorg.apache.log4j.Layout;
importorg.apache.log4j.helpers.LogLog;
importorg.apache.log4j.spi.LoggingEvent;
/**
*按分钟可配置定时appender
*
*@authorcoder_xia
*
*/
publicclassMinuteRollingAppenderextendsFileAppender
{
/**
*Thedatepattern.Bydefault,thepatternissetto"'.'yyyy-MM-dd"
*meaningdailyrollover.
*/
privatestaticStringDATEPATTERN="'.'yyyy-MM-dd-HH-mm'.log'";
/**
*间隔时间,单位:分钟
*/
privateintintervalTime=10;
/**
*ThelogfilewillberenamedtothevalueofthescheduledFilename
*variablewhenthenextintervalisentered.Forexample,iftherollover
*periodisonehour,thelogfilewillberenamedtothevalueof
*"scheduledFilename"atthebeginningofthenexthour.
*
*Theprecisetimewhenarolloveroccursdependsonloggingactivity.
*/
privateStringscheduledFilename;
/**
*Thenexttimeweestimatearollovershouldoccur.
*/
privatelongnextCheck=System.currentTimeMillis()-1;
Datenow=newDate();
SimpleDateFormatsdf;
RollingCalendarrc=newRollingCalendar();
/**
*Thedefaultconstructordoesnothing.
*/
publicMinuteRollingAppender()
{
}
/**
*Instantiatea<code>MinuteRollingAppender</code>andopenthefile
*designatedby<code>filename</code>.Theopenedfilenamewillbecomethe
*ouputdestinationforthisappender.
*/
publicMinuteRollingAppender(Layoutlayout,Stringfilename)
throwsIOException
{
super(layout,filename,true);
activateOptions();
}
/**
*@returntheintervalTime
*/
publicintgetIntervalTime()
{
returnintervalTime;
}
/**
*@paramintervalTime
*theintervalTimetoset
*/
publicvoidsetIntervalTime(intintervalTime)
{
this.intervalTime=intervalTime;
}
@Override
publicvoidactivateOptions()
{
super.activateOptions();
if(fileName!=null)
{
now.setTime(System.currentTimeMillis());
sdf=newSimpleDateFormat(DATEPATTERN);
Filefile=newFile(fileName);
scheduledFilename=fileName
+sdf.format(newDate(file.lastModified()));
}
else
{
LogLog
.error("EitherFileorDatePatternoptionsarenotsetforappender["
+name+"].");
}
}
/**
*Rolloverthecurrentfiletoanewfile.
*/
voidrollOver()throwsIOException
{
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,true,this.bufferedIO,this.bufferSize);
}
catch(IOExceptione)
{
errorHandler.error("setFile("+fileName+",true)callfailed.");
}
scheduledFilename=datedFilename;
}
/**
*ThismethoddifferentiatesMinuteRollingAppenderfromitssuperclass.
*
*<p>
*Beforeactuallylogging,thismethodwillcheckwhetheritistimetodo
*arollover.Ifitis,itwillschedulethenextrollovertimeandthen
*rollover.
**/
@Override
protectedvoidsubAppend(LoggingEventevent)
{
longn=System.currentTimeMillis();
if(n>=nextCheck)
{
now.setTime(n);
nextCheck=rc.getNextCheckMillis(now);
try
{
rollOver();
}
catch(IOExceptionioe)
{
if(ioeinstanceofInterruptedIOException)
{
Thread.currentThread().interrupt();
}
LogLog.error("rollOver()failed.",ioe);
}
}
super.subAppend(event);
}
/**
*RollingCalendarisahelperclasstoMinuteRollingAppender.Givena
*periodicitytypeandthecurrenttime,itcomputesthestartofthenext
*interval.
**/
classRollingCalendarextendsGregorianCalendar
{
privatestaticfinallongserialVersionUID=-3560331770601814177L;
RollingCalendar()
{
super();
}
publiclonggetNextCheckMillis(Datenow)
{
returngetNextCheckDate(now).getTime();
}
publicDategetNextCheckDate(Datenow)
{
this.setTime(now);
this.set(Calendar.SECOND,0);
this.set(Calendar.MILLISECOND,0);
this.add(Calendar.MINUTE,intervalTime);
returngetTime();
}
}
}
测试配置文件如下:
<?xmlversion="1.0"encoding="UTF-8"?> <!DOCTYPElog4j:configurationSYSTEM"log4j.dtd"> <log4j:configurationxmlns:log4j="http://jakarta.apache.org/log4j/"> <appendername="myFile"class="net.csdn.blog.MinuteRollingAppender"> <paramname="File"value="log4jTest.log"/> <paramname="Append"value="true"/> <paramname="intervalTime"value="2"/> <layoutclass="org.apache.log4j.PatternLayout"> <paramname="ConversionPattern"value="%p%d(%c:%L)-%m%n"/> </layout> </appender> <root> <priorityvalue="debug"/> <appender-refref="myFile"/> </root> </log4j:configuration>
关于定时实现,还可以采用java提供的Timer实现,也就免去了每次记录日志时计算并且比较时间,区别其实就是自己起个线程与调用rollOver方法,实现如下:
packagenet.csdn.blog;
importjava.io.File;
importjava.io.IOException;
importjava.text.SimpleDateFormat;
importjava.util.Date;
importjava.util.Timer;
importjava.util.TimerTask;
importorg.apache.log4j.FileAppender;
importorg.apache.log4j.Layout;
importorg.apache.log4j.helpers.LogLog;
publicclassTimerTaskRollingAppenderextendsFileAppender
{
/**
*Thedatepattern.Bydefault,thepatternissetto"'.'yyyy-MM-dd"
*meaningdailyrollover.
*/
privatestaticfinalStringDATEPATTERN="'.'yyyy-MM-dd-HH-mm'.log'";
/**
*间隔时间,单位:分钟
*/
privateintintervalTime=10;
SimpleDateFormatsdf=newSimpleDateFormat(DATEPATTERN);
/**
*Thedefaultconstructordoesnothing.
*/
publicTimerTaskRollingAppender()
{
}
/**
*Instantiatea<code>TimerTaskRollingAppender</code>andopenthefile
*designatedby<code>filename</code>.Theopenedfilenamewillbecomethe
*ouputdestinationforthisappender.
*/
publicTimerTaskRollingAppender(Layoutlayout,Stringfilename)
throwsIOException
{
super(layout,filename,true);
activateOptions();
}
/**
*@returntheintervalTime
*/
publicintgetIntervalTime()
{
returnintervalTime;
}
/**
*@paramintervalTime
*theintervalTimetoset
*/
publicvoidsetIntervalTime(intintervalTime)
{
this.intervalTime=intervalTime;
}
@Override
publicvoidactivateOptions()
{
super.activateOptions();
Timertimer=newTimer();
timer.schedule(newLogTimerTask(),1000,intervalTime*60000);
}
classLogTimerTaskextendsTimerTask
{
@Override
publicvoidrun()
{
StringdatedFilename=fileName+sdf.format(newDate());
closeFile();
Filetarget=newFile(datedFilename);
if(target.exists())
target.delete();
Filefile=newFile(fileName);
booleanresult=file.renameTo(target);
if(result)
LogLog.debug(fileName+"->"+datedFilename);
else
LogLog.error("Failedtorename["+fileName+"]to["
+datedFilename+"].");
try
{
setFile(fileName,true,bufferedIO,bufferSize);
}
catch(IOExceptione)
{
errorHandler.error("setFile("+fileName
+",true)callfailed.");
}
}
}
}
不过,以上实现,存在2个问题:
1)并发
并发问题可能发生的一个地方在run()中调用closeFile();后,正好subAppend()方法写日志,此刻文件已关闭,则会报以下错误:
java.io.IOException:Streamclosed atsun.nio.cs.StreamEncoder.ensureOpen(UnknownSource) atsun.nio.cs.StreamEncoder.write(UnknownSource) atsun.nio.cs.StreamEncoder.write(UnknownSource) atjava.io.OutputStreamWriter.write(UnknownSource) atjava.io.Writer.write(UnknownSource) ..............................解决方法比较简单,直接让整个run()方法为同步的,加上synchronized关键字即可;不过目前楼主没有解决如果真要写,而且写的速度够快的情况下可能丢失日志的情况;
2)性能
使用Timer实现比较简单,但是Timer里面的任务如果执行时间太长,会独占Timer对象,使得后面的任务无法几时的执行,解决方法也比较简单,采用线程池版定时器类ScheduledExecutorService,实现如下:
/**
*
*/
packagenet.csdn.blog;
importjava.io.File;
importjava.io.IOException;
importjava.text.SimpleDateFormat;
importjava.util.Date;
importjava.util.concurrent.Executors;
importjava.util.concurrent.TimeUnit;
importorg.apache.log4j.FileAppender;
importorg.apache.log4j.Layout;
importorg.apache.log4j.helpers.LogLog;
/**
*@authorcoder_xia
*<p>
*采用ScheduledExecutorService实现定时配置打印日志
*<p>
*
*/
publicclassScheduledExecutorServiceAppenderextendsFileAppender
{
/**
*Thedatepattern.Bydefault,thepatternissetto"'.'yyyy-MM-dd"
*meaningdailyrollover.
*/
privatestaticfinalStringDATEPATTERN="'.'yyyy-MM-dd-HH-mm'.log'";
/**
*间隔时间,单位:分钟
*/
privateintintervalTime=10;
SimpleDateFormatsdf=newSimpleDateFormat(DATEPATTERN);
/**
*Thedefaultconstructordoesnothing.
*/
publicScheduledExecutorServiceAppender()
{
}
/**
*Instantiatea<code>ScheduledExecutorServiceAppender</code>andopenthe
*filedesignatedby<code>filename</code>.Theopenedfilenamewillbecome
*theouputdestinationforthisappender.
*/
publicScheduledExecutorServiceAppender(Layoutlayout,Stringfilename)
throwsIOException
{
super(layout,filename,true);
activateOptions();
}
/**
*@returntheintervalTime
*/
publicintgetIntervalTime()
{
returnintervalTime;
}
/**
*@paramintervalTime
*theintervalTimetoset
*/
publicvoidsetIntervalTime(intintervalTime)
{
this.intervalTime=intervalTime;
}
@Override
publicvoidactivateOptions()
{
super.activateOptions();
Executors.newSingleThreadScheduledExecutor().scheduleAtFixedRate(
newLogTimerTask(),1,intervalTime*60000,
TimeUnit.MILLISECONDS);
}
classLogTimerTaskimplementsRunnable
{
@Override
publicvoidrun()
{
StringdatedFilename=fileName+sdf.format(newDate());
closeFile();
Filetarget=newFile(datedFilename);
if(target.exists())
target.delete();
Filefile=newFile(fileName);
booleanresult=file.renameTo(target);
if(result)
LogLog.debug(fileName+"->"+datedFilename);
else
LogLog.error("Failedtorename["+fileName+"]to["
+datedFilename+"].");
try
{
setFile(fileName,true,bufferedIO,bufferSize);
}
catch(IOExceptione)
{
errorHandler.error("setFile("+fileName
+",true)callfailed.");
}
}
}
}
关于定时的实现,差不多就到这里了,采用的都默认是10分钟产生一个新的日志文件,在配置时可以自行设置,不过存在的一个隐患,万一配置的人不知道时间间隔是分钟,如果以为是秒,配了个600,又开了debug,产生上G的日志文件,这肯定是个灾难,下面的改造就是结合RollingFileAppender的最大大小和最多备份文件个数可配,再次进行完善,下次继续描述改造过程。
添加模块名配置
在前面讲到了log4j定时打印的定制类实现,就不讲指定大小和指定备份文件个数了,从RollingFileAppender类copy代码到前面的定制类中添加即可,唯一需要解决的是并发问题,即文件关闭rename文件时,发生了记录日志事件时,会报outputstreamclosed的错误。
现在有这样一种应用场景,而且经常有:
1.项目包含有多个不同的工程;
2.同一工程包含不同的模块。
对第一种情况,可以通过配置log4j<catogery=“Test”>,再在产生Logger时使用类似如下方式:
Loggerlogger=Logger.getLogger("Test");
对第二种情况,我们希望能够将不同模块打印到同一个日志文件中,不过希望能够在日志中打印出模块名以便出问题时定位问题,因此便有了本文需要的在Appender类中添加配置ModuleName,下面开始改造,与定时打印不同,我们采用RollingFileAppender类为基类进行改造。
首先,添加配置项moduleName,并增加get、set方法;
由于继承自RollingFileAppender,所以只需要在subAppend()中格式化LoggingEvent中的数据,添加formatInfo方法格式化数据,代码略;
最终的成品类如下:
packagenet.csdn.blog;
importorg.apache.log4j.Category;
importorg.apache.log4j.RollingFileAppender;
importorg.apache.log4j.spi.LoggingEvent;
/**
*@authorcoder_xia
*
*/
publicclassModuleAppenderextendsRollingFileAppender
{
privateStringmoduleName;
/**
*@returnthemoduleName
*/
publicStringgetModuleName()
{
returnmoduleName;
}
/**
*@parammoduleName
*themoduleNametoset
*/
publicvoidsetModuleName(StringmoduleName)
{
this.moduleName=moduleName;
}
/**
*格式化打印内容
*
*@paramevent
*event
*@returnmsg
*/
privateStringformatInfo(LoggingEventevent)
{
StringBuildersb=newStringBuilder();
if(moduleName!=null)
{
sb.append(moduleName).append("|");
sb.append(event.getMessage());
}
returnsb.toString();
}
@Override
publicvoidsubAppend(LoggingEventevent)
{
Stringmsg=formatInfo(event);
super.subAppend(newLoggingEvent(Category.class.getName(),event
.getLogger(),event.getLevel(),msg,null));
}
}