扩展log4j系列[二]为DailyRollingFileAppender加上maxBackupIndex属性

在log4j的大多数appender中,都有maxBackupIndex属性,但是这个DailyRollingFileAppender没有,也就是说它会每天滚一个文件,却没有办法控制文件总个数。这绝对是系统的一个“着火点”,下面就开始动手改造了:

 

一。研究整个log4j的appender结构:

    对框架的一个模块进行扩展,并非总是直接继承某个类就好了,如果不进一步深入研究就有可能掉入某些陷阱。(比如扩展log4j的Logger类,直接继承它并不能得到任何好处,具体解释清参考官方文档。),还好log4j对level,appender,layerout都扩展有很好支持的。


 

然后就是看log4j的配置文件了。 配置文件是可以直接配置扩展appender属性的,这样就替我们节省了一堆定义、解析、处理的过程

 

Java代码 复制代码
  1. <SPAN style="COLOR: #ff0000"># 给自己的类取个对应的名</SPAN>   
  2.   
  3.   
  4. log4j.appender.appenderName=fully.qualified.name.of.appender.class    
  5.     
  6. <SPAN style="COLOR: #ff0000">#还可以给自己的类property设置值,也就是说扩展的maxBackupIndex属性可以配置</SPAN>   
  7.   
  8.   
  9. log4j.appender.appenderName.option1=value1      
  10. ...    
  11. log4j.appender.appenderName.optionN=valueN    

   

 

二。大致胸有成竹后,可以开始看DailyRollingFileAppender的源码了。

直接看属性跟方法结构

 

大致可以猜出这个类做了如下几个事情:继承了根类appender、支持DatePattern解析并针对DatePattern设置的滚动条件组装filename、实现“监听”方法,到时间点切换logfile。。。 大部分的工作都给我们做好了:)

 

现在唯一需要改动的就是,“切换文件”方法,在切换新文件的同时,删除掉最老的n个log。

 

Java代码 复制代码
  1. /**  
  2.    Rollover the current file to a new file.  
  3. */  
  4. void rollOver() throws IOException {   
  5.   
  6.   /* Compute filename, but only if datePattern is specified */  
  7.   if (datePattern == null) {   
  8.     errorHandler.error("Missing DatePattern option in rollOver().");   
  9.     return;   
  10.   }   
  11.   
  12.   String datedFilename = fileName+sdf.format(now);   
  13.   // It is too early to roll over because we are still within the   
  14.   // bounds of the current interval. Rollover will occur once the   
  15.   // next interval is reached.   
  16.   if (scheduledFilename.equals(datedFilename)) {   
  17.     return;   
  18.   }   
  19.   
  20.   // close current file, and rename it to datedFilename   
  21.   this.closeFile();   
  22.   
  23.   File target  = new File(scheduledFilename);   
  24.   if (target.exists()) {   
  25.     target.delete();   
  26.   }   
  27.   
  28.   File file = new File(fileName);   
  29.   boolean result = file.renameTo(target);   
  30.   if(result) {   
  31.     LogLog.debug(fileName +" -> "+ scheduledFilename);   
  32.   } else {   
  33.     LogLog.error("Failed to rename ["+fileName+"] to ["+scheduledFilename+"].");   
  34.   }   
  35.   
  36.   try {   
  37.     // This will also close the file. This is OK since multiple   
  38.     // close operations are safe.   
  39.     this.setFile(fileName, falsethis.bufferedIO, this.bufferSize);   
  40.   }   
  41.   catch(IOException e) {   
  42.     errorHandler.error("setFile("+fileName+", false) call failed.");   
  43.   }   
  44.   scheduledFilename = datedFilename;   
  45. }  

 

      看到这里就发现问题了,由于DatePattern格式可配置,那么产生的滚动的文件名也是不同的,也没有什么规律可循。

      比如".yyyy-ww",是按周滚动,当配置改成".yyyy-MM "按月滚动之后,通过文件名匹配删除旧文件将会导致错误。    

       另外,日志文件的切换不是定时轮询而是事件促发机制,只有在进行写操作的时候才会去判断是否需要滚动文件!那么写操作在跨过一个滚动周期执行的时候,文件名会产生空缺而不保证连续性。

       也许这就是log4j本身没有对这个appender做文件个数限制的原因吧。

 

三。妥协吧。

    框架的功能总是尽量强大的,但使用总是最简单的功能!在IDC环境中通常是不允许按时间滚动记log的,主要是防止日志文件撑爆硬盘成为着火点。 这里考虑启用按时间滚动,主要是性能日志的统计脚本需要日志文件以日期为名按天存储,并且只需要备份前一天的即可.

 

    那么我的需求就简单了:简化功能!

   仿造DailyRollingFileAppender实现1.仅支持按天滚动的 、2.格式写死的DatePattern ,3.最大备份文件个数为n的appender 。(备份数可配考虑灵活性,但一定要有参数检查预防万一!)

    限制datepattern,一方面可以防止配错,弄成按月滚动肯定死翘翘;另一方面也容易处理MaxBackupIndex删除历史文件。 more,既然知道是按天滚动,check的方法当然可以简化了:

 

 

