擴充套件log4j系列[二]為DailyRollingFileAppender加上maxBackupIndex屬性
在log4j的大多數appender中,都有maxBackupIndex屬性,但是這個DailyRollingFileAppender沒有,也就是說它會每天滾一個檔案,卻沒有辦法控制檔案總個數。這絕對是系統的一個“著火點”,下面就開始動手改造了:
一。研究整個log4j的appender結構:
對框架的一個模組進行擴充套件,並非總是直接繼承某個類就好了,如果不進一步深入研究就有可能掉入某些陷阱。(比如擴充套件log4j的Logger類,直接繼承它並不能得到任何好處,具體解釋清參考官方文件。),還好log4j對level,appender,layerout都擴充套件有很好支援的。
然後就是看log4j的配置檔案了。 配置檔案是可以直接配置擴充套件appender屬性的,這樣就替我們節省了一堆定義、解析、處理的過程
- <SPAN style="COLOR: #ff0000"># 給自己的類取個對應的名</SPAN>
- log4j.appender.appenderName=fully.qualified.name.of.appender.class
- <SPAN style="COLOR: #ff0000">#還可以給自己的類property設定值,也就是說擴充套件的maxBackupIndex屬性可以配置</SPAN>
- log4j.appender.appenderName.option1=value1
- ...
- 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程式碼- /**
- 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;
- }
/**
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程式碼- package cxxxxxxxj;
- import java.io.File;
- import java.io.IOException;
- import java.text.SimpleDateFormat;
- import java.util.ArrayList;
- import java.util.Calendar;
- import java.util.Date;
- import java.util.List;
- import org.apache.log4j.FileAppender;
- import org.apache.log4j.Layout;
- import org.apache.log4j.helpers.LogLog;
- import org.apache.log4j.spi.LoggingEvent;
- /**
- * 擴充套件的一個按天滾動的appender類
- * 暫時不支援datePattern設定,但是可以配置maxBackupIndex
- * @author weisong
- *
- */
- public class DayRollingFileAppender extends FileAppender {
- /**不允許改寫的datepattern */
- private final String datePattern = "'.'yyyy-MM-dd";
- /**最多檔案增長個數*/
- private int maxBackupIndex = 2;
- /**"檔名+上次最後更新時間"*/
- private String scheduledFilename;
- /**
- The next time we estimate a rollover should occur. */
- private long nextCheck = System.currentTimeMillis () - 1;
- Date now = new Date();
- SimpleDateFormat sdf;
- /**
- The default constructor does nothing. */
- public DayRollingFileAppender() {
- }
- /**
- 改造過的構造器
- */
- public DayRollingFileAppender (Layout layout, String filename,
- int maxBackupIndex) throws IOException {
- super(layout, filename, true);
- this.maxBackupIndex = maxBackupIndex;
- activateOptions();
- }
- /**
- * 初始化本Appender物件的時候呼叫一次
- */
- public void activateOptions() {
- super.activateOptions();
- if(fileName != null) { //perf.log
- now.setTime(System.currentTimeMillis());
- sdf = new SimpleDateFormat(datePattern);
- File file = new File(fileName);
- //獲取最後更新時間拼成的檔名
- scheduledFilename = fileName+sdf.format(new Date(file.lastModified()));
- } else {
- LogLog.error("File is not set for appender ["+name+"].");
- }
- if(maxBackupIndex<=0) {
- LogLog.error("maxBackupIndex reset to default value[2],orignal value is:" + maxBackupIndex);
- maxBackupIndex=2;
- }
- }
- /**
- 滾動檔案的函式:
- 1.對檔名帶的時間戳進行比較,確定是否更新
- 2.if需要更新,當前檔案rename到檔名+日期, 重新開始寫檔案
- 3. 針對配置的maxBackupIndex,刪除過期的檔案
- */
- void rollOver() throws IOException {
- String datedFilename = fileName + sdf.format(now);
- // 如果上次寫的日期跟當前日期相同,不需要換檔案
- 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 + "].");
- }
- // 刪除過期檔案
- if (maxBackupIndex > 0) {
- File folder = new File(file.getParent());
- List<String> maxBackupIndexDates = getMaxBackupIndexDates();
- for (File ff : folder.listFiles()) { //遍歷目錄,將日期不在備份範圍內的日誌刪掉
- if (ff.getName().startsWith(file.getName()) && !ff.getName().equals(file.getName())) {
- //獲取檔名帶的日期時間戳
- String markedDate = ff.getName().substring(file.getName().length());
- if (!maxBackupIndexDates.contains(markedDate)) {
- result = ff.delete();
- }
- if (result) {
- LogLog.debug(ff.getName() + " ->deleted ");
- } else {
- LogLog.error("Failed to deleted old DayRollingFileAppender file :" + ff.getName());
- }
- }
- }
- }
- 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; // 更新最後更新日期戳
- }
- /**
- * Actual writing occurs here. 這個方法是寫操作真正的執行過程!
- * */
- protected void subAppend(LoggingEvent event) {
- long n = System.currentTimeMillis();
- if (n >= nextCheck) { //在每次寫操作前判斷一下是否需要滾動檔案
- now.setTime(n);
- nextCheck = getNextDayCheckPoint(now);
- try {
- rollOver();
- } catch (IOException ioe) {
- LogLog.error("rollOver() failed.", ioe);
- }
- }
- super.subAppend(event);
- }
- /**
- * 獲取下一天的時間變更點
- * @param now
- * @return
- */
- long getNextDayCheckPoint(Date now) {
- Calendar calendar = Calendar.getInstance();
- calendar.setTime(now);
- calendar.set(Calendar.HOUR_OF_DAY, 0);
- calendar.set(Calendar.MINUTE, 0);
- calendar.set(Calendar.SECOND, 0);
- calendar.set(Calendar.MILLISECOND, 0);//注意MILLISECOND,毫秒也要置0.。。否則錯了也找不出來的
- calendar.add(Calendar.DATE, 1);
- return calendar.getTimeInMillis();
- }
- /**
- * 根據maxBackupIndex配置的備份檔案個數,獲取要保留log檔案的日期範圍集合
- * @return list<'fileName+yyyy-MM-dd'>
- */
- List<String> getMaxBackupIndexDates() {
- List<String> result = new ArrayList<String>();
- if(maxBackupIndex>0) {
- for (int i = 1; i <= maxBackupIndex; i++) {
- Calendar calendar = Calendar.getInstance();
- calendar.setTime(now);
- calendar.set(Calendar.HOUR_OF_DAY, 0);
- calendar.set(Calendar.MINUTE, 0);
- calendar.set(Calendar.SECOND, 0);
- calendar.set(Calendar.MILLISECOND, 0);//注意MILLISECOND,毫秒也要置0.。。否則錯了也找不出來的
- calendar.add(Calendar.DATE, -i);
- result.add(sdf.format(calendar.getTime()));
- }
- }
- return result;
- }
- public int getMaxBackupIndex() {
- return maxBackupIndex;
- }
- public void setMaxBackupIndex(int maxBackupIndex) {
- this.maxBackupIndex = maxBackupIndex;
- }
- public String getDatePattern() {
- return datePattern;
- }
- // public static void main(String[] args) {
- // DayRollingFileAppender da = new DayRollingFileAppender();
- // da.setMaxBackupIndex(2);
- // da.sdf = new SimpleDateFormat(da.getDatePattern());
- // System.out.println(da.getMaxBackupIndexDates());
- //
- // File f = new File("e:/log/b2c/perf.log");
- // System.out.println("f.name=" + f.getName());
- // File p = new File(f.getParent());
- // for(File ff : p.listFiles()) {
- // System.out.println(ff);
- // }
- // }
- }