小男孩‘自慰网亚洲一区二区,亚洲一级在线播放毛片,亚洲中文字幕av每天更新,黄aⅴ永久免费无码,91成人午夜在线精品,色网站免费在线观看,亚洲欧洲wwwww在线观看

分享

如何用自己的 appender 來擴展 log4j 框架

 duduwolf 2005-08-03
如何用自己的 appender 來擴展 log4j 框架

級別:高級

Ruth Zamorano (ruth.zamorano@orange-soft.com),軟件架構(gòu)師,Orange Soft
Rafael Luque (rafael.luque@orange-soft.com), CTO,Orange Soft

2003年 9 月

日志記錄不僅是開發(fā)和測試周期中的一個重要元素——提供關(guān)鍵調(diào)試信息,而且對于系統(tǒng)已部署到生產(chǎn)環(huán)境之后調(diào)試錯誤也是很有用的——提供修復(fù)錯誤所需的準確上下文信息。在本文中,Orange Soft 公司(這是一家專業(yè)從事面向?qū)ο蠹夹g(shù)、服務(wù)器端Java 平臺和 Web 可訪問性的西班牙公司)的共同創(chuàng)辦人 Ruth Zamorano 和 Rafael Luque 闡述了如何利用 log4j 的擴展能力,使得分布式 Java 應(yīng)用程序能夠通過即時消息傳送(instant messaging,IM)來監(jiān)視。

不管您編寫多少設(shè)計良好的測試用例,即使是最小的應(yīng)用程序也會在部署到生產(chǎn)環(huán)境之后隱藏著一個或多個錯誤。雖然測試驅(qū)動的開發(fā)和 QA 手段可以提高代碼質(zhì)量 并增強對應(yīng)用程序的信心,但是當某個系統(tǒng)失敗時,開發(fā)人員和系統(tǒng)管理員需要了解系統(tǒng)的相關(guān)執(zhí)行上下文信息。有了適當?shù)男畔?,他們就能確定問題的本質(zhì)并快速解決問題,從而節(jié)省時間和金錢。

監(jiān)視分布式應(yīng)用程序要求能夠?qū)h程資源進行日志記錄——通常是一臺中央日志服務(wù)器或者系統(tǒng)管理員的計算機。log4j 環(huán)境提供一組適用于遠程日志記錄的 appender,比如 SocketAppender、JMSAppenderSMTPAppender。在本文中,我們將向您展示一種新的遠程類(remote-class)appender:IMAppender。

讓我們首先簡要回顧一下 log4j ,然后再深入研究 appender。自然地,理解 appender 的最好方式就是試著編寫一個 appender,因此我們將在最后一節(jié)實現(xiàn)一個例子 IM(即時消息傳送)appender,以說明 AppenderSkeleton 類的工作原理。

讀者應(yīng)該熟悉 log4j 框架。關(guān)于 log4j 的更多信息,請參見本文后面的 參考資料

log4j 概述
log4j 框架是用 Java 語言編寫的事實上的標準日志記錄框架。作為 Jakarta 項目的一部分,它在 Apache 軟件許可證(Apache Software License)下分發(fā),Apache 軟件許可證是由開放源代碼促進會(Open Source Initiative ,OSI)認證的一種流行的開放源代碼許可證。log4j 環(huán)境是完全可配置的,或者通過編程方式完成,或者通過屬性中的配置文件或者 XML 格式的配置文件完成。此外,它還允許開發(fā)人員無需修改源代碼就可以選擇性地篩選出日志記錄請求。

log4j 環(huán)境包括三個主要組件:

  • logger(日志記錄器):控制要啟用或禁用哪些日志記錄語句??梢詫θ罩居涗浧髦付ㄈ缦录墑e:ALL、DEBUG、INFO、WARNERROR, FATA或OFF。

  • layout(布局):根據(jù)用戶的愿望格式化日志記錄請求。

  • appender:向目的地發(fā)送格式化的輸出。

