JDK從1.4開始提供Logging實現,據說當初JDK打算采用Log4J的,后來因為某些原因談判沒談攏,然后就自己開發了一套,不知道是為了報復而故意不沿用Log4J的命名方式和抽象方式,還是開發這個模塊的人水平不夠,或沒用心,亦或是我用Commons Logging和Log4J習慣了,看JDK的Logging實現怎么看怎么不爽~~~吐個槽額~~~~
JDK Logging將日志打印抽象成以下幾個類之間的交互:
1. Level,定義日志的級別,類似Log4J中的Level類。
JDK Logging采用了完全不同于Log4J中對級別的抽象。在JDK Logging中,默認定義了以下幾個級別:SEVERE(對應Log4J中的ERROR或FATAL)、WARNING(對應Log4J中的WARN)、INFO(對應Log4J中的INFO)、CONFIG(對應Log4J中的DEBUG)、FINE(對應Log4J中的TRACE)、FINER(對應Log4J中的TRACE)、FINEST(對應Log4J中的TRACE)。另外,類似Log4J,JDK Logging也定義了兩個特殊的級別:ALL和OFF,分別對應打印所有級別的日志和關閉日志打印。
Level中包含三個字段:name、value、resourceBundleName。其中name指定級別名稱,value指定該級別對應的一個int值,其值從SEVERE開始依次遞減,resourceBundleName定義本地化后的級別名稱,默認是sun.util.logging.resources.logging,即我們在日志中看到警告、信息等級別字段就是通過調用Level的getLocalizedName()方法,讀取resourceBundleName對應的resource值來獲得的,這也是Log4J中沒有聽過的。
Level還定義了一個parse()方法,它可以支持解析name字符串、代表級別的int值(以字符串的形式)、以及對應的Localized名稱,如果所有的都不滿足需求,則拋出IllegalArgumentException。
最后,Level還實現了readResolve()方法,從而確保反序列化后的Level只是Level類中定義的幾個實例(出了自定義的Level實例)。
2. LogRecord,封裝了打印一條一直所包含的所有數據,類似Log4J中的LoggingEvent。
它包含了以下信息:
a. 日志級別(level)
b. 全局標識號(sequenceNumber,即沒創建一個LogRecord,sequenceNumber都會自增1)
c. 打印這條日志語句所在的類的名稱(sourceClassName),可以通過調用inferCaller()方法解析出來。解析實現則是通過實例化一個Throwable實例,通過解析該實例的Call Stack即可得出打印這條日志所在的類的名稱和方法名稱,這里貌似不支持行號、文件名等信息。并且通過設置needToInferCaller字段(不可序列化)來判斷sourceClassName和sourceMethodName是否已經取得,從而不用每次調用的時候都去解析而提升性能。
d. 打印這條日志語句所在調用方法的名稱(sourceMethodName),它也是通過調用inferCaller()方法解析出來。
e. 打印消息(message)
f. 線程號(threadID),對LogRecord本身而言,從0開始對每個線程自增1
g. 創建LogRecord的時間(millis)
h. 打印日志中的異常(thrown)
i. 日志名稱(loggerName)
j. 資源名稱(resourceBundleName)
k. 參數列表(parameters數組),在Formatter中通過MessageFormat使用這些參數列表,并在序列化是手動調用其toString()方法序列化,而不是采用默認的序列化方式。
l. ResourceBundle實例,獲取本地消息,不可序列化。
3. Formatter,根據配置格式化一條日志記錄,類似Log4J中的Layout。
JDK Logging支持兩種類型的Formatter:SimpleFormatter和XMLFormatter,默認采用SimpleFormatter,它先打印日期和時間、LoggerName或source ClassName、方法名稱,然后換行,在打印日志級別、本地化后的消息,然后換行,打印異常信息。而XMLFormatter實現getHead()、getTail()方法,并且將每條記錄寫成一條<record></record>記錄。吐槽一下,它的實現是在是太不專業了~~。
4. Handler,實現將日志寫入指定目的地,如ConsoleHandler、FileHandler、SocketHandler即對應將日志寫入控制臺、文件、Socket端口。它類似Log4J中的Appender。
Handler包含對Formatter、Level、Filter的引用,其中Formatter將LogRecord格式化成String字符串;Level定義當前Handler支持的日志級別;而Filter則提供一個用戶自定義的過濾一些日志的擴展點。
public interface Filter {
public boolean isLoggable(LogRecord record);
}
用戶可以定義自己的Filter類以實現用戶自定義的過濾邏輯。Handler還定義了encoding屬性,以配置底層日志的輸出格式。
Handler中最終要的方法是publish(),它實現了真正打印日志消息的邏輯。
public abstract void close() throws SecurityException;
默認JDK Logging實現了StreamHandler,而StreamHandler有三個子類:ConsoleHandler、FileHandler、SocketHandler。StreamHandler支持的配置有:
java.util.logging.StreamHandler.level 設置當前Handler支持的級別,默認為FINE
java.util.logging.StreamHandler.filter 設置當前Handler的Filter,默認為null
java.util.logging.StreamHandler.formatter 設置當前Handler的Formatter類,默認為SimpleFormatter
java.util.logging.StreamHandler.encoding 設置當前Handler的編碼方式,默認為null
ConsoleHandler只是將OutputStream設置為System.err,其他實現和StreamHandler類似。
而SocketHandler將OutputStream綁定到對應的端口號中,其他也和StreamHandler類似。另外它還增加了兩個配置:java.util.logging.SocketHandler.port和java.util.logging.SocketHandler.host分別對應端口號和主機。
FileHandler支持指定文件名模板(java.util.logging.FileHandler.pattern),文件最大支持大小(java.util.logging.FileHandler.limit,字節為單位,0為沒有限制),循環日志文件數(java.util.logging.FileHandler.count)、對已存在的日志文件是否往后添加(java.util.logging.FileHandler.append)。
FileHandler支持的文件模板參數有:
/ 目錄分隔符
%t 系統臨時目錄
%h 系統當前用戶目錄
%g 生成的以區別循環日志文件名
%u 一個唯一的數字以處理沖突問題
%% 一個%
5. LogManager類,讀取配置文件和管理Logger實例,類似Log4J的LogRepository。
在LogManager類初始化時,用戶可以通過指定java.util.logging.manager系統屬性以自定義LogManager,默認使用LogManager類本身。初始化完成后創建RootLogger,并將新創建的RootLogger實例加入到LogManager中。LogManager中將所有創建的Logger緩存在loggers字段中(Hashtable,name作為key,WeakReference<Logger>作為value)。RootLogger的name為空,因而所有對RootLogger的配置都從”.”開始。
對Logger的配置支持一下幾種方式:
<name>.level=INFO|CONFIG….
handers=<handlerName1>,<handlerName2>…. (以”,”分隔或以空格、TAB等字符分隔,全局Handler)
config=<configClassName1>,<configClassName2>….(自定義類,實現在其構造函數中實現自定義配置)
<name>.userParentHandlers=true|false|1|0
<name>.handlers=<handlerName1>,<handlerName2>…(以”,”分隔或以空格、TAB等字符分隔)
<handlerName>.level=INFO|CONFIG….
以及各自Handler本身支持的配置,具體各自的Handler。
用戶可以通過以下方式自定義配置文件:
a. 設置系統屬性java.util.logging.config.class,由自定義類的構造函數實現自定義配置,如調用LogManager、Logger中的一些靜態方法。
b. 設置系統屬性java.util.logging.config.file,自定義配置文件路徑。讀取該文件中的內容作為配置信息。
c. 默認使用${java.home}/lib/logging.properties文件作為配置文件(JDK已經提供了一些默認配置,一般是${JRE_HOME}/lib/logging.properties文件)
類似Log4J,LogManager也將Logger構建成樹狀結構,并且對那些暫時沒有真正Logger實例的節點,使用LogNode,同樣構建成樹,這個實現也類似Log4J的ProvisionNode。
6. Logger類,用戶打印log接口,類似Log4J中的Logger。
Logger包含name、Handlers、resourceBundleName、useParentHandlers、filter、anonymous、levelObject、parent、kids等字段。其中其他字段都比較容易理解,anonymous比較難理解,按注釋,它是用來表達當前Logger是一個匿名Logger,即不會被加入到LogManager中,因而也不需要安全檢查,匿名Logger一般在Applet中使用,對Applet不了解,因而也無法做更詳細的解釋。在構建樹時,該Logger同時保持了父節點和子節點的所有引用,對JDK這個Logging的實現一直無力吐槽。
Logger提供getLogger()接口,這個也是一般用戶直接打交道的接口,傳入name,返回緩存的Logger實例或新創建一個Logger實例。
對匿名Logger,Logger提供getAnonymousLogger()接口。log(LogRecord)方法是對打印日志的真正實現:
public void log(LogRecord record) {
if (record.getLevel().intValue() < levelValue || levelValue == offValue) {
return;
}
synchronized (this) {
if (filter != null && !filter.isLoggable(record)) {
return;
}
}
Logger logger = this;
while (logger != null) {
Handler targets[] = logger.getHandlers();
if (targets != null) {
for (int i = 0; i < targets.length; i++) {
targets[i].publish(record);
}
}
if (!logger.getUseParentHandlers()) {
break;
}
logger = logger.getParent();
}
}
其他log方法只是對該方法中LogRecord中不同字段參數的組合。其他logp()系類方法只是提供給用戶自定log所在的方法名和類名;而entering、existing、thrown等幾個方法只是幾個傻逼的命名而已。
最后給張JDK Logging的類圖吧:
