log4cplus源碼分析

1【引題】

雖然從本科起就學的C++,然後在工作的2年時間中也不斷的在用C++寫代碼,雖然基本的語法和一些常用的庫函數已經滾瓜爛熟,可是總覺得自己寫的代碼還不是很專業,特別是看到那些老外們寫得代碼,從設計,到編碼風格,再到各種編程技法的使用有很多都是值得學習和領悟的。於是,就決定靜下心來找點開源的代碼來研習。

因爲LOG4CPLUS代碼量不是很大,而且功能也不是非常複雜,不就是記個日誌麼,呵呵(不過,後來我也很納悶,記個LOG也能搞這麼複雜,唉)。


2【整體設計】



LOG4CPLUS的整體結構還是比較清晰的(如上圖),主要是由Logger模塊、Appender模塊、Filter模塊和Layout模塊來實現具體的日誌功能。其它模塊,如:AppenderAttachable模塊和Hierarchy模塊只是分別負責對Appender對象和Logger對象進行管理。

Logger模塊主要是將用戶輸入的日誌信息以標準的方式存儲在一個叫InternalLoggingEvent的日誌對象中。同時,Logger模塊會存在一個Appender對象的列表appenderList,Logger會調用列表中每一個Appender對象來完成剩餘的日誌記錄處理。這個時候,InternalLoggingEvent會做爲參數傳入Appender對象。LOG4CPLUS的作者在設計Logger模塊時,採用PIMPL的設計方式,先通過Logger接口類定義接口,然後功能具體通過LoggerImpl來實現。接着,爲了讓Logger對象也具有管理Appender的功能,Logger和LoggerImpl分別繼承了AppenderAttachable和AppenderAttachableImpl類。

不過,Logger、LoggerImpl、AppenderAttachable和AppenderAttachableImpl這4者之間的關係有點凌亂,採用橋模式應該更好一點吧,如下圖:


Appender模塊負責根據當時的上下文信息將合適的日誌內容記錄到具體的外圍設備中去。不過在將日誌記錄到外圍設備之前,Appender模塊必須先完成日誌的過濾和日誌內容的格式化,這兩個功能都是通過多態的機制實現的,Appender類裏面只是一個Filter指針和一個Layout指針,具體的功能全由派生類去實現了。另外,我們知道一個Appender類可能要進行不同方式的過濾,所以,爲了滿足這個要求,Filter對象中添加了一個next指針,指向下一個Filter節點,這樣Appender就可以通過這個next指針來遍歷多有的Filter對象了(這個地方可以考慮用vector<Filter>的方式來代替Filter中的next指針,雖然本質上是一回事,但這樣做更加清晰易懂,也有利於維護)。Appender模塊中定義一個Appender接口,最後這個日誌內容是顯示到屏幕上、記錄到文件裏面,還是MYSQL數據庫中,則是由不同的Appender派生類去實現的。

Appender在記錄日誌之前,會通過Filter接口調用具體過濾器來決定該日誌信息是否記錄。Layout模塊則是幫助某個具體Appender類在記錄日誌信息之前將其格式化。

Hierarchy類相當於一個容器,它將系統中所有的Logger對象以一種樹形結構進行存放。每個Logger對象可以繼承它的父Logger的一些屬性,如:Logger優先級、Appender列表等。Hierarchy有一個很特別的地方:就是它的子節點可以先於它的父節點創建,這個特性是通過創建虛擬父節點(provisionNodes)來實現的。至於其具體的算法,有興趣的讀者可以參考它的實現代碼。

3【對象工廠】

LOG4CPLUS中除了Logger對象是通過簡單的工廠方法實現的以外,其它如:Appender、Filter和Layout對象都是通過較爲複雜工廠模板類實現的。爲了方便描述,這兒以Appender工廠爲例,進行分析。


我們知道Appender有很多種,如:ConsoleAppender、FileAppender、SocketAppender等等。這每個具體Appender對象都是通過某個特定的工廠生成的,所以每個具體的Appender都有一個對應的工廠。這每個Appender工廠都遵循相同的接口:BaseFactory和AppenderFactory。然後通過LocalFactoryBase和FactoryTempl兩個模板類實現這兩個接口。最後將這每一個具體的Appender工廠對象都存放到AppenderFactoryRegistry對象中。AppenderFactoryRegistry通過MAP容器保持所有的AppenderFactory對象與其生產的具體Appender類名稱的映射關係。這樣PropertyConfiguration對象在讀入配置文件之後,通過具體的Appender名稱就能夠在AppenderFactoryRegistry中找到對應的Appender工廠,它的createObject方法就能獲得用戶需要的Appender對象了。而這一切,除了PropertyConfigurator外,其它使用Appender對象的模塊根本不知道其具體的Appender類型,這正是工廠方法的價值所在:將具體對象的生成與使用解耦。

4【PIMPL方法】

PIMPL原則即:pointer to implementation。通過在接口類中定義一個指向實現類的指針來,完成接口聲明與實現的分離。PIMPL利用了一個c++的特性,就是定義一個類指針,編譯器不需知道這個類型的定義,只要給出這個類的聲明就可以了。要這樣做的好處就是當實現類發生修改時,包含接口類.h文件的客戶代碼不需要重新編譯。當然,你也可以通過將接口類定義爲抽象類,實現類繼承該類的方式來達到同樣的目的。這兩個方法都差不多,只是當你需要針對一個接口類定義多個實現類時,應該選擇抽象類的方法;而當只需定義一個實現類的時候採用PIMPL方式更加簡潔。網上有很多講解PIMPL的文章,想進一步瞭解PIMPL方法的讀者可以去GOOGLE一下。

5【智能指針】

LOG4CPLUS中的智能指針功能類似於BOOST庫中的shared_ptr模板類,但是設計的沒有shared_ptr那麼精緻。它是通過ShareObject類和ShareObjectPtr模板來實現的,如下圖:


任何想要使用智能指針的類必須要繼承SharedObject類,而該類負責記錄該對象被引用的次數,當該對象的引用次數爲0時,就會delete該對象。ShareObjectPtr用於封裝某個SharedObject對象所有的指針操作,這樣在使用ShareObjectPtr對象時我們就可以像平時那樣操作指針了。有一個地方需要注意一下,就是在給對象引用次數計數時,是通過__sync_add_and_fetch和__sync_sub_and_fetch這兩個函數來操作count變量的,在多線程環境下這兩個函數比平時我們先加鎖後計算的方法性能上要高出很多。

6【結尾】

LOG4CPLUS的代碼雖然不多,但是代碼思路清晰,結構良好,除了一些宏定義的理解需要花點功夫以外,大部分代碼還是很容易就能看懂的。一些比較通用模塊如:配置文件解析、智能指針等等,它們的設計思路我們在平時的開發中也可以去借鑑一下。

另外,LOG4CPLUS中還有一些有趣的東西還需要進一步去挖掘,如它的多線程處理機制、AsyncAppender模塊的異步日誌記錄功能,這個便是我接下來要探索的東西了。







發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章