理解 appender
log4j 框架允許向任何日志記錄器附加多個 appender??梢栽谌魏螘r候?qū)δ硞€日子記錄器添加(或刪除)appender。附隨 log4j 分發(fā)的 appender 有多個,包括:

  • ConsoleAppender
  • FileAppender
  • SMTPAppender
  • JDBCAppender
  • JMSAppender
  • NTEventLogAppender
  • SyslogAppender

也可以創(chuàng)建自己的自定義 appender。

log4j 最主要的特性之一就是它的靈活性。遺憾的是,沒有多少現(xiàn)存文檔說明了如何編寫自己的 appender。學(xué)習(xí)編寫 appender 的方式之一就是分析可用的源代碼,然后嘗試推斷 appender 是如何工作的——本文將幫助 您完成這個任務(wù)。

揭開面紗
所有的 appender 都必須擴展 org.apache.log4j.AppenderSkeleton 類,這是一個抽象類,它實現(xiàn)了 org.apache.log4j.Appenderorg.apache.log4j.spi.OptionHandler 接口。AppenderSkeleton 類的 UML 類圖看起來如圖1所示:

圖 1. AppenderSkeleton 的 UML 類圖
AppenderSkeleton UML class diagram

下面讓我們研究一下 AppenderSkeleton 類所實現(xiàn)的 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 可以具有關(guān)聯(lián)的 Layout,因此還有另一個針對 layout 的setter/getter 方法。注意我們說的是“可以”而不是“必須”。這是因為有些 appender 不需要 layout。lauout 管理格式輸出——也就是說,它返回 LoggingEvent String 表示形式。另一方面,JMSAppender 發(fā)送的事件是 串行化的,因此您不需要對它附加 layout。如果自定義的 appender 不需要 layout,那么 requiresLayout() 方法必須返回 false,以避免 log4j 抱怨說丟失了 layout 信息。

  • errorHandler: 另一個 setter/getter 方法是為 ErrorHandler 而存在的。appender 可能把它們的錯誤處理委托給一個 ErrorHandler 對象——即 org.apache.log4j.spi 包中的一個接口。實現(xiàn)類有兩個:OnlyOnceErrorHandlerFallbackErrorHandler。OnlyOnceErrorHandle 實現(xiàn) log4j 的默認錯誤處理策略,它發(fā)送出第一個錯誤的消息并忽略其余的所有錯誤。錯誤消息將輸出到 System.err。FallbackErrorHandler 實現(xiàn) ErrorHandler 接口,以便能夠指定一個輔助的 appender。如果主 appender 失敗,輔助 appender 將接管工作。錯誤消息將輸出到 System.err,然后登錄到新的輔助 appender。

還有管理過濾器的其他方法(比如 ddFilter()、clearFilters()getFilter() 方法 )。盡管 log4j 具有過濾日志請求的多種內(nèi)置方法(比如知識庫范圍級、日志記錄器級和 appender 閾值級),但它使用自定義過濾器方法的能力也是非常強大的。

一個 appender 可以包含多個過濾器。自定義過濾器必須擴展 org.apache.log4j.spi.Filter 抽象類。這個抽象類要求把過濾器組織為線性鏈。 對每個過濾器的 decide(LoggingEvent) 方法的調(diào)用要按照過濾器被添加到鏈中的順序來進行。自定義過濾器基于三元邏輯。decide() 方法必須返回 DENYNEUTRAL 或者 ACCEPT 這三個整型常量值之一。

除了 setter/getter 方法以及和過濾器相關(guān)的方法外,還有另外兩個方法:close()doAppend()。close() 方法釋放 appender 中分配的任何資源,比如文件句柄、網(wǎng)絡(luò)連接,等等。在編寫自定義 appender 代碼時,務(wù)必要實現(xiàn)這個方法,以便當您的 appender 關(guān)閉時,它的 closed 字段將被設(shè)置為 true

如清單2所示的 doAppend() 方法遵循“四人組模板方法(Gang of Four Template Method )”設(shè)計模式(參見 參考資料)。這個方法提供了一個算法框架,它把某些步驟推遲到子類中來實現(xiàn)。

