Log4J Java日志框架特性的內(nèi)部實(shí)現(xiàn)
由于 Log4J 得到廣泛應(yīng)用,從使用者的角度考慮,本文所設(shè)計(jì)的框架,采用了部分 Log4J 的接口和概念,但內(nèi)部實(shí)現(xiàn)則完全不同。使用 Java 實(shí)現(xiàn)日志框架,關(guān)鍵的技術(shù)在于前面提及的Java日志框架特性的內(nèi)部實(shí)現(xiàn),特別是:日志的分類和級(jí)別、日志分發(fā)框架的設(shè)計(jì)、日志記錄器的設(shè)計(jì)以及在設(shè)計(jì)中的高性能和高穩(wěn)定性的考慮。
2.1系統(tǒng)架構(gòu)
Java日志框架可以分為日志記錄模塊和日志輸出模塊兩大部分。日志記錄模塊負(fù)責(zé)創(chuàng)建和管理日志記錄器 (Logger) ,每一個(gè) Logger 對(duì)象負(fù)責(zé)按照不同的級(jí)別 (LoggerLevel) 接收各種記錄了日志信息的日志對(duì)象 (LogItem) , Logger 對(duì)象首先獲取所有需要記錄的日志,并且同步地將日志分派給日志輸出模塊。日志輸出模塊則負(fù)責(zé)日志 輸出器 (Appender) 的創(chuàng)建和管理,以及日志的輸出。系統(tǒng)中允許有多個(gè)不同的日志 輸出器 ,日志 輸出器 負(fù)責(zé)將日志記錄到存儲(chǔ)介質(zhì)當(dāng)中。系統(tǒng)結(jié)構(gòu)如下圖 1 所示:
圖 1 ,Java日志系統(tǒng)結(jié)構(gòu)圖
下圖 2 使用 UML 類圖給出了Java日志系統(tǒng)的架構(gòu):
圖 2 ,日志系統(tǒng)框架架構(gòu)圖
在圖 2 給出的架構(gòu)中,日志記錄器 Logger 是整個(gè)日志系統(tǒng)框架的用戶使用接口,程序員可以通過該接口記錄日志,為了實(shí)現(xiàn)對(duì)日志進(jìn)行分類,系統(tǒng)設(shè)計(jì)允許存在多個(gè) Logger 對(duì)象,每一個(gè) Logger 負(fù)責(zé)一類日志的記錄, Logger 類同時(shí)實(shí)現(xiàn)了對(duì)其對(duì)象本身的管理。 LoggerLevel 類定義了整個(gè)日志系統(tǒng)的級(jí)別,在客戶端創(chuàng)建和發(fā)送日志時(shí),這些級(jí)別會(huì)被使用到。 Logger 對(duì)象在接收到客戶端創(chuàng)建和發(fā)送的日志消息時(shí),同時(shí)將該日志消息包裝成日志系統(tǒng)內(nèi)部所使用的日志對(duì)象 LogItem ,日志對(duì)象除了發(fā)送端所發(fā)送的消息以外,還會(huì)包裝諸如發(fā)送端類名、發(fā)送事件、發(fā)送方法名、發(fā)送行號(hào)等等。這些額外的消息對(duì)于系統(tǒng)的跟蹤和調(diào)試都非常有價(jià)值。包裝好的 LogItem 最終被發(fā)送給 輸出器 ,由這些 輸出 器負(fù)責(zé)將日志信息寫入最終媒介, 輸出器 的類型和個(gè)數(shù)均不固定,所有的 輸出器 通過 AppenderManager 進(jìn)行管理,通常通過配置文件即可方便擴(kuò)展出多個(gè) 輸出器 。
2.2日志記錄部分的設(shè)計(jì)
如前文所述,日志記錄部分負(fù)責(zé)接收日志系統(tǒng)客戶端發(fā)送來的日志消息、日志對(duì)象的管理等工作。下面詳細(xì)描述了日志記錄部分的設(shè)計(jì)要點(diǎn):
1.日志記錄器的管理
系統(tǒng)通過保持多個(gè) Logger 對(duì)象的方式來進(jìn)行日志記錄的分類。每一個(gè) Logger 對(duì)象代表一類日志分類。因此, Logger 對(duì)象的名稱屬性是其唯一標(biāo)識(shí),通過名稱屬性獲取一個(gè) Logger 對(duì)象:
- Logger logger = Logger.getLogger(“LoggerName”);
一般的,使用類名來作為日志記錄器的名稱,這樣做的好處在于能夠盡量減少日志記錄器命名之間的沖突(因?yàn)?Java 類使用包名),同時(shí)能夠?qū)⑷罩居涗浄诸惖帽M可能的精細(xì)。因此,假定有一 UserManager 類需要使用日志服務(wù),則更一般的使用方式為:
- Logger logger = Logger.getLogger(UserManager.class);
2.日志分級(jí)的實(shí)現(xiàn)
按照日志目的不同,將日志的級(jí)別由低到高分成五個(gè)級(jí)別:
DEBUG - 表示輸出的日志為一個(gè)調(diào)試信息
INFO - 表示輸出的日志是一個(gè)系統(tǒng)提示
WARN - 表示輸出的日志是一個(gè)警告信息
ERROR - 表示輸出的日志是一個(gè)系統(tǒng)錯(cuò)誤
FATAL - 表示輸出的日志是一個(gè)導(dǎo)致系統(tǒng)崩潰嚴(yán)重錯(cuò)誤
這些日志級(jí)別定義在 LoggerLevel 接口中,被日志記錄器 Logger 在內(nèi)部使用。而對(duì)于日志系統(tǒng)客戶端則可使用 Logger 類接口對(duì)直接調(diào)用并輸出這些級(jí)別的日志, Logger 的這些接口描述如下:
- public void debug(String msg); // 輸出調(diào)試信息
- public void info(String msg); // 輸出系統(tǒng)提示
- public void warn(String msg); // 輸出警告信息
- public void fatal(String msg); // 輸出系統(tǒng)錯(cuò)誤
- public void error(String msg); // 輸出嚴(yán)重錯(cuò)誤
通過對(duì) Logger 對(duì)象上這些接口的調(diào)用,直接為日志信息賦予了級(jí)別屬性,這樣為后繼的按照不同級(jí)別進(jìn)行輸出的工作奠定了基礎(chǔ)。
3.日志對(duì)象信息的獲取
日志對(duì)象上包含了一條日志所具備的所有信息。通常這些信息包括:輸出日志的時(shí)間、 Java 類、類成員方法、所在行號(hào)、日志體、日志級(jí)別等等。在 JDK1.4 中可以通過在方法中拋出并且捕獲住一個(gè)異常,則在捕捉到的異常對(duì)象中已經(jīng)由 JVM 自動(dòng)填充好了系統(tǒng)調(diào)用的堆棧,在 JDK1.4 中則可以使用 java.lang.StackTraceElement 獲取到每一個(gè)堆棧項(xiàng)的基本信息,通過對(duì)日志客戶端輸出日志方法調(diào)用層數(shù)的推算,則可以比較容易的獲取到 StackTraceElement 對(duì)象,從而獲取到輸出日志時(shí)的 Java 類、類成員方法、所在行號(hào)等信息。在 JDK1.3 或者更早的版本中,相應(yīng)的工作則必須通過將異常的堆棧信息輸出到字符串中,并分析該字符串格式得到。
2.3日志輸出部分的設(shè)計(jì)
日志輸出部分的設(shè)計(jì)具有一定的難度,在本文設(shè)計(jì)的日志系統(tǒng)中,日志的輸出、多線程的支持、日志系統(tǒng)的擴(kuò)展性、日志系統(tǒng)的效率等問題都交由日志輸出部分進(jìn)行管理。
1.日志輸出器的繼承結(jié)構(gòu)
在日志的輸出部分采用了二層結(jié)構(gòu),即定義了一個(gè)抽象的日志輸出器( AbstractLoggerAppender ) , 然后從該抽象類繼承出實(shí)際的日志輸出器。 AbstractLoggerAppender 定義了一系列的對(duì)日志進(jìn)行過濾的方法,而具體輸出到存儲(chǔ)媒介的方法則是一個(gè)抽象方法,由子類實(shí)現(xiàn)。在系統(tǒng)中默認(rèn)實(shí)現(xiàn)了控制臺(tái)輸出器和文件輸出器兩種,其中控制臺(tái)輸出器的實(shí)現(xiàn)頗為簡(jiǎn)單。
2.文件輸出器的內(nèi)部實(shí)現(xiàn)
在日志記錄部分的實(shí)現(xiàn)中,并沒有考慮多線程、高效率等問題,因此文件輸出器必須考慮這些問題的處理。在文件輸出器內(nèi)部使用 java.lang.Vector 定 義了一個(gè)線程安全的高速緩沖,所有通過日志記錄部分分派到文件輸出器的日志被直接放置到該高速緩沖當(dāng)中。同時(shí)在文件輸出器內(nèi)部定義一個(gè)工作線程,負(fù)責(zé)定期 將高速緩沖中的內(nèi)容保存到文件,在保存的過程中同時(shí)可以進(jìn)行日志文件的備份等工作。由于采用了高速緩沖的結(jié)構(gòu),很顯然日志客戶端的調(diào)用已經(jīng)不再是一個(gè)同步 調(diào)用,從而不再會(huì)需要等到文件操作后才返回,提高的系統(tǒng)調(diào)用的速度。該原理 如圖 3 所示:
圖 3 ,文件輸出器內(nèi)部結(jié)構(gòu)
2.4設(shè)計(jì)難點(diǎn)
通過上述設(shè)計(jì),一個(gè)具有良好擴(kuò)展能力的高性能Java日志架就已經(jīng)具有了一定的雛形。在設(shè)計(jì)過程中幾個(gè)難點(diǎn)問題需要進(jìn)一步反思。
一、是否整個(gè)系統(tǒng)應(yīng)當(dāng)采用完全異步的結(jié)構(gòu),通過類似于消息機(jī)制的方式來進(jìn)行由日志客戶端發(fā)送日志給日志系統(tǒng)。這種方式可以作為日志系統(tǒng)框架另一種運(yùn)行方式,在后繼設(shè)計(jì)中加以考慮。
二、在文件輸出器中可以看到,目前雖然可以擴(kuò)展多個(gè)日志輸出器,但是目前提供的抽象類中僅僅提供了對(duì)日志的過濾機(jī)制,而沒有提供的緩存機(jī)制,目前的緩存機(jī)制被放在文件輸出器中實(shí)現(xiàn),因此在未來的進(jìn)一步設(shè)計(jì)中,可以將文件輸出器中的緩存機(jī)制上移到抽象類當(dāng)中。
2.5設(shè)計(jì)模式
在設(shè)計(jì)過程中我們特別注意使用了數(shù)個(gè)經(jīng)典的設(shè)計(jì)模式。如: Logger 對(duì)象的創(chuàng)建使用了工廠方法模式( Factory Method )、由 AbstractLoggerAppender 和 ConsoleAppender 以及 FileAppender 構(gòu)成了策略模式( Strategy ),除此以外,還大量使用了單例模式( Singleton )。在設(shè)計(jì)中適當(dāng)運(yùn)用設(shè)計(jì)模式能夠加快設(shè)計(jì)進(jìn)度、提高設(shè)計(jì)質(zhì)量。
【編輯推薦】