- '.'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,有很多比较好的log4j的参考链接,比如:
1.http://www.iteye.com/topic/378077
2.http://www.cnblogs.com/duanxz/archive/2013/01/28/2880240.html