(轉)告別System.out.print() —J2SDK1.4新增Java日誌框架

的Java日誌框架,其實總結起來主要是下面幾點:
  1. 命名空間:空間外層包含內層的設置
  2. Handler:可以將日誌信息放入內容,定向到文件,或控制檯等
  3. Lever:規定日誌的級別,低級別的日誌可以被忽略
  4. Formatter:負責將日誌進行格式化,這樣出來的東西比較好看一點

正文:

告別System.out.print()
—J2SDK1.4新增Java日誌框架
(作者:Sonzhang Zhao )
引言
作爲一名Java 程序員,最熟悉的、使用最多的調用恐怕莫過於System.out.print(“…”)。
當你沒有調試工具而要跟蹤一個變量的值得時候;當你需要顯示捕獲的Exception、Error的
時候;當你想知道程序在運行的時候究竟發生了什麼的時候,通常的做法就是調用
System.out.print把他們在終端、控制檯上打印出來。這種方式對於輸出信息的分類、格式化
及永久保存帶來諸多不便。雖然我們可以把它寫入一個文件然後進行分析,但是這要需要編
寫額外的程序代碼,其成本不可忽視!而由此給目標系統本身增加的複雜程度不可避免的使
開發、調試陷入一個深深的迷潭。
JDK1.4的推出,使得這一切即將成爲歷史。讓我們向System.out.print()告別吧,使用Java
Logging API爲自己的程序構建一個完整的日誌記錄系統!
一、第一個實例
先看一個簡單的實例:SimpleLoggingTest.java
 import java.util.logging.*;
 public class SimpleLoggingTest {
 public static void main(String args[]) {
 //程序的其它處理
 //使用Logger的靜態方法獲得一個匿名Logger
 Logger logger1 = Logger.getAnonymousLogger();
 //記錄消息
 logger1.log(Level.INFO,"第一條日誌記錄");
 //程序的其它處理
 }
 }
實例1
注意:編譯、執行該程序需要JDK1.4及以上版本的支持。
運行該程序,可以在控制檯看到程序運行結果:
2003-1-14 15:09:40 SimpleLoggingTest main
信息: 第一條日誌記錄

首先,程序引用java.util.Logging包(第1行)。接着,在適當的時候獲得一個Logger(記
錄器)類的實例(第6行,獲取一個匿名的Logger)。最後,在程序需要記錄信息的地方調用
Logger類的log方法進行記錄(第8行,記錄一個INFO級別的消息)。
二、Java Logging API
Java Logging API封裝在JDK1.4.0的java.util.Logging包中。它通過產生便於最終
用戶、系統管理員、故障維護工程師以及軟件開發團隊(工程師)進行分析的日誌記錄爲軟
件的開發調試和維護提供便利的手段。它捕獲操作系統平臺和執行程序的安全故障、配置錯
誤、執行瓶頸和(或)Bug等數據信息,以純文本、XML或程序員自定的某種方式將其格式
化成日誌記錄,然後傳遞給內存、系統輸出流、控制檯、文件、Sockets等多種系統資源進
行緩存和輸出。
(一)、該軟件包中的關鍵類。
.. Logger: 應用程序進行日誌記錄調用的主要實體。 Logger對象用於記錄特
定系統或應用程序的消息。
.. LogRecord: 用於在日誌框架和單個記錄處理程序之間傳遞記錄請求。
.. Handler: 日誌數據的最終輸出處理器。它將LogRecord對象導出到各種目
標,包括內存、輸出流、控制檯、文件和套接字。多種 Handler子類可供用
於這種用途。
.ü..?..?..?..?..?..
圖一
.. Level: 定義一組標準的記錄級別,可用於控制記錄的輸出。可以把程序配置
爲只輸出某些級別的記錄,而忽略其他級別的輸出。
.. Filter: 精細過濾、控制記錄的內容,比記錄級別所提供的控制準確得多。
記錄API支持通用的過濾器機制,這種機制允許應用程序代碼添加任意過濾器
以便控制記錄的輸出。
.. Formatter: 爲LogRecord對象的格式化提供支持。