清單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所示,該算法:

  1. 檢查 appender 是否關(guān)閉。附加關(guān)閉的 appender 是一個編程錯誤。
  2. 檢查正在記錄日志的事件是否處于 appender 的閾值之下。
  3. 檢查是否有過濾器附加到 appender,如果有,則拒絕請求。
  4. 調(diào)用 appender 的 append() 方法。這個步驟被委托給每個子類。

我們已經(jīng)介紹了 AppenderSkeletonAppender 繼承來的方法和屬性。下面讓我們看看“為什么”AppenderSkeleton 要實現(xiàn) OptionHandler 接口。OptionHandler 僅包含一個方法:activateOptions()。這個方法在對屬性調(diào)用 setter 方法之后由一個配置器類調(diào)用。有些屬性彼此依賴,因此它們在全部加載完成之前是無法激活的,比如在 activateOptions() 方法中就是這樣。這個方法是開發(fā)人員在 appender 變?yōu)榧せ詈途途w之前用來執(zhí)行任何必要任務(wù)的機制。

除了上面提到的所有方法,讓我們再回頭觀察一下圖1。注意 AppenderSkeleton 提供了一個新的抽象方法(append() 方法)和一個新的 JavaBean 屬性(threshold)。threshold 屬性由 appender 用來過濾日志記錄請求,只有超過閾值的請求才會得到處理。我們在談到 doAppend() 方法之前就提到了 append() 方法。它是自定義 appender 必須實現(xiàn)的一個抽象方法,因為框架在 doAppend() 方法內(nèi)調(diào)用 append() 方法。append()方法是框架的鉤子(hook)之一。

現(xiàn)在我們已經(jīng)看到了 AppenderSkeleton 類中的所有可用方法,下面讓我們看看幕后發(fā)生的事情。圖2演示了 log4j 中的一個 appender 對象的 生命周期。

圖 2. appender 的生命周期圖
Life cycle of an appender object inside log4j

讓我們逐步地研究一下這個圖表:

  • appender 實例不存在。 或許框架還沒有配置好。

  • 框架實例化了一個新的 appender。這發(fā)生在配置器類分析配置腳本中的一個 appender 聲明的時候。配置器類調(diào)用 Class.newInstance(YourCustomAppender.class),這等價于動態(tài)調(diào)用 new YourCustomAppender()??蚣苓@樣做是為了避免被硬編碼為任何特定的 appender 名稱;框架是通用的,適用于任何 appender。

  • 框架判斷 appender 是否需要 layout。如果該 appender 不需要 layout,配置器就不會嘗試從配置腳本中加載 layout 信息。

  • Log4j 配置器調(diào)用 setter 方法。 在所有屬性都已設(shè)置好之后,框架就會調(diào)用這個方法。程序員可以在這里激活必須同時激活的屬性。

  • 配置器調(diào)用 activateOptions() 方法。 在所有屬性都已設(shè)置好之后,框架就會調(diào)用這個方法。程序員可以在這里激活必須同時激活的屬性。

  • Appender 準備就緒。 此刻,框架可以調(diào)用 append() 方法來處理日志記錄請求。這個方法由 AppenderSkeleton.doAppend() 方法調(diào)用。

  • 最后,關(guān)閉appender。 當框架即將要刪除您的自定義 appender 實例時,它會調(diào)用您的 appender 的 close() 方法。close() 是一個清理方法,意味著 您需要釋放已分配的所有資源。它是一個必需的方法,并且不接受任何參數(shù)。它必須把 closed 字段設(shè)置為 true,并在有人嘗試使用關(guān)閉的 appender 時向框架發(fā)出警報。

現(xiàn)在我們已經(jīng)回顧了與建立自己的 appender 相關(guān)的概念,下面讓我們考慮一個包括真實例子appender 的完整案例研究。

編寫自定義 appender 的訣竅

  1. 擴展 AppenderSkeleton 抽象類。

  2. 指定您的 appender 是否需要 layout。

  3. 如果某些屬性必須同時激活,則應(yīng)該在activateOptions()方法內(nèi)完成。

  4. 實現(xiàn) close() 方法。它必須把 closed 字段的值設(shè)置為 true。記 得釋放所有資源。

  5. 可選地指定要使用的默認 ErrorHandler 對象。

  6. 編寫 append() 方法的代碼。這個方法負責(zé)附加日志記錄事件,并在錯誤發(fā)生時負責(zé)調(diào)用錯誤處理程序。

