轉載自:http://www.kuqin.com/web/20090824/67374.htmlhtml
原做者簡介:沈羽是ebay中國軟件工程公司的軟件工程師,從事JSP應用、JSP引擎、企業級網絡應用方面的開發和研究。java
大型互聯網應用的突出特色是應用自己規模大,結構複雜,用戶訪問量大。設計良好的日誌系統,有助於分析流量趨勢,幫助管理網絡應用;有助於在應用出現問題時,快速查找問題,保證網絡應用的可用時間。設計一套完善的日誌系統,用於記錄應用的內部行爲,是一件頗有價值的工做。web
本文但願從設計和實現的角度,描述日誌系統的構建。本文的描述,基於Java語言,以及使用Java語言實現的類庫。讀者能夠根據本身的實際需求,替換成其餘的實現。數據庫
最簡單的日誌系統其實就是在系統內添加System.out.println(」Some log」)語句。如列表1所示。後端
1. public void someFunction() {服務器
2. System.out.println(」enter method」);網絡
3. try {架構
4. throw new Exception(」Some exception」);框架
5. } catch (Exception e) {dom
6. System.out.println(」Failed: expcetion 」 + e + 」 thrown out」);
7. }
8. System.out.println(」Success」);
9. System.out.println(」exit method」);
10.}
列表1 用System.out.println實現的最簡單的日誌系統
這個簡單的日誌系統記錄了兩類重要信息,分別是方法調用的調用棧信息(line 1和 line 9),和方法調用的成功與否信息(line 6 和line 8)。可是由於過於簡陋,這樣的實現,並不能出如今實際的大型網絡應用中。
對於Java開發人員來講,比較熟悉的用於實現日誌系統的框架包括,Log4J和Java 類庫自帶的包java.util.logging。
1. Logger logger =
2. Logger.getLogger(PackageMatcher.class.getSimpleName());
3. public void someFunctionWithLogger() {
4. logger.log(Level.INFO, 「enter method」);
5. try {
6. throw new Exception(」Some exception」);
7. } catch (Exception e) {
8. logger.log(Level.SEVERE,
9. 「Failed: expcetion 」 + e + 」 thrown out」);
10. }
11. logger.log(Level.INFO, 「exit method」);
12. }
列表2 使用java.util.logging實現的日誌系統
首先從性能上來講,這兩個框架能夠知足大規模訪問的需求,其次還添加了必要的基礎設施,好比Level的概念(line 4, line 8 和line 11),Logger的parent/child關係等。但是這些框架的問題在於它們沒有提供領域模型(domain model)。好比並無區分列表1中表示的兩類信息。這樣的日誌系統仍然比較粗糙,方法調用棧信息和方法調用是否成功的信息被輸出在同一個日誌文件中。須要額外的工做提取相應的信息。
對於一個大型互聯網應用,很重要的一點,是能夠度量網站的訪問量和訪問趨勢,系統出現異常能夠準確及時的記錄問題,並使用這些數據來幫助診斷網站出現的問題。爲了知足這些需求,日誌系統能夠分爲幾個模塊來記錄相關信息。圖一給出了一個日誌系統的模塊圖
圖1 大型互聯網應用的日誌系統
在這個設計中,日誌系統有四個子系統。分別是路徑追蹤系統,本地日誌系統,集中式日誌系統,和引用計數系統。這四個子系統,分別記錄不一樣類別的信息,爲管理和排錯,提供支持。
一個大型網絡應用,大都部署到大量的機器集羣上邊。對於每一臺物理機器,當運行在其上的應用模塊出現問題,首先要在本機器上記錄下當前的錯誤現象。列表3給出了記錄在本機上的日誌片斷
2008-12-7 21:57:24 61.171.218.225 SomeClass someFunctionWithLogger
INFO: enter method
2008-12-7 21:57:24 61.171.218.225 SomeClass someFunctionWithLogger
SEVERE: Failed: expcetion java.lang.Exception: Some exception thrown out.
2008-12-7 21:57:24 61.171.218.225 SomeClass someFunctionWithLogger
INFO: exit method
列表3 記錄在本機上的日誌片斷
由於這是一個實時的記錄系統,全部系統異常均可以被準確及時的記錄下來。通常來講,一個本地日誌系統由四個主要部分組成,圖2給出了本地日誌系統的框架
圖2 本地日誌系統的構成
從圖中能夠看出,4個部分分別是記錄條目(LogRecord)、記錄器(Logger)、處理器(Handler)、過濾器(Filter)。記錄器之間還能夠有父子關係(好比使用Decorator 模式實現)。當子孫節點處理完日誌後,能夠繼續交給父節點處理。列表4給出了一個本地日誌系統的參考實現框架。
1. public class Logger {
// parent logger
2. private Logger parent;
// add filter
3. public void addFilter(Filter filter) {
4. }
// add handler
5. public void addHandler(Handler handler) {
6. }
// log
7. public void log(LogRecord record) {
8. }
9.}
10.public interface Handler {
11. void addFilter(Filter filter);
12.}
13.public interface Filter {
14. void filter(LogRecord record);
15.}
16.public class LogRecord {
// some fields
17.}
列表4 本地日誌系統的參考實現框架
在實現本地日誌系統時,能夠在LogRecord類裏面設計記錄相關的信息。好比針對列表3給出的輸出,能夠在LogRecord增長Date (java.util.Date)、IP(String[])、ClassName(String)、MethodName(String)、Level(int)、Message(String)等域。其中Level用於標記日誌消息的級別,高於某級別的消息纔會被輸出。Handler和Filter也作過濾工做,能夠爲日誌系統提供很強的靈活性。
本地日誌系統能夠在某臺機器上記錄系統的運行狀況。雖然及時、準確,可是因爲在單一機器上的模塊通常來講只是整個系統的一部分,因此缺點是不能全局地監測系統。須要一個日誌總線,將全部的日誌信息集中起來,歸併相同類型的日誌,記錄事務信息等等。圖3給出了一個集中式日誌系統的架構圖。
圖3 集中式日誌系統的架構圖
如圖,集中式的日誌系統能夠設計成一個客戶端/服務器架構。在每一個部署應用的服務器上,均有一個日誌系統客戶端,應用在一些關鍵點調用日誌系統客戶端,經過日誌總線,客戶端將日誌信息提交到日誌系統後端服務器。列表5給出了調用日誌系統客戶端的示例代碼。
1. public void someMethodWithDistributedLogger() {
2. // Log transaction started once log client was created
3. Logger logger = CentralizedLoggerFactory.createLogger();
4. logger.setData(」key」, 「value」);
5. try {
6. throw new Exception(」Some exception」);
7. } catch (Exception e) {
8. logger.setStatus(Status.FAIL,
9. new LogRecord(e.getMessage()));
10. }
11. logger.log(Level.INFO, new LogRecord(」exit method」));
12. logger.setStatus(Status.SUCCESS);
13. // Log transaction ended
14.}
列表5 集中式日誌系統的客戶端調用代碼
當工廠方法建立日誌系統客戶端的時候,日誌事務開始,在結束方法調用的時候,日誌客戶端實例的生命週期結束,日誌事務也結束。這個示例代碼中的一個細節是,一旦狀態被設置(line 8-9),後續的狀態設置將不會再起做用(line 12)。
也能夠根據須要,將集中式的日誌系統設計爲peer-peer結構的。日誌系統的後端數據庫能夠根據須要將數據聚合,進行數據挖掘等工做。
這個系統用於追蹤已經在產品環境運行的網絡系統的運行路徑。它的功能能夠包括記錄模塊和函數的調用路徑、記錄某一用戶的身份信息、分析相應的用戶數據流向等。
路徑追蹤系統的實現能夠參考第三部分的本地日誌系統地實現。列表6給出了一種使用用途——記錄系統的運行路徑:
1.public void doSomething(String value1, String value2) {
2. tracer.traceEnterMethod(new Object[]{value1, value2});
3. // do something
4. tracer.tranceExitMethod(new Object[]{value1, value2});
5.}
列表6記錄系統的運行路徑
與第四部分介紹的集中式日誌系統相似,路徑追蹤系統也能夠增長系統總線和後端服務器支持,加強系統的能力。
大型網站會有不少模塊,每一個模塊又會由更多更小的組件組成。好比用戶界面模塊可能會有搜索框、下拉框、用戶輸入條等等;後臺組件好比用戶權限認證、業務邏輯計算組件。統計這些組件的訪問量信息能夠很好地瞭解系統的運行狀況,爲維護、性能調優提供很好的幫助。
有兩類引用信息是很是有價值的。一類稱爲靜態引用記數,衡量的是一個組件在構建的時候,被其餘的組件引用的次數。另一類稱爲動態調用記數,衡量的是運行過程當中被調用的次數。
靜態引用記數
計算靜態引用記數是一件很有難度的工做,須要在每一個引用到組件的代碼處,遞增引用計數。若是將這項工做交給組件的使用者,會發生諸如遺漏計數、錯誤計數等問題,也增長了使用者的負擔。
解決的方法有多種。好比使用AOP技術在每次引用組件的地方增長計數。本文采用builder模式構建模塊。每一個component在builder裏面被構建的時候,都會調用一次計數方法。
1.public static Component buildComponent(Component[] components) {
2. for(int i = 0; i < components.length; i++) {
3. addComponent(components[i]);
4. com.corp.countStaticReference(
5. components[i].getClass().getName());
6. }
7.}
列表6在Builder裏計數靜態引用
動態調用記數
記錄模塊的動態調用次數,相對來講簡單一些。每個被調用模塊通常來講都會有個入口函數或是一個主控函數。記錄組件的動態調用計數,只要在該函數裏添加相應的計數操做便可。
1.public void handleBody() {
2. com.corp.countDynamicReference(this.getClass().getName());
3. // handleBody
4.}
列表7在模塊的主控函數裏添加計數操做
每次進入這個函數,靜態計數函數countDynamicReference都會被調用,這個函數使用模塊的類名做爲鍵值,而相應的引用計數會被遞增。
爲了顯示靜態和動態調用計數的值,能夠設計一個管理員界面,顯示模塊的計數值。
有了以上各個層次的日誌系統,一個網絡應用的內部情況基本能夠盡收眼底的展示出來。各個不一樣的應用還能夠根據本身不一樣的需求,定製不一樣的日誌系統展現出系統的內部行爲。