扩展log4j系列[一]为DailyRollingFileAppender加上maxBackupIndex属性 玮哥也是哥 ITeye技术网站

扩展log4j系列[一]为DailyRollingFileAppender加上maxBackupIndex属性 - 玮哥也是哥 - ITeye技术网站

在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. }  
  • 大小: 91.7 KB
  • 大小: 66.7 KB
原文地址:https://www.cnblogs.com/lexus/p/2545242.html