編寫基于 IM 的 appender
本文給出的代碼說明了如何擴展 log4j 框架以集成 IM 特性。它被設(shè)計來使得 log4j 相容的應(yīng)用程序能夠把輸出記錄到 IM 網(wǎng)絡(luò)上。IM appender 實際上充當一個自定義的 客戶機。然而,它不是把 System.out、文件或者 TCP 套接字當作底層輸出設(shè)備,而是把 IM 網(wǎng)絡(luò)當作底層輸出設(shè)備。

為了提供 IM 支持,我們不需要在開發(fā)特定解決方案時完全重新開始。相反,我們將利用一個我們認為是該類別中最好的工具:Jabber。Jabber 是一種用于即時消息傳送和展示的基于 XML 的開放協(xié)議,它由 Jabber 社區(qū)開發(fā),非 營利性的 Jabber 軟件基金會(Jabber Software Foundation)對它提供技術(shù)支持。

我們之所以選擇 Jabber 而沒有選擇其他 IM 系統(tǒng),是因為 Jabber 提供了廣泛的好處,包括它的:

  • 開放性質(zhì): 不像其他的專有系統(tǒng),Jabber 的規(guī)范和源代碼是可以免費獲得的,從而允許任何人無成本地創(chuàng)建 Jabber 實現(xiàn)。

  • 簡單性: Jabber 使用基于 XML 的簡單協(xié)議作為它的標準數(shù)據(jù)格式,并且遵循大多數(shù)人都理解的客戶機/服務(wù)器 架構(gòu)。

  • 與其他 IM 系統(tǒng)的互操作性:Jabber 傳輸模塊(transport module)使得 Jabber 用戶訪問諸如 AIM、Yahoo!Messager 和 ICQ 等其他即時消息傳送系統(tǒng)成為可能。

  • 資源敏感:Jabber 對多客戶機訪問提供明確的支持。同一個用戶可以通過不同的客戶機(或者說資源)同時地連接到Jabber 服務(wù)器,消息將被恰當?shù)芈酚傻娇捎玫淖罴奄Y源。

為什么要把日志記錄到 IM 網(wǎng)絡(luò)?
日志記錄是開發(fā)人員必須養(yǎng)成的良好編碼習(xí)慣,就像編寫單元測試、處理異常或者編寫 Javadoc 注釋一樣。插入到代碼中明確位置的日志記錄語句起著審核工具的功能,提供了關(guān)于應(yīng)用程序內(nèi)部狀態(tài)的有用信息。與主流意見相反,我們認為在許多情況下,將日志語句保留在生產(chǎn)代碼中是方便的。如果 您擔心計算成本,就必須考慮從應(yīng)用程序中刪除日志記錄功能所帶來的少量性能提升是否值得。此外,log4j 的靈活性允許您聲明式地控制日志記錄行為。您可以建立嚴格的日志記錄策略來降低日志的累贅性并改進性能。

圖3顯示了 IMAppender 的一個使用場景:一個配置為使用 IMAppender 的 log4j 應(yīng)用程序記錄 它的被包裝為 IM 消息的調(diào)試數(shù)據(jù)。即時消息通過 Jabber 公司網(wǎng)絡(luò)被路由到系統(tǒng)管理員的Jabber 地址(注意,公開可用的 Jabber 服務(wù)器對生產(chǎn)應(yīng)用可能不足夠可靠)。因而,無論何時系統(tǒng)管理員需要檢查應(yīng)用程序的狀態(tài),他們只需加載最喜歡的 Jabber 客戶機,然后連接到Jabber 服務(wù)器。如圖3所示,管理員可以通過不同的設(shè)備來訪問。他可以使用辦公室的 PC 來登錄服務(wù)器,或者當他離開辦公桌時,可以使用運行在手持設(shè)備上的 Jabber 客戶機來檢查消息。

