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的類圖吧: