[java][log4j]Log4j每天、每小时、每分钟定时生成日志文件

时间:2022-11-01 17:48:33
 接到个需求,通过log4j定时打印日志,需求描述如下:需要能够定时打印日志,时间间隔可配。说到定时,首先想到了DailyRollingFileAppender类,各种定时,根据datePattern,这个可以参考类SimpleDateFormat类,常见的一些定时设置如下: [html] view plaincopyprint?[java][log4j]Log4j每天、每小时、每分钟定时生成日志文件[java][log4j]Log4j每天、每小时、每分钟定时生成日志文件
  1. '.'yyyy-MM: 每月  
  2. '.'yyyy-ww: 每周   
  3. '.'yyyy-MM-dd: 每天  
  4. '.'yyyy-MM-dd-a: 每天两次  
  5. '.'yyyy-MM-dd-HH: 每小时  
  6. '.'yyyy-MM-dd-HH-mm: 每分钟  

    通过观察发现没有n分钟类似的日期格式,因此,在DailyRollingFileAppender类基础上进行自定义类的编写。过程如下:

  1)拷贝DailyRollingFileAppender类源码并并改名MinuteRollingAppender,为了在log4j.xml中配置,增加配置项intervalTime并添加set、get方法;

[java] view plaincopyprint?[java][log4j]Log4j每天、每小时、每分钟定时生成日志文件[java][log4j]Log4j每天、每小时、每分钟定时生成日志文件
  1. private int intervalTime = 10;  

  2)由于DailyRollingFileAppender类使用了RollingCalendar类来计算下一次间隔时间,而需要传递参数intervalTime,因此修改RollingCalendar类为内部类;由于其方法就是根据datePattern来计算下一次rollOver动作的时间,此时不需要其他的时间模式,修改方法如下:

[java] view plaincopyprint?[java][log4j]Log4j每天、每小时、每分钟定时生成日志文件[java][log4j]Log4j每天、每小时、每分钟定时生成日志文件
  1. public Date getNextCheckDate(Date now)  
  2. {  
  3.     this.setTime(now);  
  4.     this.set(Calendar.SECOND, 0);  
  5.     this.set(Calendar.MILLISECOND, 0);  
  6.     this.add(Calendar.MINUTE, intervalTime);  
  7.     return getTime();  
  8. }  

  3)按照分钟可配时,时间模式就需要禁用了,将其改为static final,响应的去掉其get、set方法和MinuteRollingAppender构造函数中的datePattern参数

[java] view plaincopyprint?[java][log4j]Log4j每天、每小时、每分钟定时生成日志文件[java][log4j]Log4j每天、每小时、每分钟定时生成日志文件
  1. private static String DATEPATTERN = "'.'yyyy-MM-dd-HH-mm'.log'";  

    同样,服务于多种datePattern的方法computeCheckPeriod()也可以删除; 至此改造就完成了,成品类如下:

