許多應(yīng)用, 特別是企業(yè)應(yīng)用, 都需要日志記錄程序。應(yīng)用日志有助于服務(wù)工程師解決這個領(lǐng)域的難題, 并且能為安全分析提供審計跟蹤。
最初, Java 平臺包含了很少可用于應(yīng)用日志記錄的資源。被迫面對日志文件中的println語句的開發(fā)人員和系統(tǒng)管理員,有的設(shè)法發(fā)明自家的解決方案,有的轉(zhuǎn)而采用由商業(yè)開發(fā)商或者開放源碼開發(fā)者生產(chǎn)的眾多優(yōu)秀(但互不兼容)的日志記錄產(chǎn)品中的一種。Java 2版本1.4 改變了這種局面,因為它引入了java.util.logging包。
本期技巧簡要地解釋了Java日志記錄的基本知識,并提供了示例代碼,演示如何通過Java消息服務(wù) (JMS)發(fā)送應(yīng)用日志消息到某個獨立客戶端端。如果您不熟悉JMS, 參看2003年4月15 日那一期的“Publish/Subscribe Messaging With JMS Topics”以及 2003年3月11 日那一期的“使用JMS Queues(Using JMS Queues)”。
java.util.logging 包
Java logging包最初是作為Java Specification Request (JSR-047)創(chuàng)建的,現(xiàn)在是Java 2, Standard Edition (J2SE)的一部份。它的需求包括以下特性:
- 最小的運行時性能影響。
- 運行時啟用/禁用日志記錄。
- 細(xì)粒度控制。
- 運行時日志記錄服務(wù)注冊。
- 與現(xiàn)有日志記錄服務(wù)的互操作性, 比如,系統(tǒng)日志和遺留日志記錄方案。
- 在合適的時候向用戶顯示高優(yōu)先級消息。
- 能夠處理國際化的日志記錄消息。
- 能夠記錄對象, 而不只是記錄字符串 。
由于logging包是J2SE 的一部分, 它在其他Java 平臺也是可用的, 包括J2EE。具體地,Java logging可以用于應(yīng)用客戶端、servlets 、JSP 頁面、企業(yè)bean 和連接器。
Logging類和接口
日志記錄程序的主要類是 Logger。 Logger表示一種通道,記錄(logging)消息可以通過它進行發(fā)送。通常,每個類有它自己的Logger。
Logger 用Level來配置,這是一個指明問題嚴(yán)重性的類,用于指明所報告的問題的嚴(yán)重性和/或該類需要的詳情級別。每一條發(fā)送到Logger的消息都有一個關(guān)聯(lián) Level。 Logger 只報告那些Level比該Logger高或者與之相同的錯誤。
最高的日志記錄 Level 是 SEVERE, 表明碰到了一個嚴(yán)重問題—— 經(jīng)常指的是一個致命錯誤。其他 Level 值(以遞減順序) 是 WARNING 和 INFO, 分別為可恢復(fù)的錯誤和信息類消息。 CONFIG 級別表明, 配置事件(比如正在讀一個屬性文件) 發(fā)生過。編程人員可以使用 的Level 值有: FINE、FINER 和 FINEST ,以連續(xù)報告更加細(xì)粒度的登錄消息。服務(wù)工程師可以使用這些 Level值來隱藏外部細(xì)節(jié), 或在類到類(class-by-class)的基礎(chǔ)上增加日志記錄細(xì)節(jié)的級別。Logger的缺省 Level 是 INFO。
LogRecord 是一個表示應(yīng)該寫入日志的一條消息的對象。它包含各種各樣的信息, 包括被打印的消息, 最初發(fā)送給Logger的名字, Level 和所發(fā)送消息的創(chuàng)建時間與日期, 以及調(diào)用者的線程id。
一個叫做 Handler的抽象類表示一個知道如何以一種有用方式表示 LogRecord的類。logging包包含一些 Handler類 (比如 ConsoleHandler、FileHandler、StreamHandler 和 SocketHandler) ,它們報告 LogRecords 各種類型的目標(biāo)。每一 Logger都可以有多 個Handler。 每個 Handler 還可以有一個 Level, 摒除任何 LogRecord ,其Level 在它自己之下。因此, 例如, 單個 Logger 可能有二個Handler: 一個用于在磁盤上寫入日記文件,而另一個用于將日志消息發(fā)送到系統(tǒng)管理員的尋呼機。磁盤日志 Handler 可以用Level FINEST來配置,因此它將所有日志消息寫入磁盤。相反, 尋呼機日志 Handler可能只報告 SEVERE 日志消息。
Formatter類(及其所定義的任何子類)將LogRecord轉(zhuǎn)換為String以便打印。例如, Formatter 的XMLFormatter 子類將LogRecord 寫為格式良好的XML。開發(fā)人員可以編寫定制的 Formatter 類。
最后, Filter 接口允許編程人員編寫方法(isLoggable),該方法允許進行規(guī)劃性的控制,在這種控制下丟棄或者打印LogMessages。
示例代碼
本技巧的示例代碼示范了如何設(shè)置 Logger, 并使用它發(fā)送XML 格式的日志消息,從Web層,通過JMS,到達(dá)一個獨立JMS 客戶。
本技巧的示例代碼包含一個servlet 和一個獨立客戶端端。servlet 稱作 LogDemoServlet。獨立客戶端端稱作LoggingReceiver。獨立客戶端端監(jiān)聽正發(fā)布給JMS主題的消息, 并打印接收到任何TextMessages。示例應(yīng)用包含一個用戶向LogDemoServlet張貼數(shù)據(jù)的HTML表單。
注意, 表單提示用戶下載一個JAR文件,然后運行該文件作為一個獨立客戶端。在通過表單張貼數(shù)據(jù)之前,用戶需要完成這些步驟。
servlet 通過向Topic發(fā)布一條XML 格式的消息來作出響應(yīng)。如果 LoggingReceiver正在運行, 用戶會看到在屏幕上打印出一條XML 格式的消息。
例如:
<?xml version="1.0" encoding="MacRoman" standalone="no"?>
<!DOCTYPE log SYSTEM "logger.dtd">
<log>
<record>
<date>2003-05-05T23:51:20</date>
<millis>1052200280257</millis>
<sequence>0</sequence>
<logger>com.elucify.tips.may2003.LogDemoServlet</logger>
<level>INFO</level>
<class>com.elucify.tips.may2003.LogDemoServlet</class>
<method>doPost</method>
<thread>10</thread>
<message>quest= { 'Johnny' }
FavoriteColor= { 'Violet' }
name= { 'Johnny' }
</message>
</record>
示例代碼中的關(guān)鍵部分是servlet LogDemoServlet。這個servlet 簡單地將它的POST參數(shù)格式化為一個字符串, 并在servlet每次被調(diào)用時將該字符記錄到Logger 中。方法doPost的開始處設(shè)置了 Logger:
// Handle post request
public void doPost(HttpServletRequest req,
HttpServletResponse res)
throws IOException, ServletException {
res.setContentType("text/html");
PrintWriter pw = res.getWriter();
// Get POST parameters and write them as
// "variable=value" to a ByteArrayOutputStream
String postParams = getPostParams(req);
ByteArrayOutputStream bos =
new ByteArrayOutputStream();
Logger logger = Logger.getLogger(
this.getClass().getName());
servlet 設(shè)置內(nèi)容類型、獲得一個Writer, 并格式化POST參量。然后它通過調(diào)用靜態(tài)方法 Logger.getLogger(this.getClass().getName())來創(chuàng)建了一個Logger。這個方法返回該名稱現(xiàn)有的Logger, 或者如果不存在的話,就創(chuàng)建一個。代碼使用了servlet 類的完全限定名。這是一個公共約定,以確保系統(tǒng)中各種Logger的名稱不會發(fā)生沖突。
缺省情況下,Logger將它所創(chuàng)建的任何消息寫入到標(biāo)準(zhǔn)輸出,而不管運行什么代碼。例如,Web層消息通常寫入Web服務(wù)器日志文件。
doPost方法的第二部分向Logger添加一個新的 Handler 。每次消息記錄到Logger時, Logger發(fā)送消息到兩個 Handlers (假設(shè)消息的日志級別足夠高) 。下面是的doPost方法的第二部分 :
// Format logging messages as XML,
// store in byte array
StreamHandler sh = new StreamHandler(
bos, new XMLFormatter());
logger.addHandler(sh);
logger.log(Level.INFO, postParams);
sh.flush();
// Send contents of buffer as JMS message
// to listeners
publish(bos.toString());
pw.println(
"Logging messages sent to subscribers");
}
StreamHandler 是一個使用Formatter 來將LogRecord 格式化為字符串,并將它寫入OutputStream的處理程序。在本例中, OutputStream 是 ByteArrayOutputStream, 因此日志記錄結(jié)果被寫入內(nèi)存。這個方法還使用一個 XMLFormatter,以便 LogRecords 被寫作XML文件 。對addHandler的調(diào)用將新的StreamHandler添加到Logger,然后將包含格式化POST參數(shù)的日志消息發(fā)送到 Logger。Logger 發(fā)送消息到它的所有Handlers,包括新的StreamHandler。對flush()的調(diào)用確保了所有已寫入字節(jié)被刷新到ByteArrayOutputStream。最后, ByteArrayOutputStream 被轉(zhuǎn)換成XML字符串。再通過發(fā)布方法將字符串發(fā)布給一個JMS Topic。關(guān)于如何將消息發(fā)送到JMS Topic的詳細(xì)描述(使用同樣的發(fā)布方法) ,請參看2003年4月15 日那一期的技巧“Publish/Subscribe Messaging With JMS Topics”。