.ü..?..?..?..?..?..
圖二
.. LogManager: Java Logging框架中唯一的、全局的對象,用於維護與Logger
記錄器及日誌服務的一系列共享的數據結構及狀態。它負責整個日誌框架的初
始化、維護一組全局性的Handle對象、 維護一個樹形結構的Logger的名字
空間、診測日誌框架配置文件的改變從而重新讀入並應用相關的參數以及負責
程序停止運行時整個日誌框架的清理工作。
(二)Logger
1、Logger的命名空間
在SimpleLoggingTest.java實例中,我們使用了一個匿名的(沒有命名的)
Logger對象。在Java Logging 框架中,Logger是可以命名的。Logger的名字空
間與java類的名字空間相同的結構相同:使用“.”間隔的字符串。Logger的名
字空間體現了Logger的層次結構。例如:命名爲“a.b”的Logger是命名爲“a.b.c”
的“父”(上一級)Logger記錄器。Logger的命名可以是任意的字符串,一般情況
下,使用包或類的名字爲Logger 進行命名。
Logger的名字空間由全局單列類LogManager的實例進行創建、維護。
匿名Logger不被存儲在命名空間中。
2、創建Logger實例
Logger對象可以通過調用工廠方法getLogger或getAnonymousLogger獲取。
//獲取一個名爲“A”的Logger對象
Logger loggerA= Logger.getLogger(“A”);
// 獲取一個名爲“A.B”的Logger對象,其上級記錄器爲loggerA。
Logger loggerAB= Logger.getLogger(“A.B”);
//獲取一個匿名Logger對象
Logger loggerTmp = Logger.getAnonymousLogger();
對非匿名Logger,getLogger先在命名空間中查找同名的Logger對象,如果有,
則返回該Logger對象;如果不存在,則在命名空間中創建註冊一個新的Logger
對象,並與其上級Logger對象相關聯。
匿名Logger對象屬於創建它的對象的私有對象,只能由創建它的對象使用,
記錄一些臨時性的日誌信息。而命名Logger對象使全局性的,在日誌框架的生存
期內,除了創建它的對象外還,可由其它對象用於記錄日誌信息。
匿名的Logger對象由一個全局的的root Logger “” 對象(root Logger的名字爲
空)。這意味着所有匿名Logger對象將從root Logger “”中繼承行爲。
匿名Logger對象通常用於java Applet應用中。它去掉了在運行過程中的一班

性的安全檢查,允許其創建類對象對Logger的控制、狀態信息進行修改,如:
setLevel設置Logger的日誌消息記錄級別;addHandle增加Logger的Handle(處
理器)對象等。
一個Logger對象可以擁有有零個到多個Handler實例。當沒有Handler時,如
不禁止日誌記錄沿名字空間向上傳遞,那該Logger對象的日誌消息記錄將有其擁
有Handler實例的上級Logger進行處理。當一個Logger對象擁有多個Handler實
例對象時,其記錄的日誌數據將被所有的Handler逐一進行處理。
(三)、Handler
Handler對象接收傳來的日誌消息將其輸出。Handler可以把日誌消息輸出到多
種目標資源,如:輸出到控制檯進行顯示、寫入日誌文件、傳送到網絡上的遠程
日誌服務進行處理、寫入系統日誌等任何物理資源。
Handler對象在創建時使用LogManager對象的相關屬性的默認值(如Handler
的Filter、Formatter、Level等對象屬性)進行初始化。
Handler對象可通過調用setLevel(Level.OFF)暫停工作;通過調用setLevel設置
適當的記錄日誌消息級別恢復工作。
Handler是一個抽象類。在J2SDK1.4中,其子類及它們之間的關係見圖一。
1、 MemoryHandler Handler的子類,在內存中的一個循環緩衝區用於緩存日
志記錄請求。通常MemoryHandler只簡單的把傳入的LogRecords存儲到它的
內存中。這種緩存的開銷非常低廉,它去掉了格式化所產生的系統消耗。當某
個觸發條件滿足時,MemoryHandler將其緩衝的數據push(發佈)到目標
Handler,由後者執行實際的輸出。有三種模式觸發MemoryHandler進行push
操作:a、傳入的LogRecords的級別高於MemoryHandler預先定義的push級
別;b、有其他對象顯式的調用其push方法;c、其子類重載了log方法,逐
一檢索每個傳入的LogRecords,若符合特定的標準則進行push操作。
實例:假設我們需要跟蹤一個生產環境中的一個很少出現的Bug。在大多
數場合,系統化產生大量的日誌記錄,而我們僅只關心記錄中最近的幾條,那
麼我們只需要使用MemoryHandler對日誌記錄進行緩存,當且僅當某個事件
發生時將最近的幾條記錄從內存中 dump到制定的文件中。
//MemoryHandlerTest.java
import java.util.logging.*;
import java.io.*;
public class MemoryHandlerTest {
FileHandler fhandler;
Logger logger;
MemoryHandler mhandler;
MemoryHandlerTest() {
try {
//構造名爲my.log的日誌記錄文件
fhandler = new FileHandler("my.log");
int numRec = 5;
//構造一個5個日誌記錄的MemoryHandler,
//其目標Handler爲一個FileHandler

mhandler = new MemoryHandler (fhandler, numRec,
Level.OFF) ;
//構造一個記錄器
logger = Logger.getLogger("com.mycompany");
//爲記錄器添加一個MemoryHandler
logger.addHandler(mhandler);
} catch (IOException e) {
}
}
public static void main(String args[]) {
MemoryHandlerTest mt = new MemoryHandlerTest();
int trigger = (int)(Math.random()*100);
for (int i=1;i<100;i++) {
//在MemoryHandler中緩存日誌記錄
mt.logger.log(Level.INFO,"日誌記錄"+i);
if (i==trigger) {
//觸發事件成立,顯式調用MemoryHandler的
//push方法觸發目標Handler輸出日誌記錄到
//my.log文件中
mt.mhandler.push();
break;
}
}
}
}
實例2
2、 FileHandler 文件處理器。
StreamHandler流處理器將日誌記錄以流的形式輸出。FileHandler、
ConsoleHandler、SocketHandler爲StreamHandler的子類。 ConsoleHandler
將日誌記錄輸出到控制終端。前面的實例(實例2除外)都將日記記錄數據輸
出到控制檯。
FileHandler將日誌記錄輸出到特定的文件,或循環的幾個日誌文件中。日誌
文件可以設置容量大小。當日志文件達到限定的容量時將被自動清空,重頭開
始寫入新的日誌記錄數據。
例:創建一個容量爲1Mb的文件處理器
int limit = 1000000; // 1 Mb
FileHandler fh = new FileHandler("my.log", limit, 1);
對於循環的日誌文件,每個文件將被指定容量限制。噹噹前的日誌文件的長度
達到制定值後該文件被關閉,新的日誌文件被創建,舊的文件將在文件名模板
後追加序號。如此產生多個順序編號的日誌記錄文件。

