摘要:是否覆蓋目標文件名是否緩沖默認緩沖區大小既然緩沖了,那意味著父類中的刷新控制為不進行同步刷新利用父類中的字節流字符流轉換方法實例化父類中的實際在上面指向了文件輸出流繼承,將文件進行日常轉存。
瞎扯
Log4j對Java開發者來說是經常使用到的日志框架,我每次使用都對它的配置文件頭大,網上搜一個別人的例子自己改巴改巴,草草了事。再次使用時,又忘了怎么回事了。這次突然來了興趣,想看看它具體是怎么做的,做個筆記,加深一下印象。
目前的版本是 log4j:log4j:1.2.17
依賴結構 Appender接口Log4j的輸出類都需要實現的接口,為了用戶自定義log輸出策略,抽象出了以下幾點功能
過濾鏈
log輸出
錯誤處理
log格式
OptionHandler接口這個接口只定義了一個方法 void activateOptions();,用于按需初始化一些配置。
AppenderSkeleton抽象類既然是Skeleton,那它必須是最核心的骨架。這個類主要做了以下幾個事
過濾鏈(鏈表)增刪操作
protected Filter headFilter; protected Filter tailFilter; public void addFilter(Filter newFilter) { if(headFilter == null) { headFilter = tailFilter = newFilter; } else { tailFilter.setNext(newFilter); tailFilter = newFilter; } } public void clearFilters() { headFilter = tailFilter = null;
* 定義了日志優先級 `threshold` “門檻”,實現日志的分級輸出
protected Priority threshold;//默認為空 public boolean isAsSevereAsThreshold(Priority priority) { return ((threshold == null) || priority.isGreaterOrEqual(threshold));
}
* log的輸出核心邏輯
public synchronized void doAppend(LoggingEvent event) { if(closed) { LogLog.error("Attempted to append to closed appender named ["+name+"]."); return; } //日志級別攔截 if(!isAsSevereAsThreshold(event.getLevel())) { return; } Filter f = this.headFilter; //結合Filter實現類自身的優先級[停止輸出、立即輸出、依次過濾后輸出]進行過濾, FILTER_LOOP: while(f != null) { switch(f.decide(event)) { case Filter.DENY: return; case Filter.ACCEPT: break FILTER_LOOP; case Filter.NEUTRAL: f = f.getNext(); } } //具體的輸出開放給子類實現 this.append(event);
}
* 下放的權限
//子類只需要關心日志具體的輸出方式 abstract protected void append(LoggingEvent event); //配置方法,子類可以按自己的需求覆寫 public void activateOptions() {} ````WriteAppender
繼承AppenderSkeleton,用戶可選擇將log按字符流或字節流輸出。增加了以下特性
提供了寫入刷新控制
可配置編碼方式
提供了靜態字符流QuitWriter,異常不會拋出,會交給ErrorHandler去處理
//默認實時刷新,效率低但可保證每次輸出均可寫入,設為false時,若程序崩潰,尾部log可能丟失 protected boolean immediateFlush = true; protected String encoding;
* 提供了字節流->字符流的轉換 * log輸出 官方注釋說明了在log輸出之前做的檢查或過濾操作[檢查日志級別->過濾->檢查當前輸出狀況(Appender狀態、輸出流、格式是否均具備)->輸出]
public void append(LoggingEvent event) { // Reminder: the nesting of calls is: // // doAppend() // - check threshold // - filter // - append(); // - checkEntryConditions(); // - subAppend(); if(!checkEntryConditions()) { return; } subAppend(event);
}
protected void subAppend(LoggingEvent event) {
this.qw.write(this.layout.format(event));//將日志格式化后輸出 //依次輸出異常棧 if(layout.ignoresThrowable()) { String[] s = event.getThrowableStrRep(); if (s != null) { int len = s.length; for(int i = 0; i < len; i++) { this.qw.write(s[i]); this.qw.write(Layout.LINE_SEP); } } } //寫入刷新控制 if(shouldFlush(event)) { this.qw.flush(); }
}
* 還有一些Header、Footer的寫入和輸出流的關閉操作 ### FileAppender ### 繼承了WriteAppender,將log輸出到文件。這個比較簡單,主要就是將父類中的輸出流封裝指向到文件。
protected boolean fileAppend = true;//是否覆蓋
protected String fileName = null;//目標文件名
protected boolean bufferedIO = false;//是否緩沖
protected int bufferSize = 8*1024;//默認緩沖區大小
public synchronized void setFile(String fileName, boolean append, boolean bufferedIO, int bufferSize)
throws IOException { LogLog.debug("setFile called: "+fileName+", "+append); // It does not make sense to have immediate flush and bufferedIO. if(bufferedIO) { setImmediateFlush(false);//既然緩沖了,那意味著父類中的刷新控制為false-不進行同步刷新 } reset(); FileOutputStream ostream = null; try { ostream = new FileOutputStream(fileName, append); } catch(FileNotFoundException ex) { String parentName = new File(fileName).getParent(); if (parentName != null) { File parentDir = new File(parentName); if(!parentDir.exists() && parentDir.mkdirs()) { ostream = new FileOutputStream(fileName, append); } else { throw ex; } } else { throw ex; } } Writer fw = createWriter(ostream);//利用父類中的字節流->字符流轉換方法 if(bufferedIO) { fw = new BufferedWriter(fw, bufferSize); } this.setQWForFiles(fw);//實例化父類中的QuitWriter(實際在上面指向了文件輸出流) this.fileName = fileName; this.fileAppend = append; this.bufferedIO = bufferedIO; this.bufferSize = bufferSize; writeHeader(); LogLog.debug("setFile ended");
}
protected void setQWForFiles(Writer writer) {
this.qw = new QuietWriter(writer, errorHandler);
}
### DailyRollingFileAppender ### 繼承FileAppender,將log文件進行日常轉存。我們常用的日志處理類,官方注釋里說已證實有`并發和數據丟失`的問題,可惜我看不出來... 可以自定義轉存日期表達式datePattern(格式需遵循SimpleDateFormat的約定),如
"."yyyy-MM
"."yyyy-ww
"."yyyy-MM-dd
"."yyyy-MM-dd-a
"."yyyy-MM-dd-HH
"."yyyy-MM-dd-HH-mm
注意不要包含任何冒號
它根據用戶提供的日期表達式datePattern,通過內部類RollingCalendar計算得到對應的`日期檢查周期rc.type`,每次log輸出之前,計算`下次檢查時間nextCheck`,對比當前時間,判斷是否進行文件轉存。 主要方法有
//各級檢查周期對應的常量
// The code assumes that the following constants are in a increasing sequence.
static final int TOP_OF_TROUBLE=-1;
static final int TOP_OF_MINUTE = 0;
static final int TOP_OF_HOUR = 1;
static final int HALF_DAY = 2;
static final int TOP_OF_DAY = 3;
static final int TOP_OF_WEEK = 4;
static final int TOP_OF_MONTH = 5;
//初始化配置項
public void activateOptions() {
super.activateOptions(); if(datePattern != null && fileName != null) { now.setTime(System.currentTimeMillis()); sdf = new SimpleDateFormat(datePattern); int type = computeCheckPeriod();//計算datePattern對應的檢查周期 printPeriodicity(type);//打印當前檢查周期 rc.setType(type);//內部RollingCalendar會在log輸出之前根據type計算出下次檢查時間 File file = new File(fileName);//log輸出文件名 scheduledFilename = fileName+sdf.format(new Date(file.lastModified()));//log轉存文件名 } else { LogLog.error("Either File or DatePattern options are not set for appender [" +name+"]."); }
}
//初始化配置時,計算檢查周期
int computeCheckPeriod() {
RollingCalendar rollingCalendar = new RollingCalendar(gmtTimeZone, Locale.getDefault()); // set sate to 1970-01-01 00:00:00 GMT Date epoch = new Date(0); if(datePattern != null) { for(int i = TOP_OF_MINUTE; i <= TOP_OF_MONTH; i++) { SimpleDateFormat simpleDateFormat = new SimpleDateFormat(datePattern); simpleDateFormat.setTimeZone(gmtTimeZone); // do all date formatting in GMT String r0 = simpleDateFormat.format(epoch); rollingCalendar.setType(i); Date next = new Date(rollingCalendar.getNextCheckMillis(epoch)); String r1 = simpleDateFormat.format(next); //r0、r1均以datePattern格式來轉換日期,若type小于datePattern表示的最小范圍,對應日期next的變化不會影響格式化后的r1的值 //每循環一次,type(也就是i) 增1,最終得到的type就是datePattern表示的最小范圍 if(r0 != null && r1 != null && !r0.equals(r1)) { return i; } } } return TOP_OF_TROUBLE; // Deliberately head for trouble...
}
//log輸出
protected void subAppend(LoggingEvent event) {
//在每次調用父類subAppend方法輸出文件之前,進行周期計算 //若當前時間晚于"檢查點時間",調用rollOver()方法進行日志轉存,將當前log文件轉存為指定日期結尾的文件,然后將父類的QuietWriter指向新的log文件 //當然在轉存之前,需要再次計算并刷新"檢查點時間",rc內部type會影響計算結果(在初始化配置時已根據datePattern計算得到) long n = System.currentTimeMillis(); if (n >= nextCheck) { now.setTime(n); nextCheck = rc.getNextCheckMillis(now); try { rollOver(); } catch(IOException ioe) { if (ioe instanceof InterruptedIOException) { Thread.currentThread().interrupt(); } LogLog.error("rollOver() failed.", ioe); } } super.subAppend(event);
}
### RollingFileAppender ### 同樣繼承于FileAppender,由文件大小來轉存log文件 ### ExternallyRolledFileAppender ### 繼承于RollingFileAppender,通過Socket監聽轉存消息來進行轉存操作,后臺運行著一個Socket監聽線程,每次收到轉存消息,會新起一個線程進行日志轉存,并將轉存結果信息返回。 ## 不足 ## 只是介紹了關鍵的一些類,但他們的生命周期,相關的屬性類和輔助類還沒提到,主要是Filter和Layout,下次再更新。 還有上面幾個關鍵方法中的同步關鍵字,我還沒搞懂應該怎么解釋。
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/65203.html
摘要:和在通常系統中,日志功能使用了,對于這兩者的區別不甚了解,通過實踐,并深入源代碼層次的分析,希望能夠講的清晰一些。基本原理在項目中使用到的包主要有和三個。 LOG4J和SLF4J 在通常系統中,日志功能使用了log4j+slf4j,對于這兩者的區別不甚了解,通過實踐,并深入源代碼層次的分析,希望能夠講的清晰一些。 基本原理 在項目中使用到的jar包主要有log4j.jar , slf4...
摘要:建議只使用四個級別,優先級從高到低分別是。比如在這里定義了級別,只有等于及高于這個級別的才進行處理,則應用程序中所有級別的日志信息將不被打印出來。可同時指定多個輸出目的地。 一直感覺到log4j是使用比較混亂,今天抽空整理一下,以后方便使用 一、引用apache.log4j 使用maven進行lo4j的引用 log4j log4j 1.2.17 其他版本也...
摘要:如果日志級別等于配置級別,過濾器會根據和接收或拒絕日志。例如過濾掉所有低于級別的日志。有個子標簽,用于配置求值條件。 沒時間解釋了,快上車,老司機先看代碼 LogBack.xml DEBUG ${MESSAGE_FILE_PATTERN} ...
摘要:項目介紹在之前的整合項目之后,新增日志簡單集成,之前的代碼不予展示與介紹,想了解的請參考整合項目項目代碼獲取項目結構代碼控制層,,主要包含登錄及幾個頁面跳轉會跳到我們自定義的中登錄用戶名或密碼錯誤業務處理層,包含一個包,以接口類型存在 spring-springmvc-mybatis-shiro項目介紹 在之前的mybatis整合項目之后,新增日志、簡單集成shiro,之前的代碼不予展...
閱讀 2662·2021-11-25 09:43
閱讀 2472·2021-09-22 15:29
閱讀 984·2021-09-22 15:17
閱讀 3628·2021-09-03 10:36
閱讀 2223·2019-08-30 13:54
閱讀 1740·2019-08-30 11:23
閱讀 1163·2019-08-29 16:58
閱讀 1290·2019-08-29 16:14