学习如何用自己的 appender 来扩展 log4j 框架
2003 年 9 月 29 日
日志記錄不僅是開發和測試周期中的一個重要元素――提供關鍵調試信息,而且對于系統已部署到生產環境之后調試錯誤也是很有用的――提供修復錯誤所需的準確上下文信息。在本文中,Orange Soft?公司(這是一家專業從事面向對象技術、服務器端Java?平臺和 Web 可訪問性的西班牙公司)的共同創辦人 Ruth Zamorano 和 Rafael Luque 闡述了如何利用 log4j 的擴展能力,使得分布式?Java?應用程序能夠通過即時消息傳送(instant messaging,IM)來監視。不管您編寫多少設計良好的測試用例,即使是最小的應用程序也會在部署到生產環境之后隱藏著一個或多個錯誤。雖然測試驅動的開發和 QA 手段可以提高代碼質量并增強對應用程序的信心,但是當某個系統失敗時,開發人員和系統管理員需要了解系統的相關執行上下文信息。有了適當的信息,他們就能確定問題的本質并快速解決問題,從而節省時間和金錢。
監視分布式應用程序要求能夠對遠程資源進行日志記錄――通常是一臺中央日志服務器或者系統管理員的計算機。log4j 環境提供一組適用于遠程日志記錄的 appender,比如?SocketAppender?、?JMSAppender?和SMTPAppender?。在本文中,我們將向您展示一種新的遠程類(remote-class)appender:?IMAppender?。
讓我們首先簡要回顧一下 log4j ,然后再深入研究?appender。自然地,理解?appender 的最好方式就是試著編寫一個 appender,因此我們將在最后一節實現一個例子 IM(即時消息傳送)appender,以說明?AppenderSkeleton?類的工作原理。
讀者應該熟悉 log4j 框架。關于 log4j 的更多信息,請參見本文后面的?參考資料?。
log4j 概述
log4j 框架是用 Java 語言編寫的事實上的標準日志記錄框架。作為 Jakarta?項目的一部分,它在?Apache?軟件許可證(Apache Software License)下分發,Apache 軟件許可證是由開放源代碼促進會(Open Source Initiative ,OSI)認證的一種流行的開放源代碼許可證。log4j 環境是完全可配置的,或者通過編程方式完成,或者通過屬性中的配置文件或者?XML?格式的配置文件完成。此外,它還允許開發人員無需修改源代碼就可以選擇性地篩選出日志記錄請求。
log4j 環境包括三個主要組件:
- logger(日志記錄器):控制要啟用或禁用哪些日志記錄語句。可以對日志記錄器指定如下級別:?ALL?、DEBUG?、?INFO?、?WARN?、?ERROR?,?FATA或?OFF?。?
- layout(布局):根據用戶的愿望格式化日志記錄請求。?
- appender:向目的地發送格式化的輸出。
理解 appender
log4j 框架允許向任何日志記錄器附加多個 appender。可以在任何時候對某個日子記錄器添加(或刪除)appender。附隨 log4j 分發的 appender 有多個,包括:
- ConsoleAppender
- FileAppender
- SMTPAppender
- JDBCAppender
- JMSAppender
- NTEventLogAppender
- SyslogAppender
也可以創建自己的自定義?appender。
log4j 最主要的特性之一就是它的靈活性。遺憾的是,沒有多少現存文檔說明了如何編寫自己的 appender。學習編寫 appender 的方式之一就是分析可用的源代碼,然后嘗試推斷 appender 是如何工作的――本文將幫助 您完成這個任務。
揭開面紗
所有的 appender 都必須擴展?org.apache.log4j.AppenderSkeleton?類,這是一個抽象類,它實現了?org.apache.log4j.Appender?和?org.apache.log4j.spi.OptionHandler?接口。?AppenderSkeleton?類的 UML 類圖看起來如圖1所示:
圖 1. AppenderSkeleton 的 UML 類圖?
下面讓我們研究一下?AppenderSkeleton?類所實現的?Appender?接口的方法。如清單1所示,?Appender?接口中的幾乎所有方法都是 setter 方法和 getter 方法:
清單1. Appender 接口
| package org.apache.log4j; public interface Appender { void addFilter(Filter newFilter); void clearFilters() ; void close(); void doAppend(LoggingEvent event); ErrorHandler getErrorHandler(); Filter getFilter(); Layout getLayout(); String getName(); boolean requiresLayout(); void setErrorHandler(ErrorHandler errorHandler); void setLayout(Layout layout); void setName(String name); } |
?
這些方法處理 appender 的如下屬性:
- name:Appender 是命名的實體,因此有一個針對其名稱的 setter/getter。?
- layout:?Appender 可以具有關聯的 Layout,因此還有另一個針對 layout 的setter/getter 方法。注意我們說的是“可以”而不是“必須”。這是因為有些 appender 不需要 layout。lauout 管理格式輸出――也就是說,它返回LoggingEvent?的?String?表示形式。另一方面,?JMSAppender?發送的事件是 串行化的,因此您不需要對它附加 layout。如果自定義的 appender 不需要 layout,那么?requiresLayout()?方法必須返回?false?,以避免?log4j 抱怨說丟失了 layout 信息。?
- errorHandler?: 另一個 setter/getter 方法是為?ErrorHandler?而存在的。appender 可能把它們的錯誤處理委托給一個?ErrorHandler?對象――即?org.apache.log4j.spi?包中的一個接口。實現類有兩個:?OnlyOnceErrorHandler?和?FallbackErrorHandler?。?OnlyOnceErrorHandle?實現 log4j 的默認錯誤處理策略,它發送出第一個錯誤的消息并忽略其余的所有錯誤。錯誤消息將輸出到?System.err?。?FallbackErrorHandler?實現?ErrorHandler?接口,以便能夠指定一個輔助的 appender。如果主 appender 失敗,輔助 appender 將接管工作。錯誤消息將輸出到?System.err?,然后登錄到新的輔助 appender。
還有管理過濾器的其他方法(比如?ddFilter()?、?clearFilters()?和?getFilter()?方法 )。盡管 log4j 具有過濾日志請求的多種內置方法(比如知識庫范圍級、日志記錄器級和 appender 閾值級),但它使用自定義過濾器方法的能力也是非常強大的。
一個 appender 可以包含多個過濾器。自定義過濾器必須擴展?org.apache.log4j.spi.Filter?抽象類。這個抽象類要求把過濾器組織為線性鏈。 對每個過濾器的?decide(LoggingEvent)?方法的調用要按照過濾器被添加到鏈中的順序來進行。自定義過濾器基于三元邏輯。?decide()?方法必須返回?DENY?、?NEUTRAL?或者?ACCEPT?這三個整型常量值之一。
除了 setter/getter 方法以及和過濾器相關的方法外,還有另外兩個方法:?close()?和?doAppend()?。?close()?方法釋放 appender 中分配的任何資源,比如文件句柄、網絡連接,等等。在編寫自定義 appender 代碼時,務必要實現這個方法,以便當您的 appender 關閉時,它的 closed 字段將被設置為?true?。
如清單2所示的?doAppend()?方法遵循“四人組模板方法(Gang of Four Template Method )”設計模式(參見?參考資料)。這個方法提供了一個算法框架,它把某些步驟推遲到子類中來實現。
清單2:doAppend() 方法的實際源代碼
| public synchronized void doAppend (LoggingEvent event) { if (closed) { // step 1 LogLog.error("Attempted to append to closed appender [" + name + "]."); return; } if ( !isAsSevereAsThreshold (event.level) ) { // step 2 return; } Filter f = this.headFilter; // step 3 FILTER_LOOP: while ( f != null) { switch ( f .decide(event) ) { case Filter.DENY: return; case Filter.ACCEPT: break FILTER_LOOP; case Filter.NEUTRAL: f = f.next; } } this.append(event); // step 4 } |
?
如清單2所示,該算法:
我們已經介紹了?AppenderSkeleton?從?Appender?繼承來的方法和屬性。下面讓我們看看“為什么”?AppenderSkeleton?要實現?OptionHandler?接口。?OptionHandler?僅包含一個方法:?activateOptions()?。這個方法在對屬性調用 setter 方法之后由一個配置器類調用。有些屬性彼此依賴,因此它們在全部加載完成之前是無法激活的,比如在?activateOptions()?方法中就是這樣。這個方法是開發人員在 appender 變為激活和就緒之前用來執行任何必要任務的機制。
除了上面提到的所有方法,讓我們再回頭觀察一下?圖1。注意?AppenderSkeleton?提供了一個新的抽象方法(?append()?方法)和一個新的 JavaBean 屬性(?threshold?)。?threshold?屬性由 appender 用來過濾日志記錄請求,只有超過閾值的請求才會得到處理。我們在談到?doAppend()?方法之前就提到了?append()?方法。它是自定義 appender 必須實現的一個抽象方法,因為框架在?doAppend()?方法內調用?append()?方法。?append()?方法是框架的鉤子(hook)之一。
現在我們已經看到了?AppenderSkeleton?類中的所有可用方法,下面讓我們看看幕后發生的事情。圖2演示了 log4j 中的一個 appender 對象的 生命周期。
圖 2. appender 的生命周期圖?
讓我們逐步地研究一下這個圖表:
- appender 實例不存在。或許框架還沒有配置好。?
- 框架實例化了一個新的 appender。這發生在配置器類分析配置腳本中的一個 appender?聲明的時候。配置器類調用?Class.newInstance(YourCustomAppender.class)?,這等價于動態調用?new YourCustomAppender()?。框架這樣做是為了避免被硬編碼為任何特定的 appender 名稱;框架是通用的,適用于任何 appender。?
- 框架判斷 appender 是否需要 layout。如果該 appender 不需要 layout,配置器就不會嘗試從配置腳本中加載 layout 信息。?
- Log4j 配置器調用 setter 方法。在所有屬性都已設置好之后,框架就會調用這個方法。程序員可以在這里激活必須同時激活的屬性。?
- 配置器調用 activateOptions() 方法。在所有屬性都已設置好之后,框架就會調用這個方法。程序員可以在這里激活必須同時激活的屬性。?
- Appender 準備就緒。?此刻,框架可以調用 append() 方法來處理日志記錄請求。這個方法由 AppenderSkeleton.doAppend() 方法調用。?
- 最后,關閉appender。?當框架即將要刪除您的自定義 appender 實例時,它會調用您的 appender 的?close()?方法。?close()?是一個清理方法,意味著 您需要釋放已分配的所有資源。它是一個必需的方法,并且不接受任何參數。它必須把?closed?字段設置為?true?,并在有人嘗試使用關閉的 appender 時向框架發出警報。
現在我們已經回顧了與建立自己的 appender 相關的概念,下面讓我們考慮一個包括真實例子appender 的完整案例研究。
?
| 編寫自定義 appender 的訣竅 ? |
?
編寫基于 IM 的 appender
本文給出的代碼說明了如何擴展 log4j 框架以集成 IM 特性。它被設計來使得 log4j 相容的應用程序能夠把輸出記錄到 IM 網絡上。IM appender 實際上充當一個自定義的 客戶機。然而,它不是把?System.out?、文件或者 TCP 套接字當作底層輸出設備,而是把 IM 網絡當作底層輸出設備。
為了提供 IM 支持,我們不需要在開發特定解決方案時完全重新開始。相反,我們將利用一個我們認為是該類別中最好的工具:Jabber。Jabber 是一種用于即時消息傳送和展示的基于 XML 的開放協議,它由 Jabber?社區開發,非 營利性的 Jabber 軟件基金會(Jabber Software Foundation)對它提供技術支持。
我們之所以選擇 Jabber 而沒有選擇其他 IM 系統,是因為 Jabber 提供了廣泛的好處,包括它的:
- 開放性質:?不像其他的專有系統,Jabber 的規范和源代碼是可以免費獲得的,從而允許任何人無成本地創建 Jabber 實現。?
- 簡單性:Jabber 使用基于 XML 的簡單協議作為它的標準數據格式,并且遵循大多數人都理解的客戶機/服務器 架構。?
- 與其他 IM 系統的互操作性:Jabber 傳輸模塊(transport module)使得 Jabber 用戶訪問諸如 AIM、Yahoo!Messager 和 ICQ 等其他即時消息傳送系統成為可能。?
- 資源敏感:Jabber 對多客戶機訪問提供明確的支持。同一個用戶可以通過不同的客戶機(或者說?資源)同時地連接到Jabber 服務器,消息將被恰當地路由到可用的最佳資源。
為什么要把日志記錄到 IM 網絡?
日志記錄是開發人員必須養成的良好編碼習慣,就像編寫單元測試、處理異常或者編寫 Javadoc 注釋一樣。插入到代碼中明確位置的日志記錄語句起著審核工具的功能,提供了關于應用程序內部狀態的有用信息。與主流意見相反,我們認為在許多情況下,將日志語句保留在生產代碼中是方便的。如果您擔心計算成本,就必須考慮從應用程序中刪除日志記錄功能所帶來的少量性能提升是否值得。此外,log4j 的靈活性允許您聲明式地控制日志記錄行為。您可以建立嚴格的日志記錄策略來降低日志的累贅性并改進性能。
圖3顯示了?IMAppender?的一個使用場景:一個配置為使用?IMAppender?的 log4j 應用程序記錄 它的被包裝為 IM 消息的調試數據。即時消息通過 Jabber 公司網絡被路由到系統管理員的Jabber 地址(注意,公開可用的 Jabber 服務器對生產應用可能不足夠可靠)。因而,無論何時系統管理員需要檢查應用程序的狀態,他們只需加載最喜歡的 Jabber 客戶機,然后連接到Jabber 服務器。如圖3所示,管理員可以通過不同的設備來訪問。他可以使用辦公室的 PC 來登錄服務器,或者當他離開辦公桌時,可以使用運行在手持設備上的 Jabber 客戶機來檢查消息。
圖 3. IMAppender 使用場景?
但是為什么需要 IM appender 呢?因為向 IM 服務器發送消息將允許您通過自由選擇的工具(比如Jabber客戶機)來更容易地監視應用程序行為。
IMAppender?提供了多個優點:
- 獲得實時通知――我們稱之為“即時日志記錄”。
- 支持一對一(聊天)和一對多(小組聊天)模式。
- Jabber 不只是用于臺式計算機。用于諸如 PDA 和移動電話等無線設備的客戶機也正在開發之中。
- 要進入或退出應用程序正在向其轉發日志數據的聊天室很容易。而要訂閱和取銷訂閱由?SMTPAppender?發送的電子郵件則很困難。?
- 通過安全套接字層(SSL)上的隧道來保證安全很容易。當然,您可以加密電子郵件,但是 SSL上 的 Jabber 既方便又快捷。
進階
IMAppender?模仿隨 log4j 一起分發的?SMTPAppender?的日志記錄策略。?IMAppender?把日志記錄事件存儲在一個內部循環緩沖區(cyclic buffer)中,并且僅當所接收到的日志記錄請求觸發了某個用戶指定的條件時,才把這些事件作為即時消息來發送。或者,用戶也可以提供一個觸發事件鑒別器類(triggering event evaluator class)。然而在默認情況下,消息傳送是由指定為?ERROR?或者更高級別的事件所觸發的。
每個消息中傳送的日志記錄事件的數量是由緩沖區的大小決定的。循環緩沖區僅保留最后的?bufferSize?個日志記錄事件,當它裝滿時就會溢出并丟棄較舊的事件。
為了連接到 Jabber 服務器,?IMAppender?需要依賴 Jive Software 公司的 Smack?API。Smack 是一個開放源代碼的高級庫,它處理與 Jabber 服務器通信的協議細節。這樣, 您無需任何特別的 Jabber 或者 XML 專業經驗就能理解代碼。
IMAppender?的屬性總結在表 1中:
表 1. IMAppender 屬性
| 屬性 | 說明 | 類型 | 是否必需 |
| host | 服務器的主機名稱 | String | 是 |
| port | Jabber服務器的端口號 | int | 否,默認為 5222 |
| username | 應用程序的Jabber帳戶用戶名 | String | 是 |
| password | 應用程序的Jabber帳戶密碼 | String | 是 |
| recipient | 接收方的Jabber地址。Jabber地址也稱為Jabber ID,它在一個@字符后面指定用戶的Jabber 域,就像電子郵件地址一樣 這個屬性可以保存任何聊天地址或者聊天室地址。例如,您可以指定這樣的聊天地址:sysadmin@company.com;或者您可能希望向?sysadmin@company.com?小組聊天服務器上名為"java-apps"的某個聊天小組發送日志記錄消息(例如,?java-apps@conference.company.com?) | String | 是 |
| chatroom | 接受一個布爾值。如果為?true?,?recipient?值將被接受為小組聊天地址。如果要設置這個選項,還應該設置?nickname?選項。默認情況下,recipient 值被解釋為一個聊天地址 | boolean | 否,默認為?false |
| nickname | 僅當設置了?chatroom?屬性時才會考慮這個屬性。否則,它將被忽略? 用戶可以選擇 appender 使用的任意小組聊天昵稱來加入小組聊天。昵稱不一定要和 Jabber用戶名有關 | String | 否 |
| SSL | 用于保護與 Jabber 服務器的連接 | boolean | 否,默認為?false |
| bufferSize | 可以保留在循環緩沖區中的日志記錄事件的最大數量 | int | 否,默認為?16 |
| evaluatorClass | 這個屬性的值被當作一個類的完全限定名稱的字符串表示形式,該類實現了?org.apache.log4j.spi. TriggeringEventEvaluator?接口(換句話說,也就是一個包含自定義觸發邏輯的類,它覆蓋了默認的觸發邏輯)。如果沒有指定這個選項,?IMAppender?將使用?DefaultEvaluator?類的一個實例,這個類根據被指定為?ERROR?或更高級別的事件觸發響應 | String | 否,默認為?DefaultEvaluator |
?
現在讓我們進一步觀察代碼。IMAppender 類遵循清單3所示的結構:
清單 3. IMAppender 類的總體結構
| package com.orangesoft.logging; import org.apache.log4j.AppenderSkeleton; import org.apache.log4j.spi.LoggingEvent; import org.apache.log4j.spi.TriggeringEventEvaluator; public class IMAppender extends AppenderSkeleton { private String host; private int port = 5222; private String username; private String password; private String recipient; private boolean chatroom = false; private String nickname; private boolean SSL = false; private int bufferSize = 16; protected TriggeringEventEvaluator evaluator; // Set/Get methods for properties public void setHost(String host) { this.host = host; } public String getHost() { return this.host; } ...other set/get methods... // AppenderSkeleton callback methods public boolean requiresLayout() { ... } public void activateOptions() { ... } public void append(LoggingEvent event) { ... } public synchronized void close() { ... } } |
?
請注意關于我們的 appender 的如下幾個方面:
- IMAppender?類擴展?org.apache.log4j.AppenderSkeleton?,這是所有自定義 appender 都必須要做的。?IMAppender?從?AppenderSkeleton?繼承諸如 appender 閾值和自定義過濾之類的公共功能。?
- 我們的 appender 的第一部分很簡單。每個 appender 都有字段和 set/get 方法。屬性和方法簽名遵守 JavaBeans 命名約定。因而,log4j 能夠通過反射來分析 appender,透明地處理 appender 配置。為節省篇幅,上述代碼片斷僅顯示了?setHost()?和?getHost()?方法。?
- 為了完成我們的 appender,我們必須實現 log4j 框架調用來管理我們的 appender 的回調方法:?requiresLayout()?、?activateOptions()?、?append()?和?close()?。
log4j 框架調用?requiresLayout()?方法來判斷自定義 appender 是否需要 layout。注意,有些appender 使用內置格式或者根本就不格式化事件,因此它們不需要 Layout 對象。?IMAppender?需要 layout,因而該方法返回?true?,如 清單4所示:
清單 4. requiresLayout() 方法
| public boolean requiresLayout() { return true; } |
?
注意,?AppenderSkeleton?實現了?org.apache.log4j.spi.OptionHandler?接口(參見?圖 1?)。?AppenderSkeleton?把這個接口的單個方法?activateOptions()?實現為一個空方法。我們的?IMAppender需要這個方法是由于其屬性之間的相互依賴性。例如,與 Jabber 服務器的連接依賴?Host?、?Port?和?SSL?屬性,因此?IMAppender?在這三個屬性被初始化之前無法建立連接。log4j 框架調用?activateOptions()方法來通知 appender 所有屬性都已設置就緒。
IMAppender.activateOptions()?方法激活指定的屬性(比如 Jabber 主機、端口、?bufferSize?,等等),所采取的方式是實例化依賴這些屬性值的更高級對象,如清單5所示:
清單 5. 只有在調用 activateOptions() 方法之后,屬性才會被激活且變得有效
| protected org.apache.log4j.helpers.CyclicBuffer cb; protected org.jivesoftware.smack.XMPPConnection con; protected org.jivesoftware.smack.Chat chat; protected org.jivesoftware.smack.GroupChat groupchat; public void activateOptions() { try { cb = new CyclicBuffer(bufferSize); if (SSL) { con = new SSLXMPPConnection(host, port); } else { con = new XMPPConnection(host, port); } con.login(username, password); if (chatroom) { groupchat = con.createGroupChat(recipient); groupchat.join(nickname != null ? nickname : username); } else { chat = con.createChat(recipient); } } catch (Exception e) { errorHandler.error("Error while activating options for appender named [" + name + "]", e, ErrorCode.GENERIC_FAILURE); } } |
?
activateOptions()?方法完成以下任務:
- 建立?bufferSize?個事件的最大循環緩沖區。我們使用了?org.apache.log4j.helpers.CyclicBuffer?的一個實例,?org.apache.log4j.helpers.CyclicBuffer?是 log4j 附帶的一個輔助類,它提供了緩沖區的邏輯。?
- Smack 的?XMPPConnection?類創建了一個到 XMPP (Jabber) 服務器的連接,這個服務器是通過?host?和?port?屬性來指定的。為了創建一個 SSL 連接,我們要使用?SSLXMPPConnection?子類。?
- 大多數服務器都要求您在執行其他任務之前首先登錄,因此我們使用由?username?和?password?屬性所定義的 Jabber 帳戶來登錄,同時調用?XMPPConnection.login()?方法。?
- 在登錄之后,我們創建一個?Chat?或者?GroupChat?對象,具體視?chatroom?值而定。
在?activateOptions()?方法返回之后,appender 就準備好處理日志記錄請求了。如 清單6所示,由?AppenderSkeleton.doAppend()?調用的?append()?方法將執行大多數實際的日志附加工作。
清單 6. append() 執行實際的輸出操作
| public void append(LoggingEvent event) { // check pre-conditions if (!checkEntryConditions()) { return; } cb.add(event); if (evaluator.isTriggeringEvent(event)) { sendBuffer(); } } protected boolean checkEntryConditions() { if ((this.chat == null) && (this.groupchat == null)) { errorHandler.error("Chat object not configured"); return false; } if (this.layout == null) { errorHandler.error("No layout set for appender named [" + name + "]"); return false; } return true; } |
?
append()?方法中的第一個語句判斷進行附加嘗試是否有意義。?checkEntryConditions()?方法檢查是否有可用于附加到輸出的?Chat?或者?GroupChat?對象,以及是否有用于格式化傳入?event?對象的?Layout?對象。如果這些前提條件得不到滿足,那么?append()?將輸出一條警告消息并返回,從而不會繼續進行輸出操作。下一個語句把事件添加到循環緩沖區實例?cb?。然后,?if?語句把日志記錄事件提交給?evaluator?,這是一個?TriggeringEventEvaluator?實例。如果?evaluator?返回?true?,這意味著該事件與觸發條件匹配,?sendBuffer()?就會被調用。
清單7顯示了?sendBuffer()?方法的代碼?:
清單 7. sendBuffer()方法
| protected void sendBuffer() { try { StringBuffer buf = new StringBuffer(); int len = cb.length(); for (int i = 0; i < len; i++) { LoggingEvent event = cb.get(); buf.append(layout.format(event)); // if layout doesn't handle exceptions, the appender has // to do it if (layout.ignoresThrowable()) { String[] s = event.getThrowableStrRep(); if (s != null) { for (int j = 0; j < s.length; j++) { buf.append(LINE_SEP); buf.append(s[j]); } } } } if (chatroom) { groupchat.sendMessage(buf.toString()); } else { chat.sendMessage(buf.toString()); } } catch(Exception e) { errorHandler.error("Could not send message in IMAppender [" + name + "]", e, ErrorCode.GENERIC_FAILURE); } } |
?
sendBuffer()?方法把緩沖區的內容作為IM消息來發送。此方法逐項遍歷保留在緩沖區中的事件,同時調用 layout 對象的?format()?方法來格式化每個事件。事件的字符串表示形式被附加到?StringBuffer?對象。最后,?sendBuffer()?調用?chat?或者?groupchat?對象的?sendMessage()?方法,把消息發送出去。
請注意以下幾點:
- AppenderSkeleton.doAppend()?方法(它調用?append()?)是經過同步的,因此?sendBuffer()?已經擁有 appender 的監視器。這使得我們不必在?cb?上執行同步操作。?
- 異常提供了極其有用的信息。由于這個原因,如果指定的 layout 忽略了包含在?LoggingEvent?對象中的可拋出對象,自定義 appender 的開發人員必須輸出包括在事件中的異常信息。如果 layout 忽略了可拋出的對象,那么 layout 的?ignoresThrowable()?方法應該返回?true?,并且?sendBuffer()?可以使用?LoggingEvent.getThrowableStrRep()?方法來檢索包含在該事件中的可拋出信息的?String[]?表示形式。
?
| 下載源代碼 |
?
把全部內容組合起來
下面將通過展示?IMAppender?的實際工作效果來結束本文的討論。我們將使用一個相當簡單的名為?com.orangesoft.logging.example.EventCounter?的應用程序,如 清單8所示。這個示例應用程序在命令行接受兩個參數。第一個參數是一個整數,對應于要產生的日志記錄事件的數量。第二個參數必須是以屬性的格式提供的一個 log4j 配置文件名。這個應用程序總是以 ERROR 事件結束,該事件將觸發一次?IM 消息傳送。
清單 8. EventCounter 示例應用程序
| package com.orangesoft.logging.examples; import org.apache.log4j.Logger; import org.apache.log4j.PropertyConfigurator; /** * Generates the number of logging events indicated by the first * argument value. The application ends with an ERROR level event * to trigger the IMAppender action. */ public class EventCounter { private static Logger logger = Logger.getLogger(EventCounter.class); public static void main(String args[]) { int numEvents = Integer.parseInt(args[0]); String log4jConfigFile = args[1]; PropertyConfigurator.configure(log4jConfigFile); for (int i = 1; i <= numEvents; i++) { logger.info("Event #" + i); } logger.error("This error event triggers the delivery", new Exception("This is a mock exception")); } } |
?
我們可以使用類似清單9所示的配置文件:
清單 9. 示例 IMAppender 配置文件
| log4j.rootLogger = ALL,im log4j.appender.im = com.orangesoft.logging.IMAppender log4j.appender.im.host = JABBER_SERVER (e.g. jabber.org) log4j.appender.im.username = APP_JABBER_ACCOUNT_USERNAME log4j.appender.im.password = APP_JABBER_ACCOUNT_PASSWORD log4j.appender.im.recipient = YOUR_JABBER_ADDRESS (e.g. foobar@jabber.org) log4j.appender.im.layout=org.apache.log4j.PatternLayout log4j.appender.im.layout.ConversionPattern = %n%r [%-5p] %M:%L - %m |
?
上面的配置文件腳本把?IMAppender?添加到根日志記錄器(root logger),這樣所接收到的每個日志記錄請求都將被分派到我們的 appender。
在試驗這個示例應用程序之前,請確保將?host?、?username?、?password?和?recipient?屬性設置為 您所在環境中的適當值。下面的命令將運行?EventCounter?應用程序:
?
| java com.orangesoft.logging.examples.EventCounter 100 eventcounter.properties |
?
當運行時,?EventCounter?將根據?eventcounter.properties?所設置的策略記錄 100 個事件。然后一個 IM 消息將從接收方的屏幕上彈出來。圖4、5、6 顯示了不同平臺上的 Jabber 客戶機接收到的結果消息:
圖 4. Windows (Rhymbox)上的 Jabber 客戶機接收到的消息的屏幕快照?
圖 5. Linux (PSI)上的 Jabber 客戶機接收到的消息的屏幕快照?
圖 6. Pocket PC (imov)上的 Jabber 客戶機接收到的消息的屏幕快照?
注意?EventCounter?產生了 100 個事件。然而,由于?IMAppender?緩沖區的默認大小為 16,接收方應該收到僅包含最后 16 個事件的 IM 消息。可以看到,包含在最后一個事件(消息和堆棧跟蹤)中的異常信息已經被正確地傳送了。
這個例子應用程序只展示了?IMAppender?的一個非常小的用途,因此繼續探索它吧,您會找到很多樂趣的!
結束語
log4j 網絡 appender,?SocketAppender?、?JMSAppender?和?SMTPAppender?已經提供了監視 Java 分布式應用程序的機制。然而,多個因素使得 IM 成為用于實時遠程日志記錄的合適技術。在本文中,我們介紹了通過自定義 appender 來擴展 log4j 的基礎知識,并看到了一個基本?IMAppender?的逐步實現過程。許多開發人員和系統管理員都可以從 appender 的使用中獲益。
參考資料
- 您可以參閱本文在 developerWorks 全球站點上的?英文原文.?
- 下載 IMAppender 類的?源代碼?、例子應用程序和必需的庫。 您可以根據需要隨便使用和擴展這些源代碼。
- 從?log4j項目首頁?獲得最新的 log4j 版本 ――包括完整的源代碼、類文件和文檔。欲了解更多信息,請在?log4j官方文檔頁?查看文章和演示材料。?
- 如果您的開發工作需要超出本文檔范圍的內容,可以參考 Ceki Gülcü 編著的一本優秀參考書?log4j完全參考手冊?(QOS.ch, 2003),該書詳細介紹了 log4j 的基本特性和高級特性。?
- developerWorks?Web services 專區提供了?LogKit?作為“每周內容”(component of the week)” (2001年8月)。LogKit 是 Jakarta 的?Avalon項目?的日志記錄組件。?
- Sun 已經完成了一個名為?JSR 47?的社區過程(community process),它定義了 Java 平臺的日志記錄 API。JSR 47 API和 log4j 在體系結構層次上相當類似,不過?log4j 具有許多 JSR 47 所沒有的特性。?
- 在“?Merlin的魔力:異常和日志記錄”(?developerWorks,2001年12月)中,John Zukowski展示了新的 JDK 1.4 日志記錄 API 是如何工作的。?
- Thomas E. Davis 撰寫的?JavaWorld?文章 “?Logging on to Internet Relay Chat (IRC)” 介紹了一種允許應用程序把輸出寫到 IRC 的簡單工具。?
- Gerhard Poul 的文章“?Jabber” (?developerWorks,2002年5月)說明了 Jabber 如何適合于當今的電子商務?基礎設施。?
- Smack?是 Jive Software 公司提供的一個開放源代碼庫,用于與 Jabber 服務器通信以執行即時消息傳送和聊天。?
- Iain Shigeoka 的文章?用 Java 實現即時消息傳送?(Manning, 2002) 深入分析了各種 Jabber 協議。?
- “模板方法(Template Method)設計模式”出自 Erich Gamma、Richard Helm、Ralph Johnson 和 John Vlissides 所著的?設計模式?一書(Addison-Wesley出版, 1995年),這四位作者也被稱為“四人組(Gang of Four,GoF)。?
- 也可以在線閱讀?模板方法設計模式?的相關報道。?
- 在?developerWorks?Java技術專區?可以找到數百篇關于 Java 編程的各個方面的文章。
- 您可以參閱本文在 developerWorks 全球站點上的?英文原文.?
總結
以上是生活随笔為你收集整理的学习如何用自己的 appender 来扩展 log4j 框架的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: dedecms后台验证码错误的解决方法
- 下一篇: django的settings中几个st