轉(zhuǎn)帖|其它|編輯:郝浩|2009-01-12 13:16:53.000|閱讀 969 次
概述:日志記錄對(duì)于軟件的維護(hù)特別是對(duì)于已部署到運(yùn)行環(huán)境之后的軟件調(diào)試都有著重要的意義。本文介紹了 JDK 的日志框架,以及如何根據(jù)不同需求自定義日志處理、消息格式化、消息級(jí)別等組件。最后闡述了如何利用 JDK 日志框架的擴(kuò)展能力將 Java 程序能夠通過 STAF(Software Testing Automation Framework,一種自動(dòng)化測(cè)試框架)日志服務(wù)來進(jìn)行監(jiān)視。
# 界面/圖表報(bào)表/文檔/IDE等千款熱門軟控件火熱銷售中 >>
文章關(guān)鍵字:|JDK|日志|框架|Java|JDK|XML|HTML|測(cè)試|
自 Java 1.4 開始,JDK 包括了一個(gè)嶄新的日志框架包 java.util.logging,該日志框架設(shè)計(jì)精良,和 JDK 緊密結(jié)合,控制靈活,使用簡(jiǎn)單。日志記錄對(duì)于軟件的維護(hù)特別是對(duì)于已部署到運(yùn)行環(huán)境之后的軟件調(diào)試都有著重要的意義。在實(shí)際的項(xiàng)目中,往往還需要比該框架所提供的更為復(fù)雜的日志功能。對(duì)于這種需求,JDK 日志框架具有足夠的可擴(kuò)展能力,可以自定義不同需求的日志處理、消息格式化、日志消息級(jí)別等組件。在下面的內(nèi)容中,本文將介紹了如何擴(kuò)展 JDK 日志框架,自定義日志處理方式。并就一個(gè)實(shí)際的例子來介紹如何結(jié)合 JDK 日志框架和 STAF(Software Testing Automation Framework,一種自動(dòng)化測(cè)試框架)日志服務(wù)來對(duì) Java 程序進(jìn)行監(jiān)視。
JDK 日志框架介紹
JDK 的日志框架即 java.util.logging 包。對(duì)于一個(gè)軟件的日志系統(tǒng)而言,首先必須得有一個(gè)日志對(duì)象,該對(duì)象負(fù)責(zé)記錄日志信息。同時(shí)該信息可以輸出到不同的位置,例如控制臺(tái),文件甚至網(wǎng)絡(luò)中。對(duì)于信息的格式,則可以根據(jù)不同的需求,可以輸出成普通文本,XML 或者 HTML 的格式。同時(shí)還需要對(duì)日志信息進(jìn)行不同級(jí)別的分類,這樣的好處是可以過濾冗余信息,只保留關(guān)鍵的日志。對(duì)于一個(gè)日志框架而言,日志對(duì)象必須是可配置的,它可以按照配置來輸出到指定的目標(biāo),同時(shí)按照配置來決定輸出的格式和決定何種級(jí)別以上的日志才能輸出。配置的形式還可以是多種多樣的,既能是代碼的形式,也能是配置文件的形式。尤其是配置文件的形式,對(duì)于一個(gè)已經(jīng)部署到運(yùn)行環(huán)境中的軟件而言,可以非常方便的改變?nèi)罩九渲枚鵁o需改變其源代碼。
JDK 日志框架提供了上述的所有功能。它主要包括如下幾個(gè)部件:
對(duì)于程序而言,它的 Logger 對(duì)象首先會(huì)判斷日志的級(jí)別是否滿足輸出級(jí)別的要求,然后將滿足級(jí)別要求的日志消息交給所配置的 Handler 對(duì)象來處理,如果日志對(duì)象配置了一個(gè) Filter 對(duì)象,那么 Filter 對(duì)象將會(huì)對(duì)日志信息做一次過濾。 Handler 對(duì)象接受到日志消息后,根據(jù)其所配置的格式化類 Formatter 來改變?nèi)罩镜母袷剑鶕?jù)所配置的 Filter 對(duì)象和 Level 對(duì)象來再次過濾日志信息,最后輸出到該種 Handler 對(duì)象所指定的輸出位置中,該輸出位置可以是控制臺(tái),文件,網(wǎng)絡(luò) socket 甚至是內(nèi)存緩沖區(qū)。其架構(gòu)模型如圖 1 所示。
JDK 提供了如下幾種默認(rèn)支持的 Handler 類:
同時(shí) JDK 日志框架也不失其靈活性,你可以定制自己所需要的 Handler,將日志按照自定義的需求輸出到不同的位置,同時(shí) Formatter,Level 類都可以自定義擴(kuò)展,下面就詳細(xì)敘述如何自定義擴(kuò)展這些組件。
自定義日志 Handler
所有的 Handler 類都是繼承自 java.util.logging.Handler 抽象類,該類結(jié)構(gòu)圖如 圖 2 所示。
由該類圖可見,Handler 抽象類提供了抽象接口:publish, flush 和 close 。這些接口提供了日志輸出的基本功能。同時(shí) Handler 類保存了 Formatter,F(xiàn)ilter 和 Level 對(duì)象用來控制日志輸出。因此,編寫自定義的 Handler 類需要如下步驟:
[SPAN] 一個(gè)典型的自定義 Handler 類實(shí)現(xiàn)如清單 1 所示。
清單 1 自定義 Handler 類
public class MyHandler extends Handler { private boolean doneHeader = false; public MyHandler() { setLevel(Level.INFO); setFilter(null); setFormatter(new SimpleFormatter()); } _cnnew1@Override public void close() throws SecurityException { if (!doneHeader) { output(getFormatter().getHead(this)); doneHeader = true; } output(getFormatter().getTail(this)); flush(); } @Override public void flush() { // 清空緩沖區(qū) } @Override public void publish(LogRecord record) { if (!isLoggable(record)) { return; } String msg = getFormatter().format(record); try { if (!doneHeader ) { output(getFormatter().getHead(this)); doneHeader = true; } output(msg); } catch (Exception ex) { reportError(null, ex, ErrorManager.WRITE_FAILURE); } } private void output(String message) { // 實(shí)現(xiàn)日志輸出 } }
這里 reportError 方法是將日志類中的錯(cuò)誤信息輸出到外界,這個(gè)是由 ErrorManager 類實(shí)現(xiàn)的,ErrorManager 類負(fù)責(zé)記錄日志框架中 Handler 的錯(cuò)誤,一般情況下是將該錯(cuò)誤打印到控制臺(tái)中。具體的每條日志消息被 JDK 日志框架封裝成 LogRecord 對(duì)象,該類部分定義如 清單 2所示。
清單 2 LogRecord 類定義
public class LogRecord implements java.io.Serializable { public String getLoggerName(); public void setLoggerName(String name); public ResourceBundle getResourceBundle(); public void setResourceBundle(ResourceBundle bundle); public Level getLevel(); public void setLevel(Level level); public String getMessage(); public void setMessage(String message); public Object[] getParameters(); public void setParameters(Object parameters[]); public int getThreadID(); public void setThreadID(int threadID); public long getMillis(); public void setMillis(long millis); public Throwable getThrown(); public void setThrown(Throwable thrown); ... }
由清單 2 可見,LogRecord 類包含了一個(gè)日志消息的級(jí)別、消息文本、時(shí)間、參數(shù)、線程等等所有的信息,這些都交給 Handler,F(xiàn)ormatter 和 Filter 這些對(duì)象來處理。同時(shí)該類也是可序列化的,可以序列化到網(wǎng)絡(luò)和文件中。該類還可以和一個(gè) ResourceBundle 對(duì)象綁定,實(shí)現(xiàn)消息字符串的本地化處理。
本節(jié)描述了一個(gè)典型的自定義的 Handler 類的實(shí)現(xiàn)。在本文后面部分將會(huì)有一個(gè)實(shí)際的例子來介紹如何實(shí)現(xiàn)一個(gè) STAF 日志處理類。[SPAN]
自定義日志 Formatter
日志可以被格式化為一定格式的文本,也可以成為 XML 或者 HTML 這樣標(biāo)準(zhǔn)的格式。這取決于 Formatter 類的具體實(shí)現(xiàn)。 Formatter 抽象類提供了 format 成員函數(shù)用于擴(kuò)展。一個(gè)典型的自定義 Formatter 類實(shí)現(xiàn)如清單 3 所示:
清單 3 LogRecord 類定義
public class MyFormatter extends Formatter { private final String lineSeparator =
System.getProperty("line.separator"); @Override public String format(LogRecord record) { StringBuffer sb = new StringBuffer(); String message = formatMessage(record); sb.append(record.getLevel().getLocalizedName()); sb.append(message); sb.append(lineSeparator); if (record.getThrown() != null) { try { StringWriter sw = new StringWriter(); PrintWriter pw = new PrintWriter(sw); record.getThrown().printStackTrace(pw); pw.close(); sb.append(sw.toString()); } catch (Exception ex) { } } return sb.toString(); } }
其中 formatMessage 方法提供了默認(rèn)的將日志記錄本地化和格式化的方法。它還能支持 java.text 風(fēng)格的文本格式化,這只需要在調(diào)用 Logger 對(duì)象的 setMessage 方法設(shè)定 java.text 風(fēng)格的格式字符串,同時(shí)通過 setParameters 方法設(shè)置參數(shù),這樣 formatMessage 將會(huì)根據(jù)所設(shè)置的 java.text 風(fēng)格的格式字符串來格式化日志消息。總之,formatMessage 方法方便了子類格式化字符串。使子類只需要定義輸出文本的格式而無需考慮本地化等問題。
自定義日志消息級(jí)別
JDK 日志框架默認(rèn)提供了 SEVERE,WARNING,INFO,CONFIG,F(xiàn)INE,F(xiàn)INER,F(xiàn)INEST 這幾種日志級(jí)別。如果我們需要定義更多的日志級(jí)別,只需要繼承 java.util.logging.Level 類,然后將自定義的級(jí)別作為靜態(tài)成員變量聲明即可。一個(gè)典型的自定義的消息類如清單 4 所示。
清單 4 自定義 Level 類
public class MyLevel extends Level { protected MyLevel(String name, int value) { super(name, value); } public static final Level Level1 = new MyLevel("Level1", 123); ... // 其他自定義級(jí)別 }
權(quán)重值 value 是一個(gè)整型數(shù)。在默認(rèn)的 JDK 日志級(jí)別中,SEVERE 的權(quán)重是 1000,F(xiàn)INEST 是 300,可以根據(jù)具體的需求來定義每個(gè)自定義級(jí)別的權(quán)重。例如在 WARNING 和 INFO 級(jí)別中加入一個(gè)新的級(jí)別,該級(jí)別的權(quán)重必須介于 800 到 900 之間。[SPAN]
自由的日志配置
和其他日志框架一樣,JDK 日志框架同樣提供了強(qiáng)大的日志配置功能。你既可以通過代碼進(jìn)行動(dòng)態(tài)配置,也可以通過配置文件來實(shí)現(xiàn)自由靈活的配置。通過代碼動(dòng)態(tài)配置,應(yīng)用程序可以實(shí)現(xiàn)在運(yùn)行過程中改變?nèi)罩绢惖呐渲茫瑒?dòng)態(tài)地改變不同的配置組合。一個(gè)簡(jiǎn)單的動(dòng)態(tài)配置代碼如清單 5 所示。
清單 5 動(dòng)態(tài)配置 Logger 對(duì)象
public static void main(String[] args){ Handler fh = new FileHandler("%t/wombat.log"); Logger.getLogger("logname").addHandler(fh); Logger.getLogger("com.wombat").setLevel("com.wombat",Level.FINEST); ... }
配置文件的配置方法則同樣靈活多變。它主要是在應(yīng)用程序啟動(dòng)時(shí)根據(jù)一個(gè)指定的配置文件來設(shè)置日志對(duì)象。在配置文件中,日志對(duì)象是由其名稱來標(biāo)識(shí)的。一個(gè)典型的日志配置文件如清單 6 所示。
清單 6 JDK Logger 配置文件
# 設(shè)置日志對(duì)象的 Handler,日志對(duì)象的名稱是com.xyz.foo com.xyz.foo.handlers= java.util.logging.FileHandler,
java.util.logging.ConsoleHandler # 設(shè)置日志對(duì)象的基本輸出級(jí)別 com.xyz.foo.level = INFO #FileHandler 只允許輸出 SEVERE 以上級(jí)別的日志 java.util.logging.ConsoleHandler.level = SEVERE #ConsoleHandler 允許輸出 INFO 以上級(jí)別的日志 java.util.logging.ConsoleHandler.level = INFO
當(dāng)設(shè)置好一個(gè)日志配置文件后,在 java 程序的啟動(dòng)參數(shù)中,我們可以通過添加 -Djava.util.logging.config.file 參數(shù)來定義配置文件路徑,一個(gè)典型的 java 命令行如下:
java -Djava.util.logging.config.file=logger.properties -cp . Mainclass
我們也可以在應(yīng)用程序中聲明自定義的 Handler,F(xiàn)ormatter,Level 等組件,這只需要這些自定義組件能夠在 classpath 中找到即可。
實(shí)例——結(jié)合 STAF 日志服務(wù)
STAF(Software Testing Automation Framework)是一個(gè)自動(dòng)化軟件測(cè)試框架,它可以實(shí)現(xiàn)分布式的自動(dòng)化軟件測(cè)試管理。我們可以應(yīng)用 STAF 庫的 Java API 來做基于 STAF 框架的應(yīng)用,同時(shí) STAF 同時(shí)也提供了日志服務(wù)。其日志服務(wù)是用來記錄自動(dòng)化測(cè)試流程中的信息,方便在 24x7 的自動(dòng)化測(cè)試中記錄自動(dòng)化測(cè)試的操作,便于發(fā)現(xiàn)潛在的自動(dòng)化測(cè)試管理腳本的問題。
既然我們可以用 STAF 的 Java API 來做基于 STAF 的應(yīng)用,我們也可以將 JDK 的日志框架同 STAF 的日志服務(wù)接口結(jié)合起來。 STAF 的日志服務(wù)的 Java 接口定義如清單 7 所示:
清單 7 STAFLog 類定義
public class STAFLog { public STAFLog(String logType, String logName, STAFHandle handle); public STAFResult log(int level, String msg) // Log type constants public static STAFResult log(STAFHandle theHandle, String logType, String logName, int level, String msg) public String getName(); public String getLogType(); public int getMonitorMask(); ... //other methods }
從清單 7 我們可以看出,STAFLog 類提供了方法可以將日志信息存儲(chǔ)到 STAF 的日志庫中, 這個(gè)日志庫既可以是本地的文件,也可以是另一個(gè) STAF 服務(wù)器上的日志庫。這是通過本地 STAF 服務(wù)器的配置來決定的。而 STAFLog.log() 方法只用于記錄日志信息。[SPAN]
將 STAF 日志服務(wù)的 Java API 同 JDK 日志框架結(jié)合起來需要做如下步驟:
創(chuàng)建 STAF 日志 Handler 類
該類封裝了 STAF 日志服務(wù) API 的接口。同時(shí) STAF 的 Java API 需要一個(gè)全局的 STAFHandle 對(duì)象,用來表示本地的 STAF 服務(wù)句柄。這個(gè)可以通過建立一個(gè)靜態(tài)的 STAFHandle 對(duì)象即可。其代碼如下所示,我們定義了一個(gè) STAFHandler 類如清單 8 所示。
清單 8 STAFHandler 類實(shí)現(xiàn)
import java.util.logging.*; import com.ibm.staf.wrapper.STAFLog; public class STAFHandler extends Handler { private String logName; private static STAFHandle stafHandle = null; public STAFHandler(String name) { configure(); logName = name; } public STAFHandler() { configure(); } @Override public void close() throws SecurityException { if (stafHandle != null){ try { stafHandle.unRegister(); } catch (STAFException e) { //ignore } } } @Override public void flush() { //nothing } @Override public void publish(LogRecord record) { if (!isLoggable(record)) { return; } String msg; try { msg = getFormatter().format(record);
} catch (Exception ex) { reportError(null, ex, ErrorManager.FORMAT_FAILURE); return; } try { STAFLog.log(stafHandle, STAFLog.MACHINE,
logName, record.getLevel().getName(), msg); } catch (Exception ex) { reportError(null, ex, ErrorManager.WRITE_FAILURE); } ...
在實(shí)現(xiàn) STAFHandler 類時(shí)有以下幾個(gè)要點(diǎn):
但到目前為止,我們還沒有給 STAFHandler 類添加一個(gè)配置的代碼,使之可以支持配置文件。下面我們定義了一個(gè)函數(shù) configure,其代碼如清單 9 所示。
清單 9 配置函數(shù)實(shí)現(xiàn)
private void configure() { if (stafHandle == null) { try { stafHandle = new STAFHandle("my application"); } catch (STAFException e) { reportError("registe staf handle error",
e, ErrorManager.OPEN_FAILURE); } } LogManager manager = LogManager.getLogManager(); String cname = getClass().getName(); //set staf log name logName = manager.getProperty(cname + ".name"); if (logName == null) logName = "demo.staflog"; //set formatter String sformatter = manager.getProperty(cname + ".formatter"); Formatter formatter = null; if (sformatter != null) { try { formatter = (Formatter)Class.forName(sformatter).newInstance(); } catch (Exception e) { //ignore } } setFormatter(formatter == null? new STAFFormatter() : formatter); //set level String sLevel = manager.getProperty(cname + ".level"); Level level = null; if (sLevel != null) { try { level = STAFLevel.parse(sLevel); } catch (Exception e) { //ignore } } setLevel(level == null? STAFLevel.DEBUG : level); }
在實(shí)現(xiàn)配置文件支持的代碼中,有以下幾個(gè)要點(diǎn):
創(chuàng)建一個(gè)適合 STAF 日志的 Formatter 類
由于 STAF 日志服務(wù)無需特殊的格式,我們只需要定義一個(gè)普通文本格式的 Formatter 即可。其代碼如清單 10 所示,注意這里考慮了如果記錄了一個(gè)異常對(duì)象的情況,將異常對(duì)象的 stack 打印到字符串中添加到消息文本中。
清單 10. STAFFormatter 實(shí)現(xiàn)
import java.io.*; import java.util.logging.*; public class STAFFormatter extends Formatter { private final String lineSeparator = System.getProperty("line.separator"); @Override public String format(LogRecord record) { StringBuffer sb = new StringBuffer(); String message = formatMessage(record); sb.append(message); sb.append(lineSeparator); if (record.getThrown() != null) { try { StringWriter sw = new StringWriter(); PrintWriter pw = new PrintWriter(sw); record.getThrown().printStackTrace(pw); pw.close(); sb.append(sw.toString()); } catch (Exception ex) { } } return sb.toString(); } }
創(chuàng)建對(duì)應(yīng)于 STAF 日志級(jí)別的 Level 對(duì)象
這是由于 STAFLog 有著不同的日志消息級(jí)別,它包括 Fatal, Error, Warning, Info, Tracer, Debug 等級(jí)別,有些是 JDK 日志框架已有的級(jí)別,有些則不是。我們需要增加新的 Level 對(duì)象來滿足 STAFLog 的需求。一個(gè)新的 Level 類:STAFLevel 定義如清單 11 所示。
清單 11 自定義 STAFLevel
import java.util.logging.Level; public class STAFLevel extends Level { protected STAFLevel(String name, int value) { super(name, value); } protected STAFLevel(String name, int value, String resourceBundleName) { super(name, value, resourceBundleName); } public static final Level FATAL = new STAFLevel("FATAL",980); public static final Level ERROR = new STAFLevel("ERROR",980); public static final Level TRACE = new STAFLevel("TRACE", 790); public static final Level DEBUG = new STAFLevel("DEBUG", 690); }
清單 11 定義了 FATAL,ERROR,TRACE 和 DEBUG 級(jí)別。這就和 STAFLog 中的部分級(jí)別一一對(duì)應(yīng)起來了。
將一切組合起
清單 12 描述了如何在一段實(shí)際的代碼中將 STAF 日志處理類和 JDK 日志類結(jié)合起來。 從清單 12 可以看出,該實(shí)例默認(rèn)指定輸出到 STAF 日志服務(wù)的日志名稱為“ staflogger ”。然后通過動(dòng)態(tài)配置的方法來設(shè)定 Handler,Level 和 Formatter 。最后在調(diào)用 JDK 的日志對(duì)象的 log 方法記錄了 4 種自定義級(jí)別的日志。
清單 12 一個(gè)完整的例子
package demo.staflog; import java.util.logging.Logger; public class STAFLoggerTest { public static void main(String[] args) { Logger logger = Logger.getLogger(STAFLoggerTest.class.getName()); logger.setUseParentHandlers(false); logger.setLevel(STAFLevel.DEBUG); STAFHandler stafHandler = new STAFHandler("staflogger"); stafHandler.setLevel(STAFLevel.DEBUG); stafHandler.setFormatter(new STAFFormatter()); logger.addHandler(stafHandler); //log logger.log(STAFLevel.DEBUG, "debug log"); logger.log(STAFLevel.FATAL, "fatal log"); logger.log(STAFLevel.ERROR, "error log"); logger.log(STAFLevel.TRACE, "trace log"); } }
但我們也可以將這些代碼改為配置文件的方式,其配置文件如清單 13 所示:
清單 13 STAFLog 類定義
# 設(shè)置日志對(duì)象的 Handler demo.staflog.STAFLoggerTest.handlers= demo.staflog.STAFHandler demo.staflog.STAFLoggerTest.level = DEBUG # 取消發(fā)送日志到父 Logger 對(duì)象 demo.staflog.STAFLoggerTest.useParentHandlers = FALSE # 設(shè)置 Handler 的名稱,輸出級(jí)別和格式化對(duì)象 demo.staflog.STAFHandler.name= staflogger demo.staflog.STAFHandler.level = DEBUG demo.staflog.STAFHandler.formatter = demo.staflog.STAFFormatter
這樣代碼可以簡(jiǎn)化為清單 14 。
清單 14 STAFLog 類定義
public class STAFLoggerTest { private static Level defaultLevel = STAFLevel.DEBUG; public static void main(String[] args) { //log logger.log(STAFLevel.DEBUG, "debug log"); logger.log(STAFLevel.FATAL, "fatal log");
logger.log(STAFLevel.ERROR, "error log"); logger.log(STAFLevel.TRACE, "trace log");
} }
配置文件的方式相對(duì)于動(dòng)態(tài)配置的方式更加靈活,因?yàn)檫@無需改變和重新編譯代碼,只需要修改配置文件,就能修改日志中 Handler,Level 和 Formatter 的組合配置,這對(duì)于已經(jīng)部署發(fā)布的軟件而言,有著更為實(shí)際的意義。
當(dāng)運(yùn)行代碼后,在命令行中輸入 STAF 命令來顯示 STAF 日志 staflogger:
mymachine:~ myname$ staf local log query machine mymachine logname staflogger Response -------- Date-Time Level Message ----------------- ----- ---------- 20081111-16:15:21 Debug debug log 20081111-16:15:21 Fatal fatal log 20081111-16:15:21 Error error log 20081111-16:15:21 Trace trace log
這顯示了我們剛才在 Java 代碼中記錄的信息,它們已經(jīng)被輸出到 STAF 的日志服務(wù)中了。
結(jié)束語
JDK 日志框架簡(jiǎn)單靈活,它雖然比 log4j 出現(xiàn)的時(shí)期晚,但其功能并不比 log4j 少。而且 JDK 日志框架直接隸屬于 JDK,被 Java 標(biāo)準(zhǔn)所支持而無需安裝第三方庫文件。本文介紹了 JDK 日志框架的結(jié)構(gòu),如何擴(kuò)展 JDK 日志框架使之滿足實(shí)際的項(xiàng)目需求。并以如何在 Java 程序中將日志輸出到 STAF 的日志服務(wù)中為例,一步步描述了如何實(shí)現(xiàn)擴(kuò)展 JDK 日志組件,使之和 STAF 日志服務(wù)結(jié)合到一起,同時(shí)如何創(chuàng)建靈活的配置文件來組合日志框架組件。希望本文可以給其他需要擴(kuò)展 JDK 日志組件的開發(fā)者提供幫助。
本站文章除注明轉(zhuǎn)載外,均為本站原創(chuàng)或翻譯。歡迎任何形式的轉(zhuǎn)載,但請(qǐng)務(wù)必注明出處、不得修改原文相關(guān)鏈接,如果存在內(nèi)容上的異議請(qǐng)郵件反饋至chenjj@fc6vip.cn
文章轉(zhuǎn)載自:IBM