一、簡介 在程序中輸出信息的目的有三:一是監(jiān)視程序運行情況;一是將程序的運行情況記錄到日志文件中,以備將來查看;一是做為調(diào)試器。但信息輸出的手段不僅限于System.out.println()或System.out.print(),還有日志記錄工具可以選擇。與System.out.pringln()和System.out.print()相比,日志記錄工具可以控制輸出級別,并且可以在配置文件中對輸出級別進行設(shè)置,這樣開發(fā)階段的信息在程序發(fā)布后就可以通過設(shè)置輸出級別來消除掉,而無須對代碼進行修正了。現(xiàn)在流行的日志記錄工具很多, Log4J就是其中的佼佼者。 Log4J是由著名開源組織Apache推出的一款日志記錄工具,供Java編碼人員做日志輸出之用,可以從網(wǎng)站http://logging.apache.org/log4j上免費獲得,最新版本1.2.11。獲得logging-log4j-1.2.11.zip文件后,解壓縮,需要的是其中的log4j-1.2.11.jar文件,將該文件放到特定的文件夾中備用,我放到了我機器的G:\YPJCCK\Log4J\lib文件夾中。 這里選擇的IDE是Eclipse和JBuilder。Eclipse用的是3.0.1加語言包,可以到www.eclipse.org網(wǎng)站上下載;JBuilder用的是JBuilder 2005。 二、配置類庫 下面打開Eclipse或JBuilder。 如果使用的是Eclipse,那么在Eclipse打開后,點擊菜單"文件"->"新建"->"項目",打開"新建項目"對話框:
請選中"Java項目",點擊"下一步",進入"新建Java項目"對話框:
在這個對話框中需要設(shè)置項目的名稱以及項目所在目錄,我為自己的項目起名為Log4JTest,目錄為G:\YPJCCK\Log4J\Eclipse\ Log4JTest。設(shè)置好后點擊"下一步",進入下一個窗口。在這個窗口中選擇名為"庫"的選項卡,然后點擊"添加外部JAR"按鈕,將保存于特定文件夾中的log4j-1.2.11.jar文件引用進來。
設(shè)置好后,點擊"完成",至此,已經(jīng)具備了在Eclipse下使用Log4J的環(huán)境。 如果使用的是JBuilder,那么在JBuilder打開后,點擊菜單"Tools"->"Configure" ->"Libraries",打開"Configure Libraries"對話框:
點擊"New"按鈕,打開"New Library Wizard"對話框:
使用"Add"按鈕將保存于特定文件夾中的log4j-1.2.11.jar文件引用進來,并設(shè)置Name,即該類庫的名字,我將Name設(shè)置為 Log4J。設(shè)置好后點擊"OK"按鈕,回到"Configure Libraries"對話框,再點擊"OK"按鈕,則JUnit類庫已經(jīng)被添加到JBuilder當(dāng)中。 下面繼續(xù),在JBuilder中創(chuàng)建項目。點擊菜單"File"->"New Project",打開"Project Wizard"對話框:
在這個窗口中設(shè)置項目名稱及存放目錄,我的項目名稱仍為Log4JTest,路徑為G:/YPJCCK/log4J/JBuilder/Log4JTest。點擊"Next"進入下一個窗口:
在這個窗口中選擇"Required Libraries"選項卡,點擊"Add"按鈕,將剛才設(shè)置的JUnit庫引用進來。然后點擊"Next"按鈕,進入下一個窗口:
在這個窗口中用鼠標(biāo)點擊Encoding下拉列表框,然后按一下"G"鍵,選中相應(yīng)選項,此時該項目的字符集就被設(shè)置成GBK了。如果做的是國內(nèi)項目,這絕對是個好習(xí)慣。最后點擊"Finish",項目創(chuàng)建完成。 三、編寫一個簡單的示例 在了解Log4J的使用方法之前,先編寫一個簡單的示例,以對Log4J有個感性認(rèn)識。 如果使用的是Eclipse,請點擊"文件"->"新建"->"類",打開"新建Java類"對話框,設(shè)置包為 piv.zheng.log4j.test,名稱為Test,并確保"public static void main(String[] args)"選項選中;如果使用的是JBuilder,請點擊"File"->"New Class",打開"Class Wizard"對話框,設(shè)置Package為piv.zheng.log4j.test,Class name為Test,并確保"Generate main method"選項選中。設(shè)置完成后,點擊"OK"。代碼如下: package piv.zheng.log4j.test; import org.apache.log4j.Logger; import org.apache.log4j.Level; import org.apache.log4j.SimpleLayout; import org.apache.log4j.ConsoleAppender; public class Test { public static void main(String[] args) { SimpleLayout layout = new SimpleLayout(); ConsoleAppender appender = new ConsoleAppender(layout); Logger log = Logger.getLogger(Test.class); log.addAppender(appender); log.setLevel(Level.FATAL); log.debug("Here is DEBUG"); log.info("Here is INFO"); log.warn("Here is WARN"); log.error("Here is ERROR"); log.fatal("Here is FATAL"); } } 至此,示例編寫完成。請點擊運行按鈕旁邊的倒三角,選擇"運行為"->"2 Java應(yīng)用程序"(Eclipse),或者在Test類的選項卡上點擊鼠標(biāo)右鍵,在調(diào)出的快捷菜單中點擊"Run using defaults"(JBuilder),運行程序,觀察從控制臺輸出的信息。 四、Log4J入門 看過程序的運行效果后可能會奇怪,為何控制臺只輸出了"FATAL - Here is FATAL"這樣一條信息,而程序代碼中的log.debug()、log.info()等方法也都設(shè)置了類似的內(nèi)容,卻沒有被輸出?其實答案很簡單,但在公布之前,先來了解一下Log4J的使用。 請先看前邊的示例代碼,會發(fā)現(xiàn),示例中用到了Logger、Level、 ConsoleAppender、SimpleLayout等四個類。其中Logger類使用最多,甚至輸出的信息也是在其對象log的fatal方法中設(shè)置的,那么Logger究竟是做什么的呢?其實Logger就是傳說中的日志記錄器(在Log4J中稱為Category),創(chuàng)建方法有三: 1.根Category,默認(rèn)創(chuàng)建,獲取方法:
Logger log = Logger.getRootLogger();
2.用戶創(chuàng)建的Category,方法:
Logger log = Logger.getLogger("test");
其中字符串test是為Category設(shè)定的名稱。Category的名稱允許使用任何字符,但區(qū)分大小寫,例如:
Logger l1 = Logger.getLogger("x"); Logger l2 = Logger.getLogger("X");
l1和l2就是兩個Category;而如果名稱完全相同,例如:
Logger l1 = Logger.getLogger("x"); Logger l2 = Logger.getLogger("x");
l1和l2就是同一個Category。此外,符號"."在Category的名稱中有特殊作用,這一點將在后邊介紹。 3.與方法2類似,只是參數(shù)由字符串換成了類對象,其目的是通過類對象獲取類的全名。這個方法比較常用,示例中使用的就是這個方法。 那么Category是如何輸出信息的呢?其實示例中用到的debug、info、warn、error、fatal等五個方法都是用來輸出信息的。什么,怎么這么多?原因很簡單,Log4J支持分級輸出。Log4J的輸出級別有五個,由低到高依次是DEBUG(調(diào)試)、INFO(信息)、WARN(警告)、ERROR(錯誤)和FATAL(致命),分別與以上方法對應(yīng)。當(dāng)輸出級別設(shè)置為DEBUG時,以上方法都能夠輸出信息,當(dāng)輸出級別設(shè)置為INFO 時,則只有debug方法將不能再輸出信息,依此類推,當(dāng)輸出級別設(shè)置為FATAL時,就只有fatal方法可以輸出信息了。現(xiàn)在再回頭看前邊的問題,為何只有設(shè)置給fatal方法的信息被輸出就不難理解了,示例中有這樣一行代碼:
log.setLevel(Level.FATAL);
正是這行代碼將log對象的輸出級別設(shè)成了FATAL。在為log對象設(shè)置輸出級別時用到了Level類,該類中定義了DEBUG、INFO、WARN、 ERROR、FATAL等五個靜態(tài)對象,與五個輸出級別相對應(yīng)。此外,Level還有兩個特殊的靜態(tài)對象ALL和OFF,前者允許所有的方法輸出信息,其級別其實比DEBUG還低;后者則會禁止所有的方法輸出信息,其級別比FATAL要高。除前邊示例中用到的五個方法,Logger還提供了這五個方法的重載,以在輸出信息的同時拋出異常,以fatal方法為例:
log.fatal("Here is FATAL", new Exception("Exception"));
執(zhí)行后輸出信息: FATAL - Here is FATAL java.lang.Exception: Exception at piv.zheng.log4j.test.Test.main(Test.java:24) 其他方法類似。此外,Logger還提供了log方法,該方法不針對任何輸出級別,需要在調(diào)用時設(shè)置,例如:
log.log(Level.FATAL, "Here is FATAL"); log.log(Level.FATAL, "Here is FATAL", new Exception("Exception"));
雖然一般情況下log方法不如其它方法方便,但由于允許設(shè)置級別,因此log方法在很多時候反而比其它方法更靈活,甚至可以在輸出級別為OFF時輸出信息。不過log方法主要是給用戶自定義的輸出級別用的,而且設(shè)立OFF輸出級別的目的也為了不輸出任何信息,因此請不要在log方法中使用OFF來輸出信息。 此外,Category的輸出級別并非必須,若未設(shè)置,子Category會默認(rèn)使用其父Category的輸出級別,若父Category也沒設(shè)置,就使用再上一級Category的設(shè)置,直到根Category為止。根Category默認(rèn)輸出級別為DEBUG,因此在示例中,若將 "log.setLevel(Level.FATAL);"一行注釋掉,則所有方法都會輸出信息。 下面簡單介紹一下Log4J中 Category的繼承關(guān)系。其實在Log4J中Category之間是存在繼承關(guān)系的,根Category默認(rèn)創(chuàng)建,是級別最高的Category,用戶創(chuàng)建的Category均繼承自它。而用戶創(chuàng)建的Category之間也存在繼承關(guān)系,例如:
Logger lx = Logger.getLogger("x"); Logger lxy = Logger.getLogger("xy"); Logger lx_y = Logger.getLogger("x.y"); Logger lx_z = Logger.getLogger("x.z"); Logger lx_y_z = Logger.getLogger("x.y.z");
其中的lx_y、lx_z就是lx的子Category,而lx_y_z是lx_y的子Category。但lxy并不是lx的子Category。也許有點亂,下面來一個一個看。首先看與lx_y、lx_z對應(yīng)的Category的名稱"x.y"和"x.z","."前邊的是什么,"x",這說明與名稱為 "x"的Category對應(yīng)lx就是它們的父Category;而與lx_y_z對應(yīng)的Category的名稱"x.y.z",最后一個"."前邊的是什么,"x.y",這說明lx_y是lx_y_z的父Category;至于lxy,由于與之對應(yīng)的Category名稱"xy"之間沒有".",因此它是一個與lx同級的Category,其父Category就是根Category器。此外還有一種情況,例如有一個名稱為"a.b"的 Category,如果沒有名稱為"a"的Category,那么它的父Category也是根Category。前邊說過,"."在Category名稱中有特殊作用,其實它的作用就是繼承。至此,為何使用類對象來創(chuàng)建Category也就不難理解了。 可是,僅有Category是無法完成信息輸出的,還需要為Category添加Appender,即Category的輸出源。前邊的例子使用的是ConsoleAppender,即指定 Category將信息輸出到控制臺。其實Log4J提供的Appender有很多,這里選擇幾常用的進行介紹。 1.org.apache.log4j.WriterAppender,可以根據(jù)用戶選擇將信息輸出到Writer或OutputStream。 示例代碼: SimpleLayout layout = new SimpleLayout (); //向文件中輸出信息,OutputStream示例 WriterAppender appender1 = null; try { appender1 = new WriterAppender(layout, new FileOutputStream("test.txt")); } catch(Exception ex) {} //向控制臺輸出信息,Writer示例 WriterAppender appender2 = null; try { appender2 = new WriterAppender(layout, new OutputStreamWriter(System.out)); } catch(Exception ex) {} //Category支持同時向多個目標(biāo)輸出信息 Logger log = Logger.getLogger(Test.class); log.addAppender(appender1); log.addAppender(appender2); log.debug("output"); 這個示例由第一個示例修改而來,沒有設(shè)置輸出級別,而且向Category中添加了兩個輸出源,運行后會在控制臺中輸出"DEBUG - output",并在工程目錄下生成test.txt文件,該文件中也記錄著"DEBUG - output"。若要將test.txt文件放到其它路徑下,例如f:,則將"test.txt"改為"f:/test.txt",又如e:下的temp 文件夾,就改為"e:/temp/test.txt"。后邊FileAppender、RollingFileAppender以及 DailyRollingFileAppender設(shè)置目標(biāo)文件時也都可以這樣來寫。 2.org.apache.log4j.ConsoleAppender,向控制臺輸出信息,繼承了WriterAppender,前邊的示例使用的就是它。 3.org.apache.log4j.FileAppender,向文件輸出信息,也繼承了WriterAppender。 示例代碼: SimpleLayout layout = new SimpleLayout(); //若文件不存在則創(chuàng)建文件,若文件已存在則向文件中追加信息 FileAppender appender = null; try { appender = new FileAppender(layout, "test.txt"); } catch(Exception e) {} Logger log = Logger.getLogger(Test.class); log.addAppender(appender); log.debug("output"); 這個示例也由第一個示例修改而來,運行后會在工程目錄下生成test.txt文件,該文件中記錄著"DEBUG - output"。再次運行程序,查看文件,則"DEBUG - output"有兩行。 另外,F(xiàn)ileAppender還有一個構(gòu)造:
FileAppender(Layout layout, String filename, boolean append)
與示例的類似,只是多了一個boolean型的參數(shù)append。append參數(shù)是個開關(guān),用來設(shè)置當(dāng)程序重啟,而目標(biāo)文件已存在時,是向目標(biāo)文件追加信息還是覆蓋原來的信息,當(dāng)值為true時就追加,這是FileAppender默認(rèn)的,當(dāng)值為false時則覆蓋。此外,F(xiàn)ileAppender還提供了setAppend方法來設(shè)置append開關(guān)。 4.org.apache.log4j.RollingFileAppender,繼承了 FileAppender,也是向文件輸出信息,但文件大小可以限制。當(dāng)文件大小超過限制時,該文件會被轉(zhuǎn)為備份文件或刪除,然后重新生成。文件的轉(zhuǎn)換或刪除與設(shè)置的備份文件最大數(shù)量有關(guān),當(dāng)數(shù)量大于0時就轉(zhuǎn)為備份文件,否則(小于等于0)刪除,默認(rèn)的備份文件數(shù)量是1。轉(zhuǎn)換備份文件非常簡單,就是修改文件名,在原文件名之后加上".1",例如文件test.txt,轉(zhuǎn)為備份文件后文件名為"test.txt.1"。但若同名的備份文件已存在,則會先將該備份文件刪除或更名,這也與設(shè)置的備份文件最大數(shù)量有關(guān),若達到最大數(shù)量就刪除,否則更名。若備份文件更名時也遇到同樣情況,則使用同樣的處理方法,依此類推,直到達到設(shè)置的備份文件最大數(shù)量。備份文件更名也很簡單,就是將擴展名加1,例如test.txt.1文件更名后變?yōu)閠est.txt.2, test.txt.2文件更名后變?yōu)閠est.txt.3。 示例代碼: SimpleLayout layout = new SimpleLayout(); //若文件不存在則創(chuàng)建文件,若文件已存在則向文件中追加內(nèi)容 RollingFileAppender appender = null; try { appender = new RollingFileAppender(layout, "test.txt"); } catch(Exception e) {} //限制備份文件的數(shù)量,本例為2個 appender.setMaxBackupIndex(2); //限制目標(biāo)文件的大小,單位字節(jié),本例為10字節(jié) appender.setMaximumFileSize(10); Logger log = Logger.getLogger(Test.class); log.addAppender(appender); log.debug("output0"); log.debug("output1"); log.debug("output2"); 程序運行后,會在工程目錄下生成test.txt、test.txt.1和test.txt.2三個文件,其中test.txt內(nèi)容為空,而后兩個文件則分別記錄著"DEBUG - output2"和"DEBUG - output1",這是怎么回事?原來由于目標(biāo)文件大小被限制為10字節(jié),而三次使用log.debug方法輸出的信息都超過了10字節(jié),這樣就導(dǎo)致了三次備份文件轉(zhuǎn)換,所以test.txt內(nèi)容為空。而備份文件最大數(shù)量被設(shè)為2,因此第一次轉(zhuǎn)換的備份文件就被刪掉了,而后兩次的則保存下來。此外,由于 test.txt轉(zhuǎn)換備份文件時是先轉(zhuǎn)為test.txt.1,再轉(zhuǎn)為test.txt.2,因此最后test.txt.2的內(nèi)容是"DEBUG - output1",而test.txt.1是"DEBUG - output2",這點千萬別弄混了。 另外,RollingFileAppender還提供了兩個方法: (1)setMaxFileSize,功能與setMaximumFileSize一樣,但參數(shù)是字符串,有兩種情況:一是僅由數(shù)字組成,默認(rèn)單位為字節(jié),例如"100",即表示限制文件大小為100字節(jié);一是由數(shù)字及存儲單位組成,例如"1KB"、"1MB"、"1GB",其中單位不區(qū)分大小寫,分別表示限制文件大小為1K、1M、1G。 (2)rollOver,手動將目標(biāo)文件轉(zhuǎn)換為備份文件,使用起來較靈活,適用于復(fù)雜情況。 示例代碼: SimpleLayout layout = new SimpleLayout(); RollingFileAppender appender = null; try { appender = new RollingFileAppender(layout, "test.txt"); } catch(Exception e) {} appender.setMaxBackupIndex(2);
Logger log = Logger.getLogger(Test.class); log.addAppender(appender); log.debug("output0"); appender.rollOver(); log.debug("output1"); appender.rollOver(); log.debug("output2"); appender.rollOver(); 這里沒限制目標(biāo)文件大小,但程序運行后,效果與上例相同。 5.org.apache.log4j.DailyRollingFileAppender,也繼承了FileAppender,并且也是向文件輸出信息,但會根據(jù)設(shè)定的時間頻率生成備份文件。 時間頻率格式簡介: '.'yyyy-MM,按月生成,生成時間為每月最后一天午夜過后,例如test.txt在2005年7月31日午夜過后會被更名為test.txt.2005-07,然后重新生成。 '.'yyyy-ww,按周生成,生成時間為每周六午夜過后,例如test.txt在2005年8月13日午夜過后會被更名為test.txt.2005-33,33表示當(dāng)年第33周。 '.'yyyy-MM-dd,按天生成,生成時間為每天午夜過后,例如2005年8月16日午夜過后,test.txt會被更名為test.txt.2005-08-16。 '.'yyyy-MM-dd-a,也是按天生成,但每天會生成兩次,中午12:00過后一次,午夜過后一次,例如test.txt在2005年8月16 日12:00過后會被更名為test.txt.2005-8-16-上午,午夜過后會被更名為test.txt.2005-8-16-下午。 '.'yyyy-MM-dd-HH,按小時生成,例如test.txt在2005年8月16日12:00過后會被更名為test.txt.2005-8-16-11。 '.'yyyy-MM-dd-HH-mm,按分鐘生成,例如test.txt在2005年8月16日12:00過后會被更名為test.txt.2005-8-16-11-59。 示例代碼: SimpleLayout layout = new SimpleLayout(); DailyRollingFileAppender appender = null; try { appender = new DailyRollingFileAppender(layout, "test.txt", "'.'yyyy-MM-dd-HH-mm"); } catch(Exception e) {} Logger log = Logger.getLogger(Test.class); log.addAppender(appender); log.debug("output"); 編碼完成后運行程序,等一分鐘后再次運行,由于我是在2005年8月17日15:42分第一次運行程序的,因此工程目錄下最終有兩個文件test.txt和test.txt.2005-08-17-15-42。 6.org.apache.log4j.AsyncAppender,用于管理不同類型的Appender,也能實現(xiàn)同時向多個源輸出信息,但其執(zhí)行是異步的。 示例代碼: SimpleLayout layout = new SimpleLayout(); //向控制臺輸出 ConsoleAppender appender1 = null; try { appender1 = new ConsoleAppender(layout); } catch(Exception e) {} //向文件輸出 FileAppender appender2 = null; try { appender2 = new FileAppender(layout, "test.txt"); } catch(Exception e) {} //使用AsyncAppender實現(xiàn)同時向多個目標(biāo)輸出信息 AsyncAppender appender = new AsyncAppender(); appender.addAppender(appender1); appender.addAppender(appender2); Logger log = Logger.getLogger(Test.class); log.addAppender(appender); log.debug("output"); 此外,AsyncAppender和Logger都提供了更多的方法來管理Appender,例如getAppender、 getAllAppenders、removeAppender和removeAllAppenders,分別用來獲取指定的Appender、獲取全部 Appender、移除指定的Appender以及移除全部Appender。 7.org.apache.log4j.jdbc.JDBCAppender,將信息輸出到數(shù)據(jù)庫。 示例代碼: JDBCAppender appender = new JDBCAppender(); appender.setDriver("com.mysql.jdbc.Driver"); appender.setURL("jdbc:mysql://localhost:3306/zheng"); appender.setUser("root"); appender.setPassword("11111111"); appender.setSql("insert into log4j (msg) values ('%m')"); Logger log = Logger.getLogger(Test.class); log.addAppender(appender); log.debug("output"); 這里使用的數(shù)據(jù)庫是MySQL 5.0.4beta,用戶名root,密碼11111111,我在其中建了一個庫zheng,包含表log4j,該表只有一個字段msg,類型為varchar(300)。此外,本例用到的JDBC驅(qū)動可以從http://dev.mysql.com/downloads/connector/j/3.1.html下載,版本3.1.8a,下載mysql-connector-java-3.1.8a.zip文件后解壓縮,需要其中的mysql-connector- java-3.1.8-bin.jar文件。下面再來看代碼。由于JDBCAppender內(nèi)部默認(rèn)使用PatternLayout格式化輸出信息,因此這里沒用到SimpleLayout,而appender.setSql所設(shè)置的SQL語句就是PatternLayout所需的格式化字符串,故此其中才有"%m"這樣的字符,有關(guān)PatternLayout的具體內(nèi)容后邊介紹。執(zhí)行后,表log4j增加一條記錄,內(nèi)容為"output"。 8.org.apache.log4j.nt.NTEventLogAppender,向Windows NT系統(tǒng)日志輸出信息。 示例代碼: SimpleLayout layout = new SimpleLayout(); NTEventLogAppender appender = new NTEventLogAppender("Java", layout);
Logger log = Logger.getLogger(Test.class); log.addAppender(appender); log.debug("output"); 注意,要完成此示例,還需向C:\WINNT\system32文件夾(我的操作系統(tǒng)裝在了C:\)中復(fù)制一個名為 NTEventLogAppender.dll的文件。如果跟我一樣用的是Log4J 1.2.11,實在對不住,Log4J 1.2.11并未提供該文件。雖然logging-log4j-1.2.11.zip文件解壓縮后,其下的src\java\org\apache\ log4j\nt文件夾中有一個make.bat文件執(zhí)行后可以編譯出該文件,但還需要配置,很麻煩。還好,條條大道通羅馬,1.2.11不行,就換 1.2.9,可以從http://apache.justdn.org/logging/log4j/1.2.9下載,下載后解壓縮logging-log4j-1.2.9.zip文件,在其下的src\java\org\apache\log4j\nt文件夾中找到 NTEventLogAppender.dll,復(fù)制過去就可以了。程序執(zhí)行后,打開"事件查看器",選擇"應(yīng)用程序日志",其中有一條來源為Java的記錄,這條記錄就是剛才輸出的信息了。 9.org.apache.log4j.lf5.LF5Appender,執(zhí)行時會彈出一個窗口,信息在該窗口中以表格的形式顯示。 示例代碼: LF5Appender appender = new LF5Appender(); Logger log = Logger.getLogger(Test.class); log.addAppender(appender); log.debug("output"); 由于LF5Appender不需要Layout格式化輸出信息,因此這里沒有設(shè)置。此外LF5Appender還提供了一個setMaxNumberOfRecords方法,用來限制信息在表格中顯示的行數(shù)。 10.org.apache.log4j.net.SocketAppender,以套接字方式向服務(wù)器發(fā)送日志,然后由服務(wù)器將信息輸出。 示例代碼: //指定要連接的服務(wù)器地址及端口,這里使用的是本機9090端口 SocketAppender appender = new SocketAppender("localhost", 9090); Logger log = Logger.getLogger(Test.class); log.addAppender(appender); log.debug("output"); SocketAppender不需要設(shè)置Layout,因為SocketAppender不負責(zé)輸出信息。那么如何看到信息輸出的效果呢?這就需要SocketServer和SimpleSocketServer了。 示例代碼1: package piv.zheng.log4j.test; import org.apache.log4j.net.SocketServer; public class TestServer { public static void main(String[] args) { SocketServer.main(new String[]{"9090", "test.properties", "G:/YPJCCK/Log4J"}); } } 這是SocketServer的示例。SocketServer只有一個靜態(tài)方法main,該方法意味著SocketServer不僅可以在代碼中被調(diào)用,也可以用java命令執(zhí)行。main方法只有一個參數(shù),是個字符串?dāng)?shù)組,但要求必須有三個元素:元素一用來指定端口,本例為9090;元素二用來指定輸出信息時需要的配置文件,該文件放在工程目錄下,本例使用的test.properties內(nèi)容如下: log4j.rootLogger=, console log4j.appender.console =org.apache.log4j.ConsoleAppender log4j.appender.console.layout=org.apache.log4j.SimpleLayout 該配置指定SocketServer使用ConsoleAppender以SimpleLayout格式輸出信息;元素三用來指定一個路徑,以存放.lcf 文件,我指定的是本機的G:/YPJCCK/Log4J文件夾。.lcf文件也是輸出信息時使用的配置文件,格式與元素二所指定的配置文件一樣,但 test.properties是默認(rèn)配置文件,即當(dāng).lcf文件找不到時才使用。那么.lcf文件如何命名呢?其實.lcf文件的名稱并不是隨意起的,當(dāng)SocketAppender與SocketServer建立連接時,SocketServer就會獲得SocketAppender所在計算機的IP 地址與網(wǎng)絡(luò)ID,并將其格式化成"網(wǎng)絡(luò)ID/IP地址"這樣的字符串,然后獲取其中的網(wǎng)絡(luò)ID作為.lcf文件的主名,例如 "zhengyp/127.0.0.1",其中的"zhengyp"就是主文件名,而后再根據(jù)這個文件名來調(diào)用相應(yīng)的.lcf文件。這意味著對不同的計算機可以提供不同的配置文件,使信息輸出時有不同的效果。此外,SocketServer還默認(rèn)了一個名為generic.lcf的文件,用于處理網(wǎng)絡(luò)ID 獲取不到或其他情況,本例是用的就是這個文件,內(nèi)容如下: log4j.rootLogger=, console log4j.appender.console =org.apache.log4j.ConsoleAppender log4j.appender.console.layout=org.apache.log4j.PatternLayout log4j.appender.console.layout.ConversionPattern=%m%n 該配置指定SocketServer使用ConsoleAppender以PatternLayout格式輸出信息。運行程序時請先運行 SocketServer,再運行SocketAppender。SocketAppender運行結(jié)束后,就可以從SocketServer的控制臺看到輸出的信息了。 示例代碼2: package piv.zheng.log4j.test; import org.apache.log4j.net.SimpleSocketServer;
public class TestServer { public static void main(String[] args) { SimpleSocketServer.main(new String[]{"9090", "test.properties"}); } } 這是SimpleSocketServer的示例,與SocketServer相比,只允許指定一個默認(rèn)的配置文件,而無法對不同計算機使用不同的配置文件。 11.org.apache.log4j.net.SocketHubAppender,也是以套接字方式發(fā)送日志,但與SocketAppender相反,SocketHubAppender是服務(wù)器端,而不是客戶端。 示例代碼: //指定服務(wù)器端口,這里使用的是本機9090端口 SocketHubAppender appender = new SocketHubAppender(9090); Logger log = Logger.getLogger(Test.class); log.addAppender(appender); while (true) { Thread.sleep(1000); log.debug("output"); //輸出信息 } 由于SocketHubAppender一旦運行就開始發(fā)送消息,而無論有無接收者,因此這里使用了while語句并將條件設(shè)為true以保證程序持續(xù)運行。不過為了保證性能,這里還使用了Thread.sleep(1000),這樣程序每循環(huán)一次都休眠1秒,如果機器性能不好,還可以將值設(shè)的再大些。此外,由于SocketHubAppender也不負責(zé)輸出信息,因此同樣不需要設(shè)置Layout。那么如何看到信息輸出的效果呢?這里我自己寫了個客戶端程序,代碼如下: package piv.zheng.log4j.test; import java.net.Socket; import java.lang.Thread; import org.apache.log4j.LogManager; import org.apache.log4j.PropertyConfigurator; import org.apache.log4j.net.SocketNode; public class TestClient { public static void main(String[] args) throws Exception { //創(chuàng)建客戶端套接字對象 Socket s = new Socket("localhost", 9090); //調(diào)用配置文件 PropertyConfigurator.configure("test.properties"); //從套接字中恢復(fù)Logger,并輸出信息 new Thread(new SocketNode(s, LogManager.getLoggerRepository())).start(); } } 由于SocketHubAppender與SocketAppender一樣,發(fā)送的也是SocketNode對象,因此編寫該程序時參考了 SocketServer的源碼。此外,這里的配置文件直接使用了上例的test.properties文件。運行程序時請先運行 SocketHubAppender,再運行客戶端程序,然后從客戶端的控制臺就可以看到效果了。 13.org.apache.log4j.net.TelnetAppender,與SocketHubAppender有些類似,也是作為服務(wù)器發(fā)送信息,但TelnetAppender發(fā)送的不是SocketNode對象,而是Category輸出的結(jié)果。 示例代碼: SimpleLayout layout = new SimpleLayout(); TelnetAppender appender = new TelnetAppender(); appender.setLayout(layout); //設(shè)置Layout appender.setPort(9090); //設(shè)置端口號 appender.activateOptions(); //應(yīng)用設(shè)置 Logger log = Logger.getLogger(Test.class); log.addAppender(appender); while (true) { java.lang.Thread.sleep(1000); log.debug("output"); //輸出信息 } //appender.close(); 注意最后一行被注釋掉的代碼,若該行代碼執(zhí)行,則TelnetAppender的資源會被清理,從而導(dǎo)致TelnetAppender無法繼續(xù)運行。那么如何看到信息輸出的效果呢?這里提供兩種方法:方法一,使用Telnet工具,我使用的就是Windows自帶的Telnet。運行 TelnetAppender程序后,點擊[開始]菜單->[運行],在"運行"框中輸入"telnet",回車,telnet客戶端彈出,這是一個命令行程序,輸入命令"open localhost 9090",回車,然后就可以看到效果了。方法二,自己寫程序,代碼如下: package piv.zheng.log4j.test; import java.net.*; import java.io.*; public class TestClient { public static void main(String[] args) throws Exception { //創(chuàng)建客戶端套接字對象 Socket s = new Socket("localhost", 9090); //將BufferedReader與Socket綁定,以輸出Socket獲得的信息 BufferedReader in = new BufferedReader(new InputStreamReader(s.getInputStream())); //獲得信息并輸出 String line = in.readLine(); while (line != null) { System.out.println(line); line = in.readLine(); } } } 13.org.apache.log4j.net.SMTPAppender,向指定的電子郵件發(fā)送信息,但只能發(fā)送ERROR和FATAL級別的信息,而且還沒提供身份驗證功能。 示例代碼: SimpleLayout loyout = new SimpleLayout(); SMTPAppender appender = new SMTPAppender(); appender.setLayout(loyout); //設(shè)置Layout appender.setFrom("zhengyp@126.com"); //設(shè)置發(fā)件人 appender.setSMTPHost("smtp.126.com"); //設(shè)置發(fā)送郵件服務(wù)器 appender.setTo("zhengyp@126.com"); //設(shè)置收件人 appender.setSubject("Log4J Test"); //設(shè)置郵件標(biāo)題 appender.activateOptions(); //應(yīng)用設(shè)置 Logger log = Logger.getLogger(Test.class); log.addAppender(appender); log.debug("Here is DEBUG"); log.info("Here is INFO"); log.warn("Here is WARN"); log.error("Here is ERROR"); log.fatal("Here is FATAL"); 要運行此示例,還需要JavaMail 和JAF,前者是Sun推出的電子郵件類庫,可以從http://java.sun.com/products/javamail/downloads/index.html下載,最新版本1.3.3,下載javamail-1_3_3-ea.zip壓縮包后需要其中的mail.jar文件;后者全稱是JavaBeans Activation Framework,提供了對輸入任意數(shù)據(jù)塊的支持,并能相應(yīng)地對其進行處理,可以從http://www.sun.com/download中找到,最新版本1.1,下載jaf-1_1-ea.zip壓縮包后需要其中的activation.jar文件。不過,程序運行后會拋出兩次異常,分別是log.error和log.fatal方法導(dǎo)致的,失敗的原因很簡單,我用的郵件服務(wù)器需要身份驗證。 14.piv.zheng.log4j.test.SMTPAppender,自定義的,依照Log4J提供的SMTPAppender修改而來,增加了身份驗證功能,并去掉了對級別的限制。由于代碼太長,所以放到了另一篇文章《自定義SMTPAppender的源碼》中,有興趣的請自行去查看。 示例代碼: SimpleLayout layout = new SimpleLayout(); SMTPAppender appender = new SMTPAppender(layout); appender.setFrom("zhengyp@126.com"); //發(fā)件人 appender.setSMTPHost("smtp.126.com"); //發(fā)送郵件服務(wù)器 appender.setTo("zhengyp@126.com"); //收件人 appender.setSubject("Log4J Test"); //郵件標(biāo)題 appender.setAuth("true"); //身份驗證標(biāo)識 appender.setUsername("zhengyp"); //用戶名 appender.setPassword("1111111"); //密碼 appender.activateOptions(); //應(yīng)用設(shè)置 Logger log = Logger.getLogger(Test.class); log.addAppender(appender); log.debug("output"); 同樣需要JavaMail 和JAF。程序運行后會發(fā)送一封郵件,快去查看一下自己的郵箱吧^_^ 此外,Log4J還提供了SyslogAppender、JMSAppender(均在org.apache.log4j.net包下)以及更多的 Appender,或者用來向Unix操作系統(tǒng)的syslogd服務(wù)發(fā)送信息,或者通過JMS方式發(fā)送信息,或者以其他方式發(fā)送信息。由于條件有現(xiàn),就不再介紹了。 不過,在前邊的示例中還使用了SimpleLayout和PatternLayout來格式化輸出的信息,這里也簡單介紹一下。 1.org.apache.log4j.SimpleLayout,一直用的就是它,輸出的格式比較簡單,就是"級別 - 信息"。 2.org.apache.log4j.HTMLLayout,以HTML格式輸出信息。 示例代碼: HTMLLayout layout = new HTMLLayout(); layout.setTitle("Log4J Test"); //HTML頁標(biāo)題 FileAppender appender = null; try { appender = new FileAppender(layout, "test.html"); } catch(Exception e) {} Logger log = Logger.getLogger(Test.class); log.addAppender(appender); log.debug("output"); 程序運行后會在工程目錄下生成一個HTML頁,可以用瀏覽器來查看。 3.org.apache.log4j.xml.XMLLayout,以XML格式輸出信息。 示例代碼: XMLLayout layout = new XMLLayout(); FileAppender appender = null; try { appender = new FileAppender(layout, "test.xml"); } catch(Exception e) {} Logger log = Logger.getLogger(Test.class); log.addAppender(appender); log.debug("output"); 程序運行后會在工程目錄下生成一個test.xml文件。 4.org.apache.log4j.TTCCLayout,輸出信息的同時輸出日志產(chǎn)生時間、相關(guān)線程及Category等信息。 示例代碼: TTCCLayout layout = new TTCCLayout(); //是否打印與TTCCLayout關(guān)聯(lián)的Category的名稱,默認(rèn)為true,表示打印 layout.setCategoryPrefixing(true); //是否打印當(dāng)前線程,默認(rèn)為true,表示打印 layout.setThreadPrinting(true); //是否打印輸出和當(dāng)前線程相關(guān)的NDC信息,默認(rèn)為true,表示打印 layout.setContextPrinting(true); //設(shè)置日期時間格式 layout.setDateFormat("iso8601"); //設(shè)置時區(qū) layout.setTimeZone("GMT+8:00"); //設(shè)置時區(qū)后需要調(diào)用此方法應(yīng)用設(shè)置 layout.activateOptions(); ConsoleAppender appender = new ConsoleAppender(layout); Logger log = Logger.getLogger(Test.class); log.addAppender(appender); log.debug("output"); 注意,TTCCLayout輸出的時間格式及時區(qū)是可以設(shè)置的: (1)setDateFormat,設(shè)置日期時間格式,有五個常用值:"NULL",表示不輸出;"RELATIVE",輸出信息所用的時間,以毫秒為單位,默認(rèn)使用該值;"ABSOLUTE",僅輸出時間部分;"DATE",按當(dāng)前所在地區(qū)顯示日期和時間;"ISO8601",按ISO8601標(biāo)準(zhǔn)顯示日期和時間。這些字符串不區(qū)分大小寫。此外,還可以使用時間模式字符來格式化日期時間,詳細內(nèi)容請參考J2SE文檔中的 java.text.SimpleDateFormat類。 (2)setTimeZone,設(shè)置時區(qū),詳細內(nèi)容請參考J2SE文檔中的java.util.TimeZone類和java.util.SimpleTimeZone類。但請注意,當(dāng)日期格式為"RELATIVE"時,設(shè)置時區(qū)會造成沖突。 5.org.apache.log4j.PatternLayout,用模式字符靈活指定信息輸出的格式。 示例代碼: String pattern = "Logger: %c %n" + "Date: %d{DATE} %n" + "Message: %m %n"; PatternLayout layout = new PatternLayout(pattern); ConsoleAppender appender = new ConsoleAppender(layout); Logger log = Logger.getLogger(Test.class); log.addAppender(appender); log.debug("output"); 模式字符串簡介: %c:Category名稱。還可以使用%c{n}的格式輸出Category的部分名稱,其中n為正整數(shù),輸出時會從Category名稱的右側(cè)起查 n個".",然后截取第n個"."右側(cè)的部分輸出,例如Category的名稱為"x.y.z",指定格式為"%c{2}",則輸出"y.z"。 %C:輸出信息時Category所在類的名稱,也可以使用%C{n}的格式輸出。 %d:輸出信息的時間,也可以用%d{FormatString}的格式輸出,其中FormatString的值請參考TTCCLayout的setDateFormat方法,但NULL和RELATIVE在%d中無法使用。 %F:輸出信息時Category所在類文件的名稱。 %l:輸出信息時Category所在的位置,使用"%C.%M(%F:%L)"可以產(chǎn)生同樣的效果。 %L:輸出信息時Category在類文件中的行號。 %m:信息本身。 %M:輸出信息時Category所在的方法。 %n:換行符,可以理解成回車。 %p:日志級別。 %r:輸出信息所用的時間,以毫秒為單位。 %t:當(dāng)前線程。 %x:輸出和當(dāng)前線程相關(guān)的NDC信息。 %X:輸出與當(dāng)前現(xiàn)成相關(guān)的MDC信息。 %%:輸出%。 此外,還可以在%與模式字符之間加上修飾符來設(shè)置輸出時的最小寬度、最大寬度及文本對齊方式,例如: %30d{DATE}:按當(dāng)前所在地區(qū)顯示日期和時間,并指定最小寬度為30,當(dāng)輸出信息少于30個字符時會補以空格并右對齊。 %-30d{DATE}:也是按當(dāng)前所在地區(qū)顯示日期和時間,指定最小寬度為30,并在字符少于30時補以空格,但由于使用了"-",因此對齊方式為左對齊,與默認(rèn)情況一樣。 %.40d{DATE}:也是按當(dāng)前所在地區(qū)顯示日期和時間,但指定最大寬度為40,當(dāng)輸出信息多于40個字符時會將左邊多出的字符截掉。此外,最大寬度只支持默認(rèn)的左對齊方式,而不支持右對齊。 %30.40d{DATE}:如果輸出信息少于30個字符就補空格并右對齊,如果多于40個字符,就將左邊多出的字符截掉。 %-30.40d{DATE}:如果輸出信息少于30個字符就補空格并左對齊,如果多于40個字符,就將左邊多出的字符截掉。 五、Log4J進階 了解以上內(nèi)容后,就已經(jīng)初步掌握Log4J了,但要想靈活使用Log4J,則還需要了解其配置功能。這里簡單介紹一下。 1.org.apache.log4j.BasicConfigurator,默認(rèn)使用ConsoleAppender以PatternLayout (使用PatternLayout.TTCC_CONVERSION_PATTERN,即"%r [%t] %p %c %x - %m%n"格式)輸出信息。 示例代碼: BasicConfigurator.configure(); Logger log = Logger.getLogger(Test.class); log.debug("output"); 注意,BasicConfigurator及其它Configurator其實都只對根Category進行配置,但由于用戶創(chuàng)建的Category會繼承根Category的特性(聲明,許多資料介紹Category繼承關(guān)系時都主要在討論輸出級別,而事實上,Category間繼承的不僅是輸出級別,所有特性都可以繼承),因此輸出時仍會顯示BasicConfigurator配置的效果。此外,還可以使用configure方法指定Appender,以自定義輸出。BasicConfigurator允許同時指定多個Appender。 示例代碼: SimpleLayout layout1 = new SimpleLayout(); ConsoleAppender appender1 = new ConsoleAppender(layout1); BasicConfigurator.configure(appender1); String pattern = "Logger: %c %n" + "Date: %d{DATE} %n" + "Message: %m %n"; PatternLayout layout2 = new PatternLayout(pattern); FileAppender appender2 = null; try { appender2 = new FileAppender(layout2, "test.log", false); } catch(Exception e){} BasicConfigurator.configure(appender2); Logger log = Logger.getLogger(Test.class); log.debug("output"); 這里用BasicConfigurator指定了兩個Appender,即ConsoleAppender和FileAppender,程序運行后信息會在以SimpleLayout輸出到控制臺的同時以PatternLayout輸出到test.log文件。若要清除這些Appender,可以調(diào)用 BasicConfigurator的resetConfiguration方法。 2. org.apache.log4j.PropertyConfigurator,調(diào)用文本配置文件輸出信息,通常使用.properties文件。配置文件以"鍵=值"的形式保存數(shù)據(jù),注釋以"#"開頭。PropertyConfigurator和配置文件在介紹SocketAppender和 SocketHubAppender時曾提到過。使用PropertyConfigurator可以避免硬編碼。 示例代碼: PropertyConfigurator.configure("test.properties"); Logger log = Logger.getLogger(Test.class); log.debug("output"); 要完成該示例,還需要在工程目錄下創(chuàng)建一個test.properties文件,內(nèi)容如下: ##設(shè)置根Category,其值由輸出級別和指定的Appender兩部分組成 #這里設(shè)置輸出級別為DEBUG log4j.rootLogger=DEBUG,appender ##輸出信息到控制臺 #創(chuàng)建一個名為appender的Appender,類型為ConsoleAppender log4j.appender.appender=org.apache.log4j.ConsoleAppender #設(shè)置appender以SimpleLayout輸出 log4j.appender.appender.layout=org.apache.log4j.SimpleLayout 此外,PropertyConfigurator也允許同時指定多個Appender,例如: #這里沒有設(shè)置輸出級別,但指定了兩個Appender log4j.rootLogger=,appender1,appender2 #輸出信息到控制臺 log4j.appender.appender1=org.apache.log4j.ConsoleAppender log4j.appender.appender1.layout=org.apache.log4j.SimpleLayout #輸出信息到文件 log4j.appender.appender2=org.apache.log4j.FileAppender log4j.appender.appender2.File=test.log log4j.appender.appender2.Append=false log4j.appender.appender2.layout=org.apache.log4j.PatternLayout log4j.appender.appender2.layout.ConversionPattern=Logger: %c %nDate: %d{DATE} %nMessage: %m %n 關(guān)于更多配置,網(wǎng)上示例很多,這里不再贅述。但要說明一件事,就是配置文件中的鍵是怎么來的。參照后一個示例,查看 PropertyConfigurator源碼,會發(fā)現(xiàn)"log4j.rootLogger"是定義好的,只能照寫;而"log4j.appender" 字樣也可以找到,與指定的Appender名稱appender1、appender2聯(lián)系起來,log4j.appender.appender1和 log4j.appender.appender2也就不難理解了;再看下去,還能找到"prefix + ".layout"",這樣log4j.appender.appender1.layout也有了;可是 log4j.appender.appender2.File 和log4j.appender.appender2.Append呢?還記得前邊介紹FileAppender時曾提到的setAppend方法嗎?其實FileAppender還有個getAppend方法,這說明FileAppender具有Append屬性。那么File呢?當(dāng)然也是 FileAppender的屬性了。至于log4j.appender.appender2.layout.ConversionPattern也一樣,只不過FileAppender換成了PatternLayout。其實別的Appender和Layout的屬性也都是這樣定義成鍵來進行設(shè)置的。此外,定義鍵時,屬性的首字母不區(qū)分大小寫,例如"File",也可以寫成"file"。 3. org.apache.log4j.xml.DOMConfigurator,調(diào)用XML配置文件輸出信息。其定義文檔是log4j- 1.2.11.jar中org\apache\log4j\xml包下的log4j.dtd文件。與PropertyConfigurator相比, DOMConfigurator似乎是趨勢。 示例代碼: DOMConfigurator.configure("test.xml"); Logger log = Logger.getLogger(Test.class); log.debug("output"); 要完成該示例,也需要在工程目錄下創(chuàng)建一個test.xml文件,內(nèi)容如下: <?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE log4j:configuration SYSTEM "log4j.dtd"> <log4j:configuration xmlns:log4j="http://jakarta.apache.org/log4j/"> <!-- 輸出信息到控制臺 創(chuàng)建一個名為appender的Appender,類型為ConsoleAppender --> <appender name="appender" class="org.apache.log4j.ConsoleAppender"> <!-- 設(shè)置appender以SimpleLayout輸出 --> <layout class="org.apache.log4j.SimpleLayout"/> </appender> <!-- 設(shè)置根Category,其值由輸出級別和指定的Appender兩部分組成 這里設(shè)置輸出級別為DEBUG --> <root> <priority value ="debug" /> <appender-ref ref="appender"/> </root> </log4j:configuration> 此外,DOMConfigurator也允許同時指定多個Appender,例如: <?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE log4j:configuration SYSTEM "log4j.dtd"> <log4j:configuration xmlns:log4j="http://jakarta.apache.org/log4j/"> <!-- 輸出信息到控制臺 --> <appender name="appender1" class="org.apache.log4j.ConsoleAppender"> <layout class="org.apache.log4j.SimpleLayout"/> </appender> <!-- 輸出信息到文件 --> <appender name="appender2" class="org.apache.log4j.FileAppender"> <param name="File" value="test.log"/> <param name="Append" value="false"/> <layout class="org.apache.log4j.PatternLayout"> <param name="ConversionPattern" value="Logger: %c %nDate: %d{DATE} %nMessage: %m %n"/> </layout> </appender> <!-- 這里沒有設(shè)置輸出級別,但指定了兩個Appender --> <root> <appender-ref ref="appender1"/> <appender-ref ref="appender2"/> </root> </log4j:configuration> 由于以上兩個示例是在PropertyConfigurator的兩個示例基礎(chǔ)上改的,而且也寫了注釋,因此這里只簡單介紹一下<param> 標(biāo)記。<param>標(biāo)記有兩個屬性,name和value,前者的值也是Appender或Layout的屬性名,作用與 log4j.appender.appender2.File這樣的鍵一樣。設(shè)置時,首字母同樣不區(qū)分大小寫,例如"File"也可以寫成"file"。此外還請注意,使用這兩段XML代碼時應(yīng)將中文注釋去掉,或者把<?xml version="1.0" encoding="UTF-8" ?>中的UTF-8改成GBK或GB2312,否則會導(dǎo)致錯誤。這里使用的UTF-8是XML默認(rèn)的字符集。 4. org.apache.log4j.lf5.DefaultLF5Configurator,默認(rèn)使用LF5Appender來輸出信息,需要調(diào)用 log4j-1.2.11.jar中org\apache\log4j\lf5\config包下的defaultconfig.properties文件。 示例代碼: try { DefaultLF5Configurator.configure(); } catch(Exception e){} Logger log = Logger.getLogger(Test.class); log.debug("output"); 下面討論另外一個話題:Diagnostic Context。Diagnostic Context意為診斷環(huán)境,針對于多用戶并發(fā)環(huán)境,在這種環(huán)境下,通常需要對每個客戶端提供獨立的線程以處理其請求,此時若要在日志信息中對客戶端加以區(qū)分,為每個線程分別創(chuàng)建Category是個辦法。但這樣做并不高效,反而會導(dǎo)致大量資源被占用。Diagnostic Context所要解決的就是這個問題。Diagnostic Context會為當(dāng)前線程提供一定空間,然后將信息保存到該空間供Category調(diào)用。與創(chuàng)建一個Category相比,這點信息所占的資源自然要少得多。 1.org.apache.log4j.NDC。NDC是Nested Diagnostic Context的簡寫,意為嵌套診斷環(huán)境,使用時提供一個堆棧對象來保存信息。堆棧的特點是數(shù)據(jù)后進先出、先進后出,即清理堆棧時,后保存的數(shù)據(jù)會被先清掉,而先保存的數(shù)據(jù)則被后清掉。 示例代碼: PatternLayout layout = new PatternLayout("%m %x%n"); ConsoleAppender appender = new ConsoleAppender(layout); Logger log = Logger.getLogger(Test.class); log.addAppender(appender); String tmp = "zhengyp"; //模擬從客戶端獲取的信息 log.debug("Start"); NDC.push(tmp); //添加信息到堆棧中 log.debug("Before"); NDC.pop(); //將信息從堆棧中移除 log.debug("After"); NDC.remove(); //將當(dāng)前線程移除,退出NDC環(huán)境 log.debug("End"); 這里使用了PatternLayout來格式化信息,其模式字符%x就是用來輸出NDC信息的。程序運行后會輸出如下內(nèi)容: Start Before zhengyp After End 可以看到,第二行輸出時由于已向堆棧中添加了信息,因此"zhengyp"也會同時輸出;而第三行輸出時由于信息已被移除,因此就沒再輸出"zhengyp"。不過這個示例僅簡單演示了NDC的用法,而沒有顯示出NDC的堆棧特性,所以下面再提供一個示例,代碼如下: TTCCLayout layout = new TTCCLayout(); ConsoleAppender appender = new ConsoleAppender(layout); Logger log = Logger.getLogger(Test.class); log.addAppender(appender); log.debug("Start"); NDC.push("zhengyp"); //添加信息到堆棧中 log.debug("Test1"); NDC.push("192.168.0.1"); //向堆棧中追加信息 log.debug("Test2"); NDC.pop(); //從堆棧中移除信息,但移除的只是最后的信息 log.debug("Test3"); NDC.pop(); //再次從堆棧中移除信息 log.debug("Test4");??? log.debug("End"); 這里格式化輸出信息使用的是TTCCLayout,還記得其setContextPrinting方法嗎?程序運行后,從輸出的信息就可以看到效果了。此外,NDC還提供了其他方法: (1)get,獲取堆棧中的全部信息。以上例為例,當(dāng)輸出Test2時,使用該方法會獲得"zhengyp 192.168.0.1"。 (2)peek,獲取堆棧中最后的信息。仍以上例為例,當(dāng)輸出Test1時會獲得"zhengyp",Test2時為"192.168.0.1",而當(dāng)輸出Test3時由于"192.168.0.1"已被移除,"zhengyp"又成了最后的信息,因此獲得的仍是"zhengyp"。 (3)clear,清空堆棧中的全部信息。 (4)setMaxDepth,設(shè)置堆棧的最大深度,即當(dāng)前的信息可以保留多少,對之后追加的信息沒有影響。當(dāng)需要一次清掉多條信息時,使用setMaxDepth會比多次調(diào)用pop方便。 2.org.apache.log4j.MDC。MDC是Mapped Diagnostic Context的簡寫,意為映射診斷環(huán)境,提供了一個Map對象來保存信息。Map對象使用Key、Value的形式保存值。 示例代碼: PatternLayout layout = new PatternLayout("%m %X{name} %X{ip}%n"); ConsoleAppender appender = new ConsoleAppender(layout); Logger log = Logger.getLogger(Test.class); log.addAppender(appender); log.debug("Start"); //添加信息到Map中 MDC.put("name", "zhengyp1"); MDC.put("ip", "192.168.1.1"); log.debug("Test1"); //添加信息到Map中,若Key重復(fù),則覆蓋之前的值 MDC.put("name", "zhengyp2"); MDC.put("ip", "192.168.1.2"); log.debug("Test2"); //將信息從Map中移除,此時信息不再輸出 MDC.remove("name"); MDC.remove("ip"); log.debug("End"); 這個示例演示了MDC的基本用法,格式化信息用的也是PatternLayout,模式字符為"%X",其格式必須為"%X{Key}"。其中Key就是向 Map對象添加信息時put方法所用的Key,這里為name和ip。由于可以使用"%X{Key}"輸出信息,因此MDC使用起來會比NDC更靈活。此外,MDC還提供了get方法來獲取指定Key的信息。 六、小結(jié) 用了近半個月,終于大概掌握了Log4J。由于本文是邊學(xué)邊寫的,目的是將Log4J的用法記錄下來,而非提供一份中文參考,因此內(nèi)容并不細致,但盡量提供了示例。不過到最后才發(fā)現(xiàn),示例存在問題,其實Logger做為類的static成員比較恰當(dāng),而我為了圖方便,竟直接寫到了main方法中,這一點還請注意。 此外,這里再推薦一下《The Complete log4j Manual》,是對Log4J較詳細的介紹,在網(wǎng)上可以找到,只不過是英文的。
|