[java] view plaincopyprint?[java][log4j]Log4j每天、每小时、每分钟定时生成日志文件[java][log4j]Log4j每天、每小时、每分钟定时生成日志文件
  1. package net.csdn.blog;  
  2.   
  3. import java.io.File;  
  4. import java.io.IOException;  
  5. import java.io.InterruptedIOException;  
  6. import java.text.SimpleDateFormat;  
  7. import java.util.Calendar;  
  8. import java.util.Date;  
  9. import java.util.GregorianCalendar;  
  10.   
  11. import org.apache.log4j.FileAppender;  
  12. import org.apache.log4j.Layout;  
  13. import org.apache.log4j.helpers.LogLog;  
  14. import org.apache.log4j.spi.LoggingEvent;  
  15.   
  16. /** 
  17.  * 按分钟可配置定时appender 
  18.  *  
  19.  * @author coder_xia 
  20.  *  
  21.  */  
  22. public class MinuteRollingAppender extends FileAppender  
  23. {  
  24.     /** 
  25.      * The date pattern. By default, the pattern is set to "'.'yyyy-MM-dd" 
  26.      * meaning daily rollover. 
  27.      */  
  28.     private static String DATEPATTERN = "'.'yyyy-MM-dd-HH-mm'.log'";  
  29.     /** 
  30.      * 间隔时间,单位:分钟 
  31.      */  
  32.     private int intervalTime = 10;  
  33.   
  34.     /** 
  35.      * The log file will be renamed to the value of the scheduledFilename 
  36.      * variable when the next interval is entered. For example, if the rollover 
  37.      * period is one hour, the log file will be renamed to the value of 
  38.      * "scheduledFilename" at the beginning of the next hour. 
  39.      *  
  40.      * The precise time when a rollover occurs depends on logging activity. 
  41.      */  
  42.     private String scheduledFilename;  
  43.   
  44.     /** 
  45.      * The next time we estimate a rollover should occur. 
  46.      */  
  47.     private long nextCheck = System.currentTimeMillis() - 1;  
  48.   
  49.     Date now = new Date();  
  50.   
  51.     SimpleDateFormat sdf;  
  52.   
  53.     RollingCalendar rc = new RollingCalendar();  
  54.   
  55.     /** 
  56.      * The default constructor does nothing. 
  57.      */  
  58.     public MinuteRollingAppender()  
  59.     {  
  60.     }  
  61.   
  62.     /** 
  63.      * Instantiate a <code>MinuteRollingAppender</code> and open the file 
  64.      * designated by <code>filename</code>. The opened filename will become the 
  65.      * ouput destination for this appender. 
  66.      */  
  67.     public MinuteRollingAppender(Layout layout, String filename)  
  68.             throws IOException  
  69.     {  
  70.         super(layout, filename, true);  
  71.         activateOptions();  
  72.     }  
  73.   
  74.     /** 
  75.      * @return the intervalTime 
  76.      */  
  77.     public int getIntervalTime()  
  78.     {  
  79.         return intervalTime;  
  80.     }  
  81.   
  82.     /** 
  83.      * @param intervalTime 
  84.      *            the intervalTime to set 
  85.      */  
  86.     public void setIntervalTime(int intervalTime)  
  87.     {  
  88.         this.intervalTime = intervalTime;  
  89.     }  
  90.   
  91.     @Override  
  92.     public void activateOptions()  
  93.     {  
  94.         super.activateOptions();  
  95.         if (fileName != null)  
  96.         {  
  97.             now.setTime(System.currentTimeMillis());  
  98.             sdf = new SimpleDateFormat(DATEPATTERN);  
  99.             File file = new File(fileName);  
  100.             scheduledFilename = fileName  
  101.                     + sdf.format(new Date(file.lastModified()));  
  102.   
  103.         }  
  104.         else  
  105.         {  
  106.             LogLog  
  107.                     .error("Either File or DatePattern options are not set for appender ["  
  108.                             + name + "].");  
  109.         }  
  110.     }  
  111.   
  112.     /** 
  113.      * Rollover the current file to a new file. 
  114.      */  
  115.     void rollOver() throws IOException  
  116.     {  
  117.         String datedFilename = fileName + sdf.format(now);  
  118.         // It is too early to roll over because we are still within the  
  119.         // bounds of the current interval. Rollover will occur once the  
  120.         // next interval is reached.  
  121.         if (scheduledFilename.equals(datedFilename))  
  122.         {  
  123.             return;  
  124.         }  
  125.   
  126.         // close current file, and rename it to datedFilename  
  127.         this.closeFile();  
  128.   
  129.         File target = new File(scheduledFilename);  
  130.         if (target.exists())  
  131.         {  
  132.             target.delete();  
  133.         }  
  134.   
  135.         File file = new File(fileName);  
  136.         boolean result = file.renameTo(target);  
  137.         if (result)  
  138.         {  
  139.             LogLog.debug(fileName + " -> " + scheduledFilename);  
  140.         }  
  141.         else  
  142.         {  
  143.             LogLog.error("Failed to rename [" + fileName + "] to ["  
  144.                     + scheduledFilename + "].");  
  145.         }  
  146.   
  147.         try  
  148.         {  
  149.             // This will also close the file. This is OK since multiple  
  150.             // close operations are safe.  
  151.             this.setFile(fileName, truethis.bufferedIO, this.bufferSize);  
  152.         }  
  153.         catch (IOException e)  
  154.         {  
  155.             errorHandler.error("setFile(" + fileName + ", true) call failed.");  
  156.         }  
  157.         scheduledFilename = datedFilename;  
  158.     }  
  159.   
  160.     /** 
  161.      * This method differentiates MinuteRollingAppender from its super class. 
  162.      *  
  163.      * <p> 
  164.      * Before actually logging, this method will check whether it is time to do 
  165.      * a rollover. If it is, it will schedule the next rollover time and then 
  166.      * rollover. 
  167.      * */  
  168.     @Override  
  169.     protected void subAppend(LoggingEvent event)  
  170.     {  
  171.         long n = System.currentTimeMillis();  
  172.         if (n >= nextCheck)  
  173.         {  
  174.             now.setTime(n);  
  175.             nextCheck = rc.getNextCheckMillis(now);  
  176.             try  
  177.             {  
  178.                 rollOver();  
  179.             }  
  180.             catch (IOException ioe)  
  181.             {  
  182.                 if (ioe instanceof InterruptedIOException)  
  183.                 {  
  184.                     Thread.currentThread().interrupt();  
  185.                 }  
  186.                 LogLog.error("rollOver() failed.", ioe);  
  187.             }  
  188.         }  
  189.         super.subAppend(event);  
  190.     }  
  191.   
  192.     /** 
  193.      * RollingCalendar is a helper class to MinuteRollingAppender. Given a 
  194.      * periodicity type and the current time, it computes the start of the next 
  195.      * interval. 
  196.      * */  
  197.     class RollingCalendar extends GregorianCalendar  
  198.     {  
  199.         private static final long serialVersionUID = -3560331770601814177L;  
  200.   
  201.         RollingCalendar()  
  202.         {  
  203.             super();  
  204.         }  
  205.   
  206.         public long getNextCheckMillis(Date now)  
  207.         {  
  208.             return getNextCheckDate(now).getTime();  
  209.         }  
  210.   
  211.         public Date getNextCheckDate(Date now)  
  212.         {  
  213.             this.setTime(now);  
  214.             this.set(Calendar.SECOND, 0);  
  215.             this.set(Calendar.MILLISECOND, 0);  
  216.             this.add(Calendar.MINUTE, intervalTime);  
  217.             return getTime();  
  218.         }  
  219.     }  
  220. }  


