log4j定制类实现(一):配置间隔时间,定时打印日志
来源:互联网 发布:seo搜索好学吗 编辑:程序博客网 时间:2024/06/06 00:16
接到个需求,通过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;}@Overridepublic 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 datedFilenamethis.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. * */@Overrideprotected 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;}@Overridepublic void activateOptions(){super.activateOptions();Timer timer = new Timer();timer.schedule(new LogTimerTask(), 1000, intervalTime * 60000);}class LogTimerTask extends TimerTask{@Overridepublic 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);elseLogLog.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 closedat 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)..............................解决方法比较简单,直接让整个run()方法为同步的,加上synchronized关键字即可;不过目前楼主没有解决如果真要写,而且写的速度够快的情况下可能丢失日志的情况;
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;}@Overridepublic void activateOptions(){super.activateOptions();Executors.newSingleThreadScheduledExecutor().scheduleAtFixedRate(new LogTimerTask(), 1, intervalTime * 60000,TimeUnit.MILLISECONDS);}class LogTimerTask implements Runnable{@Overridepublic 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);elseLogLog.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,有很多比较好的log4j的参考链接,比如:
1.http://www.iteye.com/topic/378077
2.http://www.cnblogs.com/duanxz/archive/2013/01/28/2880240.html
- log4j定制类实现(一):配置间隔时间,定时打印日志
- log4j 日志打印 配置
- log4j打印日志配置
- log4j定制类实现(二):添加模块名配置
- log4j日志打印的配置
- log4j日志配置(一)
- mybatis打印日志的log4j.xml配置
- Log4j配置spring+druid打印日志
- mybatis打印日志的log4j.xml配置
- mybatis配置log4j打印sql日志
- Log4j日志分文件打印配置规则
- log4j.properties打印日志信息(1)
- log4j.xml打印日志信息(2)
- Spring(4):log4j打印日志
- 使用Log4j打印日志
- log4j日志打印
- Log4j 日志打印
- log4j 日志打印总结
- 黑马程序员——throw于throws的区别
- TCP中ECN的工作原理分析二(摘自:RFC3168)
- Unicode 多字节 utf-8互转
- oracle什么时候需要commit .
- HDU 1384 Intervals(差分约束)
- log4j定制类实现(一):配置间隔时间,定时打印日志
- POJ3461_Oulipo_KMP_求重复子串的个数_可重叠
- CSDN首发-Flex 4.0 网络大讲堂-Flex教程-窗内网
- matlab基本操作学习(3)
- 互联网项目开发版本划分
- JSM消息模型简介
- STL
- 在windows中安装ubuntu
- CSDN首发-Oracle 11g网络大讲堂-Oracle教程-窗内网