圖 3. IMAppender 使用場景
IMAppender usage scenario

但是為什么需要 IM appender 呢?因為向 IM 服務(wù)器發(fā)送消息將允許您通過自由選擇的工具(比如Jabber客戶機)來更容易地監(jiān)視應(yīng)用程序行為。

IMAppender提供了多個優(yōu)點:

  • 獲得實時通知——我們稱之為“即時日志記錄”。

  • 支持一對一(聊天)和一對多(小組聊天)模式。

  • Jabber 不只是用于臺式計算機。用于諸如 PDA 和移動電話等無線設(shè)備的客戶機也正在開發(fā)之中。

  • 要進入或退出應(yīng)用程序正在向其轉(zhuǎn)發(fā)日志數(shù)據(jù)的聊天室很容易。而要訂閱和取銷訂閱由 SMTPAppender 發(fā)送的電子郵件則很困難。

  • 通過安全套接字層(SSL)上的隧道來保證安全很容易。當然,您可以加密電子郵件,但是 SSL上 的 Jabber 既方便又快捷。

進階
IMAppender 模仿隨 log4j 一起分發(fā)的 SMTPAppender 的日志記錄策略。IMAppender 把日志記錄事件存儲在一個內(nèi)部循環(huán)緩沖區(qū)(cyclic buffer)中,并且僅當所接收到的日志記錄請求觸發(fā)了某個用戶指定的條件時,才把這些事件作為即時消息來發(fā)送?;蛘撸脩粢部梢蕴峁┮粋€觸發(fā)事件鑒別器類(triggering event evaluator class)。然而在默認情況下,消息傳送是由指定為 ERROR 或者更高級別的事件所觸發(fā)的。

每個消息中傳送的日志記錄事件的數(shù)量是由緩沖區(qū)的大小決定的。循環(huán)緩沖區(qū)僅保留最后的 bufferSize 個日志記錄事件,當它裝滿時就會溢出并丟棄較舊的事件。

為了連接到 Jabber 服務(wù)器,IMAppender 需要依賴 Jive Software 公司的 Smack API。Smack 是一個開放源代碼的高級庫,它處理與 Jabber 服務(wù)器通信的協(xié)議細節(jié)。這樣, 您無需任何特別的 Jabber 或者 XML 專業(yè)經(jīng)驗就能理解代碼。

IMAppender 的屬性總結(jié)在表 1中:

表 1. IMAppender 屬性

屬性 說明 類型 是否必需
host 服務(wù)器的主機名稱 String
port Jabber服務(wù)器的端口號 int 否,默認為 5222
username 應(yīng)用程序的Jabber帳戶用戶名 String
password 應(yīng)用程序的Jabber帳戶密碼 String
recipient

接收方的Jabber地址。Jabber地址也稱為Jabber ID,它在一個@字符后面指定用戶的Jabber 域,就像電子郵件地址一樣

這個屬性可以保存任何聊天地址或者聊天室地址。例如,您可以指定這樣的聊天地址:sysadmin@company.com;或者 您可能希望向 sysadmin@company.com 小組聊天服務(wù)器上名為"java-apps"的某個聊天小組發(fā)送日志記錄消息(例如,java-apps@conference.company.com

String
chatroom 接受一個布爾值。如果為 true,recipient 值將被接受為小組聊天地址。如果要設(shè)置這個選項,還應(yīng)該設(shè)置 nickname 選項。默認情況下,recipient 值被解釋為一個聊天地址 boolean 否,默認為 false
nickname 僅當設(shè)置了chatroom 屬性時才會考慮這個屬性。否則,它將被忽略

用戶可以選擇 appender 使用的任意小組聊天昵稱來加入小組聊天。昵稱不一定要和 Jabber用戶名有關(guān)
String
SSL 用于保護與 Jabber 服務(wù)器的連接 boolean 否,默認為false
bufferSize 可以保留在循環(huán)緩沖區(qū)中的日志記錄事件的最大數(shù)量 int 否,默認為16
evaluatorClass

這個屬性的值被當作一個類的完全限定名稱的字符串表示形式,該類實現(xiàn)了 org.apache.log4j.spi. TriggeringEventEvaluator 接口(換句話說,也就是一個包含自定義觸發(fā)邏輯的類,它覆蓋了默認的觸發(fā)邏輯)。如果沒有指定這個選項,IMAppender 將使用 DefaultEvaluator 類的一個實例,這個類根據(jù)被指定為 ERROR 或更高級別的事件觸發(fā)響應(yīng)

String 否,默認為 DefaultEvaluator

現(xiàn)在讓我們進一步觀察代碼。IMAppender 類遵循清單3所示的結(jié)構(gòu):

清單 3. IMAppender 類的總體結(jié)構(gòu)

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() { ... }
    
}

