zoukankan      html  css  js  c++  java
  • 扩展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
  • 相关阅读:
    模拟赛总结
    2018.04.06学习总结
    2018.04.06学习总结
    Java实现 LeetCode 672 灯泡开关 Ⅱ(数学思路问题)
    Java实现 LeetCode 671 二叉树中第二小的节点(遍历树)
    Java实现 LeetCode 671 二叉树中第二小的节点(遍历树)
    Java实现 LeetCode 671 二叉树中第二小的节点(遍历树)
    Java实现 LeetCode 670 最大交换(暴力)
    Java实现 LeetCode 670 最大交换(暴力)
    Java实现 LeetCode 670 最大交换(暴力)
  • 原文地址:https://www.cnblogs.com/lexus/p/2545242.html
Copyright © 2011-2022 走看看