最终修改版的按天滚动appender如下:

 

Java代码 复制代码
  1. package cxxxxxxxj;   
  2.   
  3. import java.io.File;   
  4. import java.io.IOException;   
  5. import java.text.SimpleDateFormat;   
  6. import java.util.ArrayList;   
  7. import java.util.Calendar;   
  8. import java.util.Date;   
  9. import java.util.List;   
  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.  * 暂时不支持datePattern设置,但是可以配置maxBackupIndex  
  19.  * @author weisong  
  20.  *  
  21.  */  
  22. public class DayRollingFileAppender extends FileAppender {   
  23.   
  24.   
  25.   /**不允许改写的datepattern */  
  26.   private final String datePattern = "'.'yyyy-MM-dd";   
  27.      
  28.   /**最多文件增长个数*/  
  29.   private int  maxBackupIndex = 2;   
  30.      
  31.   /**"文件名+上次最后更新时间"*/  
  32.   private String scheduledFilename;   
  33.   
  34.   /**  
  35.      The next time we estimate a rollover should occur. */  
  36.   private long nextCheck = System.currentTimeMillis () - 1;   
  37.   
  38.   Date now = new Date();   
  39.   
  40.   SimpleDateFormat sdf;   
  41.   
  42.   /**  
  43.      The default constructor does nothing. */  
  44.   public DayRollingFileAppender() {   
  45.   }   
  46.   
  47.   /**  
  48.         改造过的构造器  
  49.     */  
  50.   public DayRollingFileAppender (Layout layout, String filename,   
  51.           int  maxBackupIndex) throws IOException {   
  52.     super(layout, filename, true);   
  53.     this.maxBackupIndex = maxBackupIndex;   
  54.     activateOptions();   
  55.   }   
  56.   
  57.   
  58.   /**  
  59.    * 初始化本Appender对象的时候调用一次  
  60.    */  
  61.   public void activateOptions() {   
  62.     super.activateOptions();   
  63.     if(fileName != null) { //perf.log   
  64.       now.setTime(System.currentTimeMillis());   
  65.       sdf = new SimpleDateFormat(datePattern);   
  66.       File file = new File(fileName);   
  67.       //获取最后更新时间拼成的文件名   
  68.       scheduledFilename = fileName+sdf.format(new Date(file.lastModified()));   
  69.     } else {   
  70.       LogLog.error("File is not set for appender ["+name+"].");   
  71.     }   
  72.     if(maxBackupIndex<=0) {   
  73.         LogLog.error("maxBackupIndex reset to default value[2],orignal value is:" + maxBackupIndex);   
  74.         maxBackupIndex=2;   
  75.     }   
  76.   }   
  77.   
  78.   
  79.   /**  
  80.          滚动文件的函数:  
  81.          1.对文件名带的时间戳进行比较,确定是否更新  
  82.          2.if需要更新,当前文件rename到文件名+日期, 重新开始写文件  
  83.          3. 针对配置的maxBackupIndex,删除过期的文件  
  84.   */  
  85.     void rollOver() throws IOException {   
  86.   
  87.         String datedFilename = fileName + sdf.format(now);   
  88.         // 如果上次写的日期跟当前日期相同,不需要换文件   
  89.         if (scheduledFilename.equals(datedFilename)) {   
  90.             return;   
  91.         }   
  92.   
  93.         // close current file, and rename it to datedFilename   
  94.         this.closeFile();   
  95.   
  96.         File target = new File(scheduledFilename);   
  97.         if (target.exists()) {   
  98.             target.delete();   
  99.         }   
  100.   
  101.         File file = new File(fileName);   
  102.         boolean result = file.renameTo(target);   
  103.         if (result) {   
  104.             LogLog.debug(fileName + " -> " + scheduledFilename);   
  105.         } else {   
  106.             LogLog.error("Failed to rename [" + fileName + "] to ["  
  107.                     + scheduledFilename + "].");   
  108.         }   
  109.   
  110.         // 删除过期文件   
  111.         if (maxBackupIndex > 0) {   
  112.             File folder = new File(file.getParent());   
  113.             List<String> maxBackupIndexDates = getMaxBackupIndexDates();   
  114.             for (File ff : folder.listFiles()) { //遍历目录,将日期不在备份范围内的日志删掉   
  115.                 if (ff.getName().startsWith(file.getName()) && !ff.getName().equals(file.getName())) {   
  116.                     //获取文件名带的日期时间戳   
  117.                     String markedDate = ff.getName().substring(file.getName().length());   
  118.                     if (!maxBackupIndexDates.contains(markedDate)) {   
  119.                         result = ff.delete();   
  120.                     }   
  121.                     if (result) {   
  122.                         LogLog.debug(ff.getName() + " ->deleted ");   
  123.                     } else {   
  124.                         LogLog.error("Failed to deleted old DayRollingFileAppender file :" + ff.getName());   
  125.                     }   
  126.                 }   
  127.             }   
  128.         }   
  129.   
  130.         try {   
  131.             // This will also close the file. This is OK since multiple   
  132.             // close operations are safe.   
  133.             this.setFile(fileName, falsethis.bufferedIO, this.bufferSize);   
  134.         } catch (IOException e) {   
  135.             errorHandler.error("setFile(" + fileName + ", false) call failed.");   
  136.         }   
  137.         scheduledFilename = datedFilename; // 更新最后更新日期戳   
  138.     }   
  139.   
  140.   /**  
  141.    * Actual writing occurs here. 这个方法是写操作真正的执行过程!  
  142.    * */  
  143.   protected void subAppend(LoggingEvent event) {   
  144.         long n = System.currentTimeMillis();   
  145.         if (n >= nextCheck) { //在每次写操作前判断一下是否需要滚动文件   
  146.             now.setTime(n);   
  147.             nextCheck = getNextDayCheckPoint(now);   
  148.             try {   
  149.                 rollOver();   
  150.             } catch (IOException ioe) {   
  151.                 LogLog.error("rollOver() failed.", ioe);   
  152.             }   
  153.         }   
  154.         super.subAppend(event);   
  155.     }   
  156.   
  157.   /**  
  158.    * 获取下一天的时间变更点  
  159.    * @param now  
  160.    * @return  
  161.    */  
  162.   long getNextDayCheckPoint(Date now) {   
  163.       Calendar calendar = Calendar.getInstance();   
  164.       calendar.setTime(now);   
  165.       calendar.set(Calendar.HOUR_OF_DAY, 0);   
  166.       calendar.set(Calendar.MINUTE, 0);   
  167.       calendar.set(Calendar.SECOND, 0);   
  168.       calendar.set(Calendar.MILLISECOND, 0);//注意MILLISECOND,毫秒也要置0.。。否则错了也找不出来的   
  169.       calendar.add(Calendar.DATE, 1);   
  170.       return calendar.getTimeInMillis();   
  171.   }   
  172.      
  173.   /**  
  174.    * 根据maxBackupIndex配置的备份文件个数,获取要保留log文件的日期范围集合  
  175.    * @return list<'fileName+yyyy-MM-dd'>  
  176.    */  
  177.   List<String> getMaxBackupIndexDates() {   
  178.       List<String> result = new ArrayList<String>();   
  179.       if(maxBackupIndex>0) {   
  180.           for (int i = 1; i <= maxBackupIndex; i++) {   
  181.             Calendar calendar = Calendar.getInstance();   
  182.             calendar.setTime(now);   
  183.             calendar.set(Calendar.HOUR_OF_DAY, 0);   
  184.             calendar.set(Calendar.MINUTE, 0);   
  185.             calendar.set(Calendar.SECOND, 0);   
  186.             calendar.set(Calendar.MILLISECOND, 0);//注意MILLISECOND,毫秒也要置0.。。否则错了也找不出来的   
  187.             calendar.add(Calendar.DATE, -i);   
  188.             result.add(sdf.format(calendar.getTime()));   
  189.         }   
  190.       }   
  191.       return result;   
  192.   }   
  193.      
  194.     public int getMaxBackupIndex() {   
  195.         return maxBackupIndex;   
  196.     }   
  197.   
  198.     public void setMaxBackupIndex(int maxBackupIndex) {   
  199.         this.maxBackupIndex = maxBackupIndex;   
  200.     }   
  201.        
  202.     public String getDatePattern() {   
  203.         return datePattern;   
  204.     }   
  205.   
  206. //  public static void main(String[] args) {   
  207. //      DayRollingFileAppender da = new DayRollingFileAppender();   
  208. //      da.setMaxBackupIndex(2);   
  209. //      da.sdf = new SimpleDateFormat(da.getDatePattern());   
  210. //      System.out.println(da.getMaxBackupIndexDates());   
  211. //         
  212. //      File f = new File("e:/log/b2c/perf.log");   
  213. //      System.out.println("f.name=" + f.getName());   
  214. //      File p = new File(f.getParent());   
  215. //      for(File ff : p.listFiles()) {   
  216. //          System.out.println(ff);   
  217. //      }   
  218. //  }   
  219. }  
posted @ 2010-01-26 12:03  cuker919  阅读(494)  评论(0编辑  收藏  举报