請注意關(guān)于我們的 appender 的如下幾個方面:

  • IMAppender 類擴展 org.apache.log4j.AppenderSkeleton,這是所有自定義 appender 都必須要做的。IMAppenderAppenderSkeleton 繼承諸如 appender 閾值和自定義過濾之類的公共功能。

  • 我們的 appender 的第一部分很簡單。每個 appender 都有字段和 set/get 方法。屬性和方法簽名遵守 JavaBeans 命名約定。因而,log4j 能夠通過反射來分析 appender,透明地處理 appender 配置。為節(jié)省篇幅,上述代碼片斷僅顯示了 setHost()getHost() 方法。

  • 為了完成我們的 appender,我們必須實現(xiàn) log4j 框架調(diào)用來管理我們的 appender 的回調(diào)方法:requiresLayout()activateOptions()、append()close()

log4j 框架調(diào)用 requiresLayout() 方法來判斷自定義 appender 是否需要 layout。注意 ,有些appender 使用內(nèi)置格式或者根本就不格式化事件,因此它們不需要 Layout 對象。IMAppender 需要 layout,因而該方法返回 true,如 清單4所示:

清單 4. requiresLayout() 方法

public boolean requiresLayout() {
    return true;
}

注意,AppenderSkeleton 實現(xiàn)了 org.apache.log4j.spi.OptionHandler 接口(參見 圖 1 )。AppenderSkeleton 把這個接口的單個方法 activateOptions() 實現(xiàn)為一個空方法。我們的 IMAppender 需要這個方法是由于其屬性之間的相互依賴性。例如,與 Jabber 服務(wù)器的連接依賴 Host、PortSSL 屬性,因此 IMAppender 在這三個屬性被初始化之前無法建立連接。log4j 框架調(diào)用 activateOptions() 方法來通知 appender 所有屬性都已設(shè)置就緒。

IMAppender.activateOptions() 方法激活指定的屬性(比如 Jabber 主機、端口、bufferSize,等等),所采取的方式是實例化依賴這些屬性值的更高級對象,如清單5所示:

清單 5. 只有在調(diào)用 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() 方法完成以下任務(wù):

  • 建立 bufferSize 個事件的最大循環(huán)緩沖區(qū)。我們使用了 org.apache.log4j.helpers.CyclicBuffer 的一個實例,org.apache.log4j.helpers.CyclicBuffer 是 log4j 附帶的一個輔助類,它提供了緩沖區(qū)的邏輯。

  • Smack 的 XMPPConnection 類創(chuàng)建了一個到 XMPP (Jabber) 服務(wù)器的連接,這個服務(wù)器是通過 hostport 屬性來指定的。為了創(chuàng)建一個 SSL 連接,我們要使用 SSLXMPPConnection 子類。

  • 大多數(shù)服務(wù)器都要求您在執(zhí)行其他任務(wù)之前首先登錄,因此我們使用由 usernamepassword 屬性所定義的 Jabber 帳戶來登錄,同時調(diào)用 XMPPConnection.login() 方法。

  • 在登錄之后,我們創(chuàng)建一個 Chat 或者 GroupChat 對象,具體視 chatroom 值而定。

activateOptions() 方法返回之后,appender 就準備好處理日志記錄請求了。如 清單6所示,由 AppenderSkeleton.doAppend() 調(diào)用的 append() 方法將執(zhí)行大多數(shù)實際的日志附加工作。