测试配置文件如下:

[java] view plaincopyprint?[java][log4j]Log4j每天、每小时、每分钟定时生成日志文件[java][log4j]Log4j每天、每小时、每分钟定时生成日志文件
  1. <?xml version="1.0" encoding="UTF-8"?>  
  2. <!DOCTYPE log4j:configuration SYSTEM "log4j.dtd">  
  3.   
  4. <log4j:configuration xmlns:log4j="http://jakarta.apache.org/log4j/">  
  5.   
  6.     <appender name="myFile" class="net.csdn.blog.MinuteRollingAppender">       
  7.         <param name="File" value="log4jTest.log" />  
  8.         <param name="Append" value="true" />    
  9.         <param name="intervalTime" value="2"/>  
  10.         <layout class="org.apache.log4j.PatternLayout">    
  11.             <param name="ConversionPattern" value="%p %d (%c:%L)- %m%n" />    
  12.         </layout>    
  13.     </appender>    
  14.   
  15.     <root>    
  16.         <priority value="debug"/>    
  17.         <appender-ref ref="myFile"/>       
  18.     </root>    
  19.   
  20. </log4j:configuration>  

      关于定时实现,还可以采用java提供的Timer实现,也就免去了每次记录日志时计算并且比较时间,区别其实就是自己起个线程与调用rollOver方法,实现如下:

[java] view plaincopyprint?[java][log4j]Log4j每天、每小时、每分钟定时生成日志文件[java][log4j]Log4j每天、每小时、每分钟定时生成日志文件
  1. package net.csdn.blog;  
  2.   
  3. import java.io.File;  
  4. import java.io.IOException;  
  5. import java.text.SimpleDateFormat;  
  6. import java.util.Date;  
  7. import java.util.Timer;  
  8. import java.util.TimerTask;  
  9.   
  10. import org.apache.log4j.FileAppender;  
  11. import org.apache.log4j.Layout;  
  12. import org.apache.log4j.helpers.LogLog;  
  13.   
  14. public class TimerTaskRollingAppender extends FileAppender  
  15. {  
  16.     /** 
  17.      * The date pattern. By default, the pattern is set to "'.'yyyy-MM-dd" 
  18.      * meaning daily rollover. 
  19.      */  
  20.     private static final String DATEPATTERN = "'.'yyyy-MM-dd-HH-mm'.log'";  
  21.   
  22.     /** 
  23.      * 间隔时间,单位:分钟 
  24.      */  
  25.     private int intervalTime = 10;  
  26.   
  27.     SimpleDateFormat sdf = new SimpleDateFormat(DATEPATTERN);  
  28.   
  29.     /** 
  30.      * The default constructor does nothing. 
  31.      */  
  32.     public TimerTaskRollingAppender()  
  33.     {  
  34.     }  
  35.   
  36.     /** 
  37.      * Instantiate a <code>TimerTaskRollingAppender</code> and open the file 
  38.      * designated by <code>filename</code>. The opened filename will become the 
  39.      * ouput destination for this appender. 
  40.      */  
  41.     public TimerTaskRollingAppender(Layout layout, String filename)  
  42.             throws IOException  
  43.     {  
  44.         super(layout, filename, true);  
  45.         activateOptions();  
  46.     }  
  47.   
  48.     /** 
  49.      * @return the intervalTime 
  50.      */  
  51.     public int getIntervalTime()  
  52.     {  
  53.         return intervalTime;  
  54.     }  
  55.   
  56.     /** 
  57.      * @param intervalTime 
  58.      *            the intervalTime to set 
  59.      */  
  60.     public void setIntervalTime(int intervalTime)  
  61.     {  
  62.         this.intervalTime = intervalTime;  
  63.     }  
  64.   
  65.     @Override  
  66.     public void activateOptions()  
  67.     {  
  68.         super.activateOptions();  
  69.         Timer timer = new Timer();  
  70.         timer.schedule(new LogTimerTask(), 1000, intervalTime * 60000);  
  71.     }  
  72.   
  73.     class LogTimerTask extends TimerTask  
  74.     {  
  75.         @Override  
  76.         public void run()  
  77.         {  
  78.             String datedFilename = fileName + sdf.format(new Date());  
  79.             closeFile();  
  80.             File target = new File(datedFilename);  
  81.             if (target.exists())  
  82.                 target.delete();  
  83.             File file = new File(fileName);  
  84.             boolean result = file.renameTo(target);  
  85.             if (result)  
  86.                 LogLog.debug(fileName + " -> " + datedFilename);  
  87.             else  
  88.                 LogLog.error("Failed to rename [" + fileName + "] to ["  
  89.                         + datedFilename + "].");  
  90.             try  
  91.             {  
  92.                 setFile(fileName, true, bufferedIO, bufferSize);  
  93.             }  
  94.             catch (IOException e)  
  95.             {  
  96.                 errorHandler.error("setFile(" + fileName  
  97.                         + ", true) call failed.");  
  98.             }  
  99.         }  
  100.     }  
  101. }  

    不过,以上实现,存在2个问题:

   1)并发

    并发问题可能发生的一个地方在run()中调用closeFile();后,正好subAppend()方法写日志,此刻文件已关闭,则会报以下错误:

[java] view plaincopyprint?[java][log4j]Log4j每天、每小时、每分钟定时生成日志文件[java][log4j]Log4j每天、每小时、每分钟定时生成日志文件
  1. java.io.IOException: Stream closed  
  2.     at sun.nio.cs.StreamEncoder.ensureOpen(Unknown Source)  
  3.     at sun.nio.cs.StreamEncoder.write(Unknown Source)  
  4.     at sun.nio.cs.StreamEncoder.write(Unknown Source)  
  5.     at java.io.OutputStreamWriter.write(Unknown Source)  
  6.     at java.io.Writer.write(Unknown Source)  
  7. ..............................  
   解决方法比较简单,直接让整个run()方法为同步的,加上synchronized关键字即可;不过目前 楼主没有解决如果真要写,而且写的速度够快的情况下可能丢失日志的情况;

   2)性能

    使用Timer实现比较简单,但是Timer里面的任务如果执行时间太长,会独占Timer对象,使得后面的任务无法几时的执行,解决方法也比较简单,采用线程池版定时器类ScheduledExecutorService,实现如下:

