1. 程式人生 > >擴充套件log4j系列[二]為DailyRollingFileAppender加上maxBackupIndex屬性

擴充套件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. log4j.appender.appenderName=fully.qualified.name.of.appender.class    
  3. <SPAN style="COLOR: #ff0000">#還可以給自己的類property設定值,也就是說擴充套件的maxBackupIndex屬性可以配置</SPAN>   
  4. log4j.appender.appenderName.option1=value1      
  5. ...    
  6. log4j.appender.appenderName.optionN=valueN    
# 給自己的類取個對應的名


log4j.appender.appenderName=fully.qualified.name.of.appender.class 
 
#還可以給自己的類property設定值,也就是說擴充套件的maxBackupIndex屬性可以配置


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

    /* Compute filename, but only if datePattern is specified */
    if (datePattern == null) {
      errorHandler.error("Missing DatePattern option in rollOver().");
      return;
    }

    String datedFilename = fileName+sdf.format(now);
    // It is too early to roll over because we are still within the
    // bounds of the current interval. Rollover will occur once the
    // next interval is reached.
    if (scheduledFilename.equals(datedFilename)) {
      return;
    }

    // close current file, and rename it to datedFilename
    this.closeFile();

    File target  = new File(scheduledFilename);
    if (target.exists()) {
      target.delete();
    }

    File file = new File(fileName);
    boolean result = file.renameTo(target);
    if(result) {
      LogLog.debug(fileName +" -> "+ scheduledFilename);
    } else {
      LogLog.error("Failed to rename ["+fileName+"] to ["+scheduledFilename+"].");
    }

    try {
      // This will also close the file. This is OK since multiple
      // close operations are safe.
      this.setFile(fileName, false, this.bufferedIO, this.bufferSize);
    }
    catch(IOException e) {
      errorHandler.error("setFile("+fileName+", false) call failed.");
    }
    scheduledFilename = datedFilename;
  }

      看到這裡就發現問題了,由於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. import java.io.File;   
  3. import java.io.IOException;   
  4. import java.text.SimpleDateFormat;   
  5. import java.util.ArrayList;   
  6. import java.util.Calendar;   
  7. import java.util.Date;   
  8. import java.util.List;   
  9. import org.apache.log4j.FileAppender;   
  10. import org.apache.log4j.Layout;   
  11. import org.apache.log4j.helpers.LogLog;   
  12. import org.apache.log4j.spi.LoggingEvent;   
  13. /**  
  14.  * 擴充套件的一個按天滾動的appender類  
  15.  * 暫時不支援datePattern設定,但是可以配置maxBackupIndex  
  16.  * @author weisong  
  17.  *  
  18.  */  
  19. public class DayRollingFileAppender extends FileAppender {   
  20.   /**不允許改寫的datepattern */  
  21.   private final String datePattern = "'.'yyyy-MM-dd";   
  22.   /**最多檔案增長個數*/  
  23.   private int  maxBackupIndex = 2;   
  24.   /**"檔名+上次最後更新時間"*/  
  25.   private String scheduledFilename;   
  26.   /**  
  27.      The next time we estimate a rollover should occur. */  
  28.   private long nextCheck = System.currentTimeMillis () - 1;   
  29.   Date now = new Date();   
  30.   SimpleDateFormat sdf;   
  31.   /**  
  32.      The default constructor does nothing. */  
  33.   public DayRollingFileAppender() {   
  34.   }   
  35.   /**  
  36.         改造過的構造器  
  37.     */  
  38.   public DayRollingFileAppender (Layout layout, String filename,   
  39.           int  maxBackupIndex) throws IOException {   
  40.     super(layout, filename, true);   
  41.     this.maxBackupIndex = maxBackupIndex;   
  42.     activateOptions();   
  43.   }   
  44.   /**  
  45.    * 初始化本Appender物件的時候呼叫一次  
  46.    */  
  47.   public void activateOptions() {   
  48.     super.activateOptions();   
  49.     if(fileName != null) { //perf.log   
  50.       now.setTime(System.currentTimeMillis());   
  51.       sdf = new SimpleDateFormat(datePattern);   
  52.       File file = new File(fileName);   
  53.       //獲取最後更新時間拼成的檔名   
  54.       scheduledFilename = fileName+sdf.format(new Date(file.lastModified()));   
  55.     } else {   
  56.       LogLog.error("File is not set for appender ["+name+"].");   
  57.     }   
  58.     if(maxBackupIndex<=0) {   
  59.         LogLog.error("maxBackupIndex reset to default value[2],orignal value is:" + maxBackupIndex);   
  60.         maxBackupIndex=2;   
  61.     }   
  62.   }   
  63.   /**  
  64.          滾動檔案的函式:  
  65.          1.對檔名帶的時間戳進行比較,確定是否更新  
  66.          2.if需要更新,當前檔案rename到檔名+日期, 重新開始寫檔案  
  67.          3. 針對配置的maxBackupIndex,刪除過期的檔案  
  68.   */  
  69.     void rollOver() throws IOException {   
  70.         String datedFilename = fileName + sdf.format(now);   
  71.         // 如果上次寫的日期跟當前日期相同,不需要換檔案   
  72.         if (scheduledFilename.equals(datedFilename)) {   
  73.             return;   
  74.         }   
  75.         // close current file, and rename it to datedFilename   
  76.         this.closeFile();   
  77.         File target = new File(scheduledFilename);   
  78.         if (target.exists()) {   
  79.             target.delete();   
  80.         }   
  81.         File file = new File(fileName);   
  82.         boolean result = file.renameTo(target);   
  83.         if (result) {   
  84.             LogLog.debug(fileName + " -> " + scheduledFilename);   
  85.         } else {   
  86.             LogLog.error("Failed to rename [" + fileName + "] to ["  
  87.                     + scheduledFilename + "].");   
  88.         }   
  89.         // 刪除過期檔案   
  90.         if (maxBackupIndex > 0) {   
  91.             File folder = new File(file.getParent());   
  92.             List<String> maxBackupIndexDates = getMaxBackupIndexDates();   
  93.             for (File ff : folder.listFiles()) { //遍歷目錄,將日期不在備份範圍內的日誌刪掉   
  94.                 if (ff.getName().startsWith(file.getName()) && !ff.getName().equals(file.getName())) {   
  95.                     //獲取檔名帶的日期時間戳   
  96.                     String markedDate = ff.getName().substring(file.getName().length());   
  97.                     if (!maxBackupIndexDates.contains(markedDate)) {   
  98.                         result = ff.delete();   
  99.                     }   
  100.                     if (result) {   
  101.                         LogLog.debug(ff.getName() + " ->deleted ");   
  102.                     } else {   
  103.                         LogLog.error("Failed to deleted old DayRollingFileAppender file :" + ff.getName());   
  104.                     }   
  105.                 }   
  106.             }   
  107.         }   
  108.         try {   
  109.             // This will also close the file. This is OK since multiple   
  110.             // close operations are safe.   
  111.             this.setFile(fileName, falsethis.bufferedIO, this.bufferSize);   
  112.         } catch (IOException e) {   
  113.             errorHandler.error("setFile(" + fileName + ", false) call failed.");   
  114.         }   
  115.         scheduledFilename = datedFilename; // 更新最後更新日期戳   
  116.     }   
  117.   /**  
  118.    * Actual writing occurs here. 這個方法是寫操作真正的執行過程!  
  119.    * */  
  120.   protected void subAppend(LoggingEvent event) {   
  121.         long n = System.currentTimeMillis();   
  122.         if (n >= nextCheck) { //在每次寫操作前判斷一下是否需要滾動檔案   
  123.             now.setTime(n);   
  124.             nextCheck = getNextDayCheckPoint(now);   
  125.             try {   
  126.                 rollOver();   
  127.             } catch (IOException ioe) {   
  128.                 LogLog.error("rollOver() failed.", ioe);   
  129.             }   
  130.         }   
  131.         super.subAppend(event);   
  132.     }   
  133.   /**  
  134.    * 獲取下一天的時間變更點  
  135.    * @param now  
  136.    * @return  
  137.    */  
  138.   long getNextDayCheckPoint(Date now) {   
  139.       Calendar calendar = Calendar.getInstance();   
  140.       calendar.setTime(now);   
  141.       calendar.set(Calendar.HOUR_OF_DAY, 0);   
  142.       calendar.set(Calendar.MINUTE, 0);   
  143.       calendar.set(Calendar.SECOND, 0);   
  144.       calendar.set(Calendar.MILLISECOND, 0);//注意MILLISECOND,毫秒也要置0.。。否則錯了也找不出來的   
  145.       calendar.add(Calendar.DATE, 1);   
  146.       return calendar.getTimeInMillis();   
  147.   }   
  148.   /**  
  149.    * 根據maxBackupIndex配置的備份檔案個數,獲取要保留log檔案的日期範圍集合  
  150.    * @return list<'fileName+yyyy-MM-dd'>  
  151.    */  
  152.   List<String> getMaxBackupIndexDates() {   
  153.       List<String> result = new ArrayList<String>();   
  154.       if(maxBackupIndex>0) {   
  155.           for (int i = 1; i <= maxBackupIndex; i++) {   
  156.             Calendar calendar = Calendar.getInstance();   
  157.             calendar.setTime(now);   
  158.             calendar.set(Calendar.HOUR_OF_DAY, 0);   
  159.             calendar.set(Calendar.MINUTE, 0);   
  160.             calendar.set(Calendar.SECOND, 0);   
  161.             calendar.set(Calendar.MILLISECOND, 0);//注意MILLISECOND,毫秒也要置0.。。否則錯了也找不出來的   
  162.             calendar.add(Calendar.DATE, -i);   
  163.             result.add(sdf.format(calendar.getTime()));   
  164.         }   
  165.       }   
  166.       return result;   
  167.   }   
  168.     public int getMaxBackupIndex() {   
  169.         return maxBackupIndex;   
  170.     }   
  171.     public void setMaxBackupIndex(int maxBackupIndex) {   
  172.         this.maxBackupIndex = maxBackupIndex;   
  173.     }   
  174.     public String getDatePattern() {   
  175.         return datePattern;   
  176.     }   
  177. //  public static void main(String[] args) {   
  178. //      DayRollingFileAppender da = new DayRollingFileAppender();   
  179. //      da.setMaxBackupIndex(2);   
  180. //      da.sdf = new SimpleDateFormat(da.getDatePattern());   
  181. //      System.out.println(da.getMaxBackupIndexDates());   
  182. //         
  183. //      File f = new File("e:/log/b2c/perf.log");   
  184. //      System.out.println("f.name=" + f.getName());   
  185. //      File p = new File(f.getParent());   
  186. //      for(File ff : p.listFiles()) {   
  187. //          System.out.println(ff);   
  188. //      }   
  189. //  }   
  190. }