清單 6. append() 執(zhí)行實際的輸出操作

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() 將輸出一條警告消息并返回,從而不會繼續(xù)進行輸出操作。下一個語句把事件添加到循環(huán)緩沖區(qū)實例 cb。然后,if 語句把日志記錄事件提交給 evaluator,這是一個 TriggeringEventEvaluator 實例。如果 evaluator 返回 true,這意味著該事件與觸發(fā)條件匹配,sendBuffer() 就會被調(diào)用。

清單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() 方法把緩沖區(qū)的內(nèi)容作為IM消息來發(fā)送。此方法逐項遍歷保留在緩沖區(qū)中的事件,同時調(diào)用 layout 對象的 format() 方法來格式化每個事件。事件的字符串表示形式被附加到 StringBuffer 對象。最后,sendBuffer() 調(diào)用 chat 或者 groupchat 對象的 sendMessage() 方法,把消息發(fā)送出去。

請注意以下幾點:

  • AppenderSkeleton.doAppend() 方法(它調(diào)用 append())是經(jīng)過同步的,因此 sendBuffer() 已經(jīng)擁有 appender 的監(jiān)視器。這使得我們不必在 cb 上執(zhí)行同步操作。

  • 異常提供了極其有用的信息。由于這個原因,如果指定的 layout 忽略了包含在 LoggingEvent 對象中的可拋出對象,自定義 appender 的開發(fā)人員必須輸出包括在事件中的異常信息。如果 layout 忽略了可拋出的對象,那么 layout 的 ignoresThrowable() 方法應(yīng)該返回 true,并且 sendBuffer() 可以使用 LoggingEvent.getThrowableStrRep() 方法來檢索包含在該事件中的可拋出信息的 String[] 表示形式。

下載源代碼
執(zhí)行這個示例所必需的全部JAR文件(instantlogging.jar、smack-1.1.0.jar 和 log4j-1.2.8.jar)包括在本文的源代碼的 lib/ 目錄下。相應(yīng)的 zip 檔案文件可從 參考資料 下載。

把全部內(nèi)容組合起來
下面將通過展示 IMAppender 的實際工作效果來結(jié)束本文的討論。我們將使用一個相當簡單的名為 com.orangesoft.logging.example.EventCounter 的應(yīng)用程序,如 清單8所示。這個示例應(yīng)用程序在命令行接受兩個參數(shù)。第一個參數(shù)是一個整數(shù),對應(yīng)于要產(chǎn)生的日志記錄事件的數(shù)量。第二個參數(shù)必須是以屬性的格式提供的一個 log4j 配置文件名。這個應(yīng)用程序總是以 ERROR 事件結(jié)束,該事件將觸發(fā)一次 IM 消息傳送。

清單 8. EventCounter 示例應(yīng)用程序

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。

在試驗這個示例應(yīng)用程序之前,請確保將 host、username、passwordrecipient 屬性設(shè)置為 您所在環(huán)境中的適當值。下面的命令將運行 EventCounter 應(yīng)用程序:


java com.orangesoft.logging.examples.EventCounter 100 
eventcounter.properties

當運行時,EventCounter 將根據(jù) eventcounter.properties 所設(shè)置的策略記錄 100 個事件。然后一個 IM 消息將從接收方的屏幕上彈出來。圖4、5、6 顯示了不同平臺上的 Jabber 客戶機接收到的結(jié)果消息:

圖 4. Windows (Rhymbox)上的 Jabber 客戶機接收到的消息的屏幕快照
Windows Jabber client (Rhymbox)

圖 5. Linux (PSI)上的 Jabber 客戶機接收到的消息的屏幕快照
Linux Jabber client (PSI)

圖 6. Pocket PC (imov)上的 Jabber 客戶機接收到的消息的屏幕快照
Pocket PC Jabber client (imov)