例:
try {
// 創建一個擁有3個日誌文件,每個容量爲1Mb的文件處理器
String pattern = "my%g.log";
int limit = 1000000; // 1 Mb
int numLogFiles = 3;
FileHandler fh = new FileHandler(pattern, limit, numLogFiles);

} catch (IOException e) {
}
(四)、Level
Level對象定義了一組日誌消息的級別,用於控制日誌消息的輸出。一個級別
對應一個整型值。日誌消息級別按照其整數值的大小排定優先級。在Logger對象
中設定一個級別,則大於等於該級別的日誌消息將會被傳遞到某個Handler對象
進行輸出處理。
J2SDK1.4的Java Logging框架中定義了以下消息級別:
級別名稱
int值(Windows 2000環境)
OFF
2147483647
SEVERE
1000
WARNING
900
INFO
800
CONFIG
700
FINE
500
FINER
400
FINEST
300
ALL
-2147483648
表一
Level.OFF具有最高的級別。將Logger的Level級別設置成Level.OFF
讓我們看看消息級別是怎樣工作的:LoggingLevelTest.java
import java.util.logging.*;
public class LoggingLevelTest {
public static void main(String args[]) {
//使用Logger的靜態方法獲得一個匿名Logger
Logger logger1 = Logger.getAnonymousLogger();
//設置Logger對象記錄的最低日誌消息級別
logger1.setLevel(Level.FINER);
//記錄消息
logger1.severe("SEVERE級消息");
logger1.warning("WARNING級消息");
logger1.config("CONFIG級消息");
logger1.info("INFO級消息");
logger1.fine("FINE級消息");

logger1.finer("FINER級消息");
logger1.finest("FINEST級消息");
}
}
實例3
運行結果:
2003-1-15 7:02:03 LoggingLevelTest main
服務器: SEVERE級消息
2003-1-15 7:02:04 LoggingLevelTest main
警告: WARNING級消息
2003-1-15 7:02:04 LoggingLevelTest main
配置: CONFIG級消息
2003-1-15 7:02:04 LoggingLevelTest main
信息: INFO級消息
可以看出,優先級低於INFO的日誌消息不被記錄。
Level的構造函數爲protected便於程序員開發自己的消息級別類。
import java.util.logging.*;
//自定義消息級別
class myLevel extends Level{
//定義自己的消息級別SYSE
public static final Level SYSE = new myLevel("SYSE",
Level.SEVERE.intValue()+10);
public myLevel(String ln,int v) {
super(ln,v);
}
}
public class MyLevelTest {
public static void main(String args[]) {
Logger logger1 = Logger.getAnonymousLogger();
//設置消息級別
logger1.setLevel(myLevel.SYSE);
//記錄消息
logger1.log(myLevel.SYSE,"SYSE消息");
logger1.severe("SVERE消息");
}
}
實例4
運行結果:

2003-1-15 15:40:04 MyLevelTest main
SYSE: SYSE消息
只有SYSE消息被記錄,SVERE消息不被記錄,因爲自定義級別SYSE高於
SEVERE.
(五)Formatter
Formatter負責對LogRecords進行格式化。每個記錄處理器Handler同一個
Formatter對象相關聯。Formatter對象接收從Handler傳來的LogRecord,將其
格式化成字符串後返回給Handler進行輸出。
Formatter是一個抽象類。在J2SDK1.4中,其子類及它們之間的關係見圖二。
自定義擴展Formatter類。實例:MyFormatterTest.java
import java.util.Date;
import java.util.logging.*;
//創建每條日誌記錄以行的日誌格式:
//時間<空格>消息級別<空格>消息ID<空格>日誌信息內容<換行>
class MyFormatter extends Formatter {
public String format(LogRecord rec) {
StringBuffer buf = new StringBuffer(1000);
buf.append(new Date().toLocaleString()); //時間
buf.append(' ');
buf.append(rec.getLevel()); //消息級別
buf.append(' ');
buf.append(rec.getMillis()); //作爲消息ID
buf.append(' ');
buf.append(formatMessage(rec));//格式化日誌記錄數據
buf.append('/n'); //換行
return buf.toString();
}
}
public class MyFormatterTest {
public static void main(String args[]){
//創建記錄器
Logger log1 = Logger.getLogger("MyLogger");
//創建記錄處理器
Handler mh = new ConsoleHandler();
//爲記錄處理器設置Formatter
mh.setFormatter(new MyFormatter());
//爲記錄器添加記錄處理器

log1.addHandler(mh);
//禁止消息處理將日誌消息上傳給父級處理器
log1.setUseParentHandlers(false);
//記錄消息
log1.severe("消息1");
log1.warning("消息2");
log1.info("消息3");
log1.config("消息4");
}
}
實例5
程序運行結果:
2003-1-15 16:59:38 SEVERE 1042621178968 消息1
2003-1-15 16:59:40 WARNING 1042621178985 消息2
2003-1-15 16:59:41 INFO 1042621179105 消息3
三、配置文件
J2SDK1.4的Java Logging框架的配置文件(Windows):
%J2SDK1.4_HOME%/jre/lig/logging.properties
從配置文件可以看到:
(一) 自定義日誌配置文件:
java -Djava.util.logging.config.file=myfile
(二)全局Handler在Java VM啓動時被加載。
(二) 全局Handler默認爲java.util.logging.ConsoleHandler。
handlers= java.util.logging.ConsoleHandler
所以我們的任何日誌記錄動作都會在控制檯進行顯示。
(三) 缺省的消息記錄級別爲:INFO
.level= INFO
在缺省情況下我們在控制檯看不見低於INFO級別的日誌消息。
(四) 缺省的Handler消息格式爲java.util.logging.SimpleFormatter
四、日誌框架在程序測試中的應用
Logger類提供了兩個的方法:Logger.entering() ogger.exiting() 。這對我們調試
自己的方法調用提供了便利的方式。
例子:
記錄方法調用的輸入參數和輸出參數 方法myMethod將一個int 追加在一個對象之後。
運行該程序應將logging.properties的
java.util.logging.ConsoleHandler.level = INFO
改爲:
java.util.logging.ConsoleHandler.level = ALL

import java.util.logging.*;
public class MyClass {
public String myMethod(int p1, Object p2) {
Logger logger = Logger.getLogger("com.mycompany.MyClass");
if (logger.isLoggable(Level.FINER)) {
logger.entering(this.getClass().getName(), "myMethod",
new Object[]{new Integer(p1), p2});
}
String tmp = p2.toString() + p1;
if (logger.isLoggable(Level.FINER)) {
logger.exiting(this.getClass().getName(), "myMethod", tmp);
}
return tmp;
}
public static void main(String args[]) {
MyClass mc = new MyClass();
String rslt = mc.myMethod(123,"Hello");
}
}
後記
J2SDK1.4引入的日誌記錄框架爲構建簡易的日誌記錄系統提供了便利的解決方案。雖
然還有期它的一些專用日誌包如Log4j,但從簡單的打印輸出到嚴密的、可擴展的日誌記錄
框架,J2SDK1.4的日誌系統已經足以滿足一般的系統開發的要求。

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