[java] view plaincopyprint?[java][log4j]Log4j每天、每小时、每分钟定时生成日志文件[java][log4j]Log4j每天、每小时、每分钟定时生成日志文件
  1. /** 
  2.  *  
  3.  */  
  4. package net.csdn.blog;  
  5.   
  6. import java.io.File;  
  7. import java.io.IOException;  
  8. import java.text.SimpleDateFormat;  
  9. import java.util.Date;  
  10. import java.util.concurrent.Executors;  
  11. import java.util.concurrent.TimeUnit;  
  12.   
  13. import org.apache.log4j.FileAppender;  
  14. import org.apache.log4j.Layout;  
  15. import org.apache.log4j.helpers.LogLog;  
  16.   
  17. /** 
  18.  * @author coder_xia 
  19.  *         <p> 
  20.  *         采用ScheduledExecutorService实现定时配置打印日志 
  21.  *         <p> 
  22.  *  
  23.  */  
  24. public class ScheduledExecutorServiceAppender extends FileAppender  
  25. {  
  26.     /** 
  27.      * The date pattern. By default, the pattern is set to "'.'yyyy-MM-dd" 
  28.      * meaning daily rollover. 
  29.      */  
  30.     private static final String DATEPATTERN = "'.'yyyy-MM-dd-HH-mm'.log'";  
  31.   
  32.     /** 
  33.      * 间隔时间,单位:分钟 
  34.      */  
  35.     private int intervalTime = 10;  
  36.   
  37.     SimpleDateFormat sdf = new SimpleDateFormat(DATEPATTERN);  
  38.   
  39.     /** 
  40.      * The default constructor does nothing. 
  41.      */  
  42.     public ScheduledExecutorServiceAppender()  
  43.     {  
  44.     }  
  45.   
  46.     /** 
  47.      * Instantiate a <code>ScheduledExecutorServiceAppender</code> and open the 
  48.      * file designated by <code>filename</code>. The opened filename will become 
  49.      * the ouput destination for this appender. 
  50.      */  
  51.     public ScheduledExecutorServiceAppender(Layout layout, String filename)  
  52.             throws IOException  
  53.     {  
  54.         super(layout, filename, true);  
  55.         activateOptions();  
  56.     }  
  57.   
  58.     /** 
  59.      * @return the intervalTime 
  60.      */  
  61.     public int getIntervalTime()  
  62.     {  
  63.         return intervalTime;  
  64.     }  
  65.   
  66.     /** 
  67.      * @param intervalTime 
  68.      *            the intervalTime to set 
  69.      */  
  70.     public void setIntervalTime(int intervalTime)  
  71.     {  
  72.         this.intervalTime = intervalTime;  
  73.     }  
  74.   
  75.     @Override  
  76.     public void activateOptions()  
  77.     {  
  78.         super.activateOptions();  
  79.         Executors.newSingleThreadScheduledExecutor().scheduleAtFixedRate(  
  80.                 new LogTimerTask(), 1, intervalTime * 60000,  
  81.                 TimeUnit.MILLISECONDS);  
  82.     }  
  83.   
  84.     class LogTimerTask implements Runnable  
  85.     {  
  86.         @Override  
  87.         public void run()  
  88.         {  
  89.             String datedFilename = fileName + sdf.format(new Date());  
  90.             closeFile();  
  91.             File target = new File(datedFilename);  
  92.             if (target.exists())  
  93.                 target.delete();  
  94.             File file = new File(fileName);  
  95.             boolean result = file.renameTo(target);  
  96.             if (result)  
  97.                 LogLog.debug(fileName + " -> " + datedFilename);  
  98.             else  
  99.                 LogLog.error("Failed to rename [" + fileName + "] to ["  
  100.                         + datedFilename + "].");  
  101.             try  
  102.             {  
  103.                 setFile(fileName, true, bufferedIO, bufferSize);  
  104.             }  
  105.             catch (IOException e)  
  106.             {  
  107.                 errorHandler.error("setFile(" + fileName  
  108.                         + ", true) call failed.");  
  109.             }  
  110.         }  
  111.     }  
  112. }  

      关于定时的实现,差不多就到这里了,采用的都默认是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