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方法;
private int intervaltime = 10;
2)由于dailyrollingfileappender类使用了rollingcalendar类来计算下一次间隔时间,而需要传递参数intervaltime,因此修改rollingcalendar类为内部类;由于其方法就是根据datepattern来计算下一次rollover动作的时间,此时不需要其他的时间模式,修改方法如下:
public date getnextcheckdate(date now) { this.settime(now); this.set(calendar.second, 0); this.set(calendar.millisecond, 0); this.add(calendar.minute, intervaltime); return gettime(); }
3)按照分钟可配时,时间模式就需要禁用了,将其改为static final,响应的去掉其get、set方法和minuterollingappender构造函数中的datepattern参数
private static string datepattern = "'.'yyyy-mm-dd-hh-mm'.log'";
同样,服务于多种datepattern的方法computecheckperiod()也可以删除; 至此改造就完成了,成品类如下:
package net.csdn.blog; import java.io.file; import java.io.ioexception; import java.io.interruptedioexception; import java.text.simpledateformat; import java.util.calendar; import java.util.date; import java.util.gregoriancalendar; import org.apache.log4j.fileappender; import org.apache.log4j.layout; import org.apache.log4j.helpers.loglog; import org.apache.log4j.spi.loggingevent; /** * 按分钟可配置定时appender * * @author coder_xia * */ public class minuterollingappender extends fileappender { /** * the date pattern. by default, the pattern is set to "'.'yyyy-mm-dd" * meaning daily rollover. */ private static string datepattern = "'.'yyyy-mm-dd-hh-mm'.log'"; /** * 间隔时间,单位:分钟 */ private int intervaltime = 10; /** * the log file will be renamed to the value of the scheduledfilename * variable when the next interval is entered. for example, if the rollover * period is one hour, the log file will be renamed to the value of * "scheduledfilename" at the beginning of the next hour. * * the precise time when a rollover occurs depends on logging activity. */ private string scheduledfilename; /** * the next time we estimate a rollover should occur. */ private long nextcheck = system.currenttimemillis() - 1; date now = new date(); simpledateformat sdf; rollingcalendar rc = new rollingcalendar(); /** * the default constructor does nothing. */ public minuterollingappender() { } /** * instantiate a <code>minuterollingappender</code> and open the file * designated by <code>filename</code>. the opened filename will become the * ouput destination for this appender. */ public minuterollingappender(layout layout, string filename) throws ioexception { super(layout, filename, true); activateoptions(); } /** * @return the intervaltime */ public int getintervaltime() { return intervaltime; } /** * @param intervaltime * the intervaltime to set */ public void setintervaltime(int intervaltime) { this.intervaltime = intervaltime; } @override public void activateoptions() { super.activateoptions(); if (filename != null) { now.settime(system.currenttimemillis()); sdf = new simpledateformat(datepattern); file file = new file(filename); scheduledfilename = filename + sdf.format(new date(file.lastmodified())); } else { loglog .error("either file or datepattern options are not set for appender [" + name + "]."); } } /** * rollover the current file to a new file. */ void rollover() throws ioexception { 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, true, this.bufferedio, this.buffersize); } catch (ioexception e) { errorhandler.error("setfile(" + filename + ", true) call failed."); } scheduledfilename = datedfilename; } /** * this method differentiates minuterollingappender from its super class. * * <p> * before actually logging, this method will check whether it is time to do * a rollover. if it is, it will schedule the next rollover time and then * rollover. * */ @override protected void subappend(loggingevent event) { long n = system.currenttimemillis(); if (n >= nextcheck) { now.settime(n); nextcheck = rc.getnextcheckmillis(now); try { rollover(); } catch (ioexception ioe) { if (ioe instanceof interruptedioexception) { thread.currentthread().interrupt(); } loglog.error("rollover() failed.", ioe); } } super.subappend(event); } /** * rollingcalendar is a helper class to minuterollingappender. given a * periodicity type and the current time, it computes the start of the next * interval. * */ class rollingcalendar extends gregoriancalendar { private static final long serialversionuid = -3560331770601814177l; rollingcalendar() { super(); } public long getnextcheckmillis(date now) { return getnextcheckdate(now).gettime(); } public date getnextcheckdate(date now) { this.settime(now); this.set(calendar.second, 0); this.set(calendar.millisecond, 0); this.add(calendar.minute, intervaltime); return gettime(); } } }
测试配置文件如下:
<?xml version="1.0" encoding="utf-8"?> <!doctype log4j:configuration system "log4j.dtd"> <log4j:configuration xmlns:log4j="http://jakarta.apache.org/log4j/"> <appender name="myfile" class="net.csdn.blog.minuterollingappender"> <param name="file" value="log4jtest.log" /> <param name="append" value="true" /> <param name="intervaltime" value="2"/> <layout class="org.apache.log4j.patternlayout"> <param name="conversionpattern" value="%p %d (%c:%l)- %m%n" /> </layout> </appender> <root> <priority value="debug"/> <appender-ref ref="myfile"/> </root> </log4j:configuration>
关于定时实现,还可以采用java提供的timer实现,也就免去了每次记录日志时计算并且比较时间,区别其实就是自己起个线程与调用rollover方法,实现如下:
package net.csdn.blog; import java.io.file; import java.io.ioexception; import java.text.simpledateformat; import java.util.date; import java.util.timer; import java.util.timertask; import org.apache.log4j.fileappender; import org.apache.log4j.layout; import org.apache.log4j.helpers.loglog; public class timertaskrollingappender extends fileappender { /** * the date pattern. by default, the pattern is set to "'.'yyyy-mm-dd" * meaning daily rollover. */ private static final string datepattern = "'.'yyyy-mm-dd-hh-mm'.log'"; /** * 间隔时间,单位:分钟 */ private int intervaltime = 10; simpledateformat sdf = new simpledateformat(datepattern); /** * the default constructor does nothing. */ public timertaskrollingappender() { } /** * instantiate a <code>timertaskrollingappender</code> and open the file * designated by <code>filename</code>. the opened filename will become the * ouput destination for this appender. */ public timertaskrollingappender(layout layout, string filename) throws ioexception { super(layout, filename, true); activateoptions(); } /** * @return the intervaltime */ public int getintervaltime() { return intervaltime; } /** * @param intervaltime * the intervaltime to set */ public void setintervaltime(int intervaltime) { this.intervaltime = intervaltime; } @override public void activateoptions() { super.activateoptions(); timer timer = new timer(); timer.schedule(new logtimertask(), 1000, intervaltime * 60000); } class logtimertask extends timertask { @override public void run() { string datedfilename = filename + sdf.format(new date()); closefile(); file target = new file(datedfilename); if (target.exists()) target.delete(); file file = new file(filename); boolean result = file.renameto(target); if (result) loglog.debug(filename + " -> " + datedfilename); else loglog.error("failed to rename [" + filename + "] to [" + datedfilename + "]."); try { setfile(filename, true, bufferedio, buffersize); } catch (ioexception e) { errorhandler.error("setfile(" + filename + ", true) call failed."); } } } }
不过,以上实现,存在2个问题:
1)并发
并发问题可能发生的一个地方在run()中调用closefile();后,正好subappend()方法写日志,此刻文件已关闭,则会报以下错误:
java.io.ioexception: stream closed at sun.nio.cs.streamencoder.ensureopen(unknown source) at sun.nio.cs.streamencoder.write(unknown source) at sun.nio.cs.streamencoder.write(unknown source) at java.io.outputstreamwriter.write(unknown source) at java.io.writer.write(unknown source) ..............................
2)性能
使用timer实现比较简单,但是timer里面的任务如果执行时间太长,会独占timer对象,使得后面的任务无法几时的执行,解决方法也比较简单,采用线程池版定时器类scheduledexecutorservice,实现如下:
/** * */ package net.csdn.blog; import java.io.file; import java.io.ioexception; import java.text.simpledateformat; import java.util.date; import java.util.concurrent.executors; import java.util.concurrent.timeunit; import org.apache.log4j.fileappender; import org.apache.log4j.layout; import org.apache.log4j.helpers.loglog; /** * @author coder_xia * <p> * 采用scheduledexecutorservice实现定时配置打印日志 * <p> * */ public class scheduledexecutorserviceappender extends fileappender { /** * the date pattern. by default, the pattern is set to "'.'yyyy-mm-dd" * meaning daily rollover. */ private static final string datepattern = "'.'yyyy-mm-dd-hh-mm'.log'"; /** * 间隔时间,单位:分钟 */ private int intervaltime = 10; simpledateformat sdf = new simpledateformat(datepattern); /** * the default constructor does nothing. */ public scheduledexecutorserviceappender() { } /** * instantiate a <code>scheduledexecutorserviceappender</code> and open the * file designated by <code>filename</code>. the opened filename will become * the ouput destination for this appender. */ public scheduledexecutorserviceappender(layout layout, string filename) throws ioexception { super(layout, filename, true); activateoptions(); } /** * @return the intervaltime */ public int getintervaltime() { return intervaltime; } /** * @param intervaltime * the intervaltime to set */ public void setintervaltime(int intervaltime) { this.intervaltime = intervaltime; } @override public void activateoptions() { super.activateoptions(); executors.newsinglethreadscheduledexecutor().scheduleatfixedrate( new logtimertask(), 1, intervaltime * 60000, timeunit.milliseconds); } class logtimertask implements runnable { @override public void run() { string datedfilename = filename + sdf.format(new date()); closefile(); file target = new file(datedfilename); if (target.exists()) target.delete(); file file = new file(filename); boolean result = file.renameto(target); if (result) loglog.debug(filename + " -> " + datedfilename); else loglog.error("failed to rename [" + filename + "] to [" + datedfilename + "]."); try { setfile(filename, true, bufferedio, buffersize); } catch (ioexception e) { errorhandler.error("setfile(" + filename + ", true) call failed."); } } } }
关于定时的实现,差不多就到这里了,采用的都默认是10分钟产生一个新的日志文件,在配置时可以自行设置,不过存在的一个隐患,万一配置的人不知道时间间隔是分钟,如果以为是秒,配了个600,又开了debug,产生上g的日志文件,这肯定是个灾难,下面的改造就是结合rollingfileappender的最大大小和最多备份文件个数可配,再次进行完善,下次继续描述改造过程。
添加模块名配置
在前面讲到了log4j定时打印的定制类实现,就不讲指定大小和指定备份文件个数了,从rollingfileappender类copy代码到前面的定制类中添加即可,唯一需要解决的是并发问题,即文件关闭rename文件时,发生了记录日志事件时,会报output stream closed的错误。
现在有这样一种应用场景,而且经常有:
1.项目包含有多个不同的工程;
2.同一工程包含不同的模块。
对第一种情况,可以通过配置log4j<catogery=“test”>,再在产生logger时使用类似如下方式:
logger logger=logger.getlogger("test");
对第二种情况,我们希望能够将不同模块打印到同一个日志文件中,不过希望能够在日志中打印出模块名以便出问题时定位问题,因此便有了本文需要的在appender类中添加配置modulename,下面开始改造,与定时打印不同,我们采用rollingfileappender类为基类进行改造。
首先,添加配置项modulename,并增加get、set方法;
由于继承自rollingfileappender,所以只需要在subappend()中格式化loggingevent中的数据,添加formatinfo方法格式化数据,代码略;
最终的成品类如下:
package net.csdn.blog; import org.apache.log4j.category; import org.apache.log4j.rollingfileappender; import org.apache.log4j.spi.loggingevent; /** * @author coder_xia * */ public class moduleappender extends rollingfileappender { private string modulename; /** * @return the modulename */ public string getmodulename() { return modulename; } /** * @param modulename * the modulename to set */ public void setmodulename(string modulename) { this.modulename = modulename; } /** * 格式化打印内容 * * @param event * event * @return msg */ private string formatinfo(loggingevent event) { stringbuilder sb = new stringbuilder(); if (modulename != null) { sb.append(modulename).append("|"); sb.append(event.getmessage()); } return sb.tostring(); } @override public void subappend(loggingevent event) { string msg = formatinfo(event); super.subappend(new loggingevent(category.class.getname(), event .getlogger(), event.getlevel(), msg, null)); } }
上一篇: 学习Java设计模式之观察者模式