注意 EventCounter 產(chǎn)生了 100 個事件。然而,由于 IMAppender 緩沖區(qū)的默認大小為 16,接收方應(yīng)該收到僅包含最后 16 個事件的 IM 消息??梢钥吹?,包含在最后一個事件(消息和堆棧跟蹤)中的異常信息已經(jīng)被正確地傳送了。

這個例子應(yīng)用程序只展示了 IMAppender 的一個非常小的用途,因此繼續(xù)探索它吧,您會找到很多樂趣的!

結(jié)束語
log4j 網(wǎng)絡(luò) appender,SocketAppender、 JMSAppenderSMTPAppender 已經(jīng)提供了監(jiān)視 Java 分布式應(yīng)用程序的機制。然而,多個因素使得 IM 成為用于實時遠程日志記錄的合適技術(shù)。在本文中,我們介紹了通過自定義 appender 來擴展 log4j 的基礎(chǔ)知識,并看到了一個基本 IMAppender 的逐步實現(xiàn)過程。許多開發(fā)人員和系統(tǒng)管理員都可以從 appender 的使用中獲益。

參考資料

  • 下載 IMAppender 類的 源代碼 、例子應(yīng)用程序和必需的庫。 您可以根據(jù)需要隨便使用和擴展這些源代碼。

  • log4j項目首頁 獲得最新的 log4j 版本 ——包括完整的源代碼、類文件和文檔。欲了解更多信息,請在 log4j官方文檔頁 查看文章和演示材料。

  • 如果您的開發(fā)工作需要超出本文檔范圍的內(nèi)容,可以參考 Ceki Gülcü 編著的一本優(yōu)秀參考書 log4j完全參考手冊(QOS.ch, 2003),該書詳細介紹了 log4j 的基本特性和高級特性。

  • developerWorks Web services 專區(qū)提供了 LogKit 作為“每周內(nèi)容”(component of the week)” (2001年8月)。LogKit 是 Jakarta 的 Avalon項目 的日志記錄組件。

  • Sun 已經(jīng)完成了一個名為 JSR 47 的社區(qū)過程(community process),它定義了 Java 平臺的日志記錄 API。JSR 47 API和 log4j 在體系結(jié)構(gòu)層次上相當類似,不過 log4j 具有許多 JSR 47 所沒有的特性。

  • 在“Merlin的魔力:異常和日志記錄”(developerWorks,2001年12月)中,John Zukowski展示了新的 JDK 1.4 日志記錄 API 是如何工作的。

  • Thomas E. Davis 撰寫的 JavaWorld 文章 “Logging on to Internet Relay Chat (IRC)” 介紹了一種允許應(yīng)用程序把輸出寫到 IRC 的簡單工具。

  • Gerhard Poul 的文章“Jabber” (developerWorks,2002年5月)說明了 Jabber 如何適合于當今的電子商務(wù) 基礎(chǔ)設(shè)施。

  • Smack 是 Jive Software 公司提供的一個開放源代碼庫,用于與 Jabber 服務(wù)器通信以執(zhí)行即時消息傳送和聊天。


  • Iain Shigeoka 的文章 用 Java 實現(xiàn)即時消息傳送 (Manning, 2002) 深入分析了各種 Jabber 協(xié)議。

  • “模板方法(Template Method)設(shè)計模式”出自 Erich Gamma、Richard Helm、Ralph Johnson 和 John Vlissides 所著的 設(shè)計模式 一書(Addison-Wesley出版, 1995年),這四位作者也被稱為“四人組(Gang of Four,GoF)。

  • 也可以在線閱讀 模板方法設(shè)計模式 的相關(guān)報道。

  • developerWorks Java技術(shù)專區(qū) 可以找到數(shù)百篇關(guān)于 Java 編程的各個方面的文章。

    本站是提供個人知識管理的網(wǎng)絡(luò)存儲空間,所有內(nèi)容均由用戶發(fā)布,不代表本站觀點。請注意甄別內(nèi)容中的聯(lián)系方式、誘導(dǎo)購買等信息,謹防詐騙。如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請點擊一鍵舉報。
    轉(zhuǎn)藏 分享 獻花(0

    0條評論

    發(fā)表

    請遵守用戶 評論公約

    類似文章 更多