zoukankan      html  css  js  c++  java
  • 扩展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. }  
  • 相关阅读:
    C语言学习笔记(二)数据类型、常量和变量简介
    将汉字转换为汉语拼音java实现
    C语言学习笔记(四)运算符与表达式(下)
    数组的几种排序算法的实现
    C语言学习笔记(三)运算符与表达式(上)
    java实现二叉树的常见操作
    java解析Internet网页中的内容
    C语言学习笔记(六)分支程序设计
    C语言学习笔记(一)初识C语言
    C语言学习笔记(五)顺序程序设计
  • 原文地址:https://www.cnblogs.com/cuker919/p/4878635.html
Copyright © 2011-2022 走看看