二 動(dòng)態(tài)配置log4j
1?配置外部配置文件來(lái)配置的基本步驟
1.1 一個(gè)運(yùn)用配置文件的實(shí)例
Log4j之所以能成功的原因之一是它的靈活性。但如果只是簡(jiǎn)單的調(diào)用BasicConfigurator.configure()來(lái)進(jìn)行配置工作,那么所有的配置都是在函數(shù)中寫死的,以后修改配置就要修改原代碼,這就不能體現(xiàn)出log4j的靈活性了,所以基本上不會(huì)通過(guò)BasicConfigurator.configure()來(lái)進(jìn)行配置工作的。
為了增加軟件的靈活性,最常用的做法就是使用配置文件,如web.xml之于J2EE,struts-config.xml之于struts一樣,log4j也提供了讓我們把配置信息從程序轉(zhuǎn)移到配置文件中的方法。Log4j提供了兩種方式的配置文件:XML文件和Java的property配置文件。通過(guò)把配置信息轉(zhuǎn)移到外部文件中,當(dāng)我們要修改配置信息時(shí),就可以直接修改配置文件而不用去修改代碼了,下面,我們就來(lái)完成一個(gè)通過(guò)配置文件來(lái)實(shí)現(xiàn)log4j的實(shí)例。
例2-a:
package?TestLog4j;
import?org.apache.log4j.Logger;
import?org.apache.log4j.BasicConfigurator;
import?org.apache.log4j.PropertyConfigurator;
import?org.apache.log4j.Priority;?public?class?TestLog4j?
{
static?Logger?logger?=?Logger.getLogger(TestLog4j.class.getName());
public?TestLog4j(){}
public?static?void?main(String[]?args)
{
//通過(guò)BasicConfigurator類來(lái)初始化
//BasicConfigurator.configure();
//(1)通過(guò)配置文件來(lái)初始化
PropertyConfigurator.configure("F:\\nepalon\\log4j.properties");
logger.debug("Start?of?the?main()?in?TestLog4j"); //代碼(2)
logger.info("Just?testing?a?log?message?with?priority?set?to?INFO");
logger.warn("Just?testing?a?log?message?with?priority?set?to?WARN");
logger.error("Just?testing?a?log?message?with?priority?set?to?ERROR");
logger.fatal("Just?testing?a?log?message?with?priority?set?to?FATAL");
logger.log(Priority.WARN,?"Testing?a?log?message?use?a?alternate?form");
logger.debug(TestLog4j.class.getName()); //代碼(2)
}
}
在這個(gè)例子中,我們用PropertyConfigurator.configure("F:\\nepalon\\log4j.properties")代替BasicConfigurator.configure()進(jìn)行配置。PropertyConfigurator.configure()函數(shù)的參數(shù)可以是一個(gè)properties文件所在路徑的String對(duì)象,可以是一個(gè)properties文件所在路徑的URL對(duì)象,也可以是一個(gè)properties對(duì)象。通過(guò)PropertyConfigurator.configure()可以通過(guò)指定的properties文件來(lái)配置信息。如果要用XML文件進(jìn)行信息配置,可以在代碼中調(diào)用DOMConfigurator()函數(shù)來(lái)進(jìn)行配置工作。在這里,我們只以properties文件來(lái)完成例子。接著,我們來(lái)看一下log4j.properties文件中都有些什么東西:
例2-b:
log4j.rootLogger?=?DEBUG,?A1
log4j.appender.A1?=?org.apache.log4j.ConsoleAppender
log4j.appender.A1.layout?=?org.apache.log4j.PatternLayout
log4j.appender.A1.layout.ConversionPattern?=?%-4r?[%t]?%-5p?%c?%x?-?%m%n
運(yùn)行這個(gè)實(shí)例,運(yùn)行結(jié)果為
0?[main]?DEBUG?TestLog4j.TestLog4j?-?Start?of?the?main()?in?TestLog4j
20?[main]?INFO?TestLog4j.TestLog4j?-?Just?testing?a?log?message?with?priority?set?to?INFO
20?[main]?WARN?TestLog4j.TestLog4j?-?Just?testing?a?log?message?with?priority?set?to?WARN
20?[main]?ERROR?TestLog4j.TestLog4j?-?Just?testing?a?log?message?with?priority?set?to?ERROR
20?[main]?FATAL?TestLog4j.TestLog4j?-?Just?testing?a?log?message?with?priority?set?to?FATAL
180?[main]?WARN?TestLog4j.TestLog4j?-?Testing?a?log?message?use?a?alternate?form
180?[main]?DEBUG?TestLog4j.TestLog4j?-?TestLog4j.TestLog4j
下面,我們分析一下這個(gè)配置文件。
1)?由于每一個(gè)Logger對(duì)旬都有一個(gè)級(jí)別,文件的第一行就是定義了一個(gè)Logger及其級(jí)別。在這里定義了一個(gè)根記錄器(root?logger),這涉及到記錄器的層次問(wèn)題,在些暫時(shí)不深入討論,在后面的章節(jié)再進(jìn)行討論。
2)?第二行定義了一個(gè)名為A1的輸出流,這個(gè)流就是控制臺(tái),所以通過(guò)Logger對(duì)象打印的信息會(huì)在控制臺(tái)輸出。
3)?第三行定義了打印信息的布局。在這里我們用PatternLayout作為此記錄器的布局,PatternLayout允許你以靈活的格式來(lái)打印信息。
4)?第四行指定的打印信息的具體格式,從結(jié)果可知,這個(gè)實(shí)例的打印格式為:當(dāng)前打印語(yǔ)句所使用的時(shí)間?[日志所在的線程]?打印的級(jí)別?當(dāng)前日志所在的類的全名?日志信息。
現(xiàn)在我們來(lái)修改一下這個(gè)記錄器的級(jí)別,把第一行的DEBUG改為INFO,再運(yùn)行程序,結(jié)果將變?yōu)椋?br />0?[main]?INFO?TestLog4j.TestLog4j?-?Just?testing?a?log?message?with?priority?set?to?INFO
10?[main]?WARN?TestLog4j.TestLog4j?-?Just?testing?a?log?message?with?priority?set?to?WARN
10?[main]?ERROR?TestLog4j.TestLog4j?-?Just?testing?a?log?message?with?priority?set?to?ERROR
10?[main]?FATAL?TestLog4j.TestLog4j?-?Just?testing?a?log?message?with?priority?set?to?FATAL
10?[main]?WARN?TestLog4j.TestLog4j?-?Testing?a?log?message?use?a?alternate?form
由于這個(gè)Logger的級(jí)別變?yōu)镮NFO,而代碼(2)是調(diào)用debug()函數(shù)來(lái)輸出日志信息時(shí)只能當(dāng)記錄器級(jí)別為DEBUG時(shí)才輸出信息,所以代碼(2)將不輸出信息。
1.2 實(shí)例原理
1.2.1 初始化配置信息
如果要通過(guò)JAVA的properties文件來(lái)配置信息,那么在代碼中就要通過(guò)PropertyConfigurator.configure()函數(shù)從properties文件中加載配置信息,這個(gè)函數(shù)有三種參數(shù)形式:一個(gè)properties文件所在路徑的String對(duì)象,可以是一個(gè)properties文件所在路徑的URL對(duì)象,也可以是一個(gè)properties對(duì)象。如果要用XML文件來(lái)配置信息,則可用類型的
DOMConfigurator()函數(shù)來(lái)從一個(gè)XML文件中加載配置信息。
1.2.2 輸出端Appender
在上面的例子中,我們都是簡(jiǎn)單的把日志信息輸出到控制臺(tái)中。其實(shí)在log4j中還可以把日志信息輸出到其它的輸出端,對(duì)于同一個(gè)日志信息,我們還可以讓它同時(shí)輸出到多個(gè)輸出端中,如同時(shí)在控制臺(tái)和文件中進(jìn)行打印。一個(gè)輸出端就是一個(gè)appender。要在配置文件中定義一個(gè)appender有三步:
1)?在定義一個(gè)記錄器的同時(shí)定義出該記錄器的輸出端appender。在例2的配置文件的第一句log4j.rootLogger?=?DEBUG,?A1中,我們定義了一個(gè)根記錄器,它的級(jí)別為DEBUG,它有一個(gè)appender名為A1。定義根記錄器的格式為log4j.rootLogger?=?[?level?],?appendName1,?appendName2,?...appendNameN。同一個(gè)記錄器可有多個(gè)輸出端。
2)?定義appender的輸出目的地。定義一個(gè)appender的輸出目的地的格式為log4j.appender.appenderName?=?fully.qualified.name.of.appender.class。log4j提供了以下幾種常用的輸出目的地:
??org.apache.log4j.ConsoleAppender,將日志信息輸出到控制臺(tái)
??org.apache.log4j.FileAppender,將日志信息輸出到一個(gè)文件
??org.apache.log4j.DailyRollingFileAppender,將日志信息輸出到一個(gè),并且每天輸出到一個(gè)新的日志文件
??org.apache.log4j.RollingFileAppender,將日志信息輸出到一個(gè)文件,通過(guò)指定文件的的尺寸,當(dāng)文件大小到達(dá)指定尺寸的時(shí)候會(huì)自動(dòng)把文件改名,如名為example.log的文件會(huì)改名為example.log.1,同時(shí)產(chǎn)生一個(gè)新的example.log文件。如果新的文件再次達(dá)到指定尺寸,又會(huì)自動(dòng)把文件改名為example.log.2,同時(shí)產(chǎn)生一個(gè)example.log文件。依此類推,直到example.log.?MaxBackupIndex,MaxBackupIndex的值可在配置文件中定義。
??org.apache.log4j.WriterAppender,將日志信息以流格式發(fā)送到任意指定的地方。
??org.apache.log4j.jdbc.JDBCAppender,通過(guò)JDBC把日志信息輸出到數(shù)據(jù)庫(kù)中。
在例2中,log4j.appender.A1?=?org.apache.log4j.ConsoleAppender定義了名為A1的appender的輸出目的地為控制臺(tái),所以日志信息將輸出到控制臺(tái)。
3)?定義與所選的輸出目的地相關(guān)的參數(shù),定義格式為:
log4j.appender.appenderName.optionName1?=?value1
......
log4j.appender.appenderName.optionNameN?=?valueN
其中一個(gè)最常用的參數(shù)layout將在下面介紹。
1.2.3 輸出格式(布局)layout
通過(guò)appender可以控制輸出的目的地,而如果要控制輸出的格式,就可通過(guò)log4j的layout組件來(lái)實(shí)現(xiàn)。通過(guò)配置文件定義一個(gè)appender的輸出格式,也通常需要兩個(gè)步驟:
1)?定義appender的布局模式。定義一個(gè)appender的布局模式的格式為log4j.appender.appenderName.layout?=?fully.qualified.name.of.layout.class。Log4j提供的布局模式有以下幾種:
??org.apache.log4j.HTMLLayout,以HTML表格形式布局
??org.apache.log4j.PatternLayout,可以靈活地指定布局模式
??org.apache.log4j.SimpleLayout,包含日志信息的級(jí)別和信息字符串
在例2 中l(wèi)og4j.appender.A1.layout?=?org.apache.log4j.PatternLayout定義了名為A1的appender的布局模式為PatternLayout。
2)?定義與所選的布局模式相關(guān)的設(shè)置信息,定義格式為:
log4j.appender.appenderName.layout.optionName1?=?value1
......
log4j.appender.appenderName.layout.optionNameN?=?valueN
選擇了不同的布局模式可能會(huì)有不同的設(shè)置信息。實(shí)例2所選的布局模式PatternLayout的一個(gè)PatternLayout為ConversionPattern?,通過(guò)定義這個(gè)PatternLayout的值,我們可以指定輸出信息的輸出格式。在例2的配置文件中的定義如下log4j.appender.A1.layout.ConversionPattern?=?%-4r?[%t]?%-5p?%c?%x?-?%m%n。在下面,我們將介紹布局模式PatternLayout的參數(shù)ConversionPattern的各個(gè)值代表的含義。
1.2.4 ConversionPattern參數(shù)的格式含義
格式名?含義
%c?輸出日志信息所屬的類的全名
%d?輸出日志時(shí)間點(diǎn)的日期或時(shí)間,默認(rèn)格式為ISO8601,也可以在其后指定格式,比如:%d{yyy-MM-dd?HH:mm:ss?},輸出類似:2002-10-18-?22:10:28
%f?輸出日志信息所屬的類的類名
%l?輸出日志事件的發(fā)生位置,即輸出日志信息的語(yǔ)句處于它所在的類的第幾行
%m?輸出代碼中指定的信息,如log(message)中的message
%n?輸出一個(gè)回車換行符,Windows平臺(tái)為"\r\n",Unix平臺(tái)為"\n"
%p?輸出優(yōu)先級(jí),即DEBUG,INFO,WARN,ERROR,F(xiàn)ATAL。如果是調(diào)用debug()輸出的,則為DEBUG,依此類推
%r?輸出自應(yīng)用啟動(dòng)到輸出該日志信息所耗費(fèi)的毫秒數(shù)
%t?輸出產(chǎn)生該日志事件的線程名
1.3 定義多個(gè)輸出目的地的實(shí)例
從上面的實(shí)例原理中我們已經(jīng)知道,同一個(gè)日志信息可以同時(shí)輸出到多個(gè)輸出目的地,在這個(gè)例子中,我們將實(shí)現(xiàn)一個(gè)把日志信息同時(shí)輸出到控制器、一個(gè)文件中的實(shí)例和數(shù)據(jù)庫(kù)中。這個(gè)實(shí)例的Java代碼我們沿用例2中的代碼,我們只需修改配置文件即可。這也體現(xiàn)了log4j的靈活性。
例3-a:
create?table?log4j(
logID?int?primary?key?identity,
message?varchar(1024),
priority?varchar(10),
milliseconds?int,
category?varchar(256),
thread?varchar(100),
NDC?varchar(256),
createDate?datetime,
location?varchar(256),
caller?varchar(100),
method?varchar(100),
filename?varchar(100),
line?int
)
例3-b:
#1?定義了兩個(gè)輸出端
log4j.rootLogger?=?INFO,?A1,?A2,A3
#2?定義A1輸出到控制器
log4j.appender.A1?=?org.apache.log4j.ConsoleAppender
#3?定義A1的布局模式為PatternLayout
log4j.appender.A1.layout?=?org.apache.log4j.PatternLayout
#4?定義A1的輸出格式
log4j.appender.A1.layout.ConversionPattern?=?%-4r?[%t]?%-5p?%c?-?%m%n
#5?定義A2輸出到文件
log4j.appender.A2?=?org.apache.log4j.RollingFileAppender
#6?定義A2要輸出到哪一個(gè)文件
log4j.appender.A2.File?=?F:\\nepalon\\classes\\example3.log
#7?定義A2的輸出文件的最大長(zhǎng)度
log4j.appender.A2.MaxFileSize?=?1KB
#8?定義A2的備份文件數(shù)
log4j.appender.A2.MaxBackupIndex?=?3
#9?定義A2的布局模式為PatternLayout
log4j.appender.A2.layout?=?org.apache.log4j.PatternLayout
#10?定義A2的輸出格式
log4j.appender.A2.layout.ConversionPattern?=?%d{yyyy-MM-dd?hh:mm:ss}:%p?%t?%c?-?%m%n
#11區(qū)?定義A3輸出到數(shù)據(jù)庫(kù)
log4j.appender.A3?=?org.apache.log4j.jdbc.JDBCAppender
log4j.appender.A3.BufferSize?=?40
log4j.appender.A3.Driver?=?com.microsoft.jdbc.sqlserver.SQLServerDriver
log4j.appender.A3.URL?=?jdbc:microsoft:sqlserver://127.0.0.1:1433;DatabaseName=nepalon
log4j.appender.A3.User?=?sa
log4j.appender.A3.Password?=?
log4j.appender.A3.layout?=?org.apache.log4j.PatternLayout
log4j.appender.A3.layout.ConversionPattern?=?INSERT?INTO?log4j?(createDate,?thread,?priority,?category,?message)?values(getdate(),?'%t',?'%-5p',?'%c',?'%m')
配置文件中的6、7、8行顯示了輸出端為RollingFileAppender的特有參數(shù)及其運(yùn)用的方法。11區(qū)顯示了輸出端為JDBCAppender的特有參數(shù)及其運(yùn)用方法。在這著重講解一下6、7、8行的作用。6行指定日志信息輸出到哪個(gè)文件,7行指定日志文件的最大長(zhǎng)度,最后要詳細(xì)介紹8行。第8行的參數(shù)是設(shè)置備份文件的個(gè)數(shù)的參數(shù),在這里我們?cè)O(shè)置為3,表示最多有3個(gè)備份文件,具體作用為:
1)?當(dāng)example3.log文件的大小超過(guò)K時(shí),就把文件改名為example3.log.1,同時(shí)生成一個(gè)新的example3.log文件
2)?當(dāng)example3.log文件的大小再次超過(guò)1K,又把文件改名為example3.log.1。但由于此時(shí)example3.log.1已存在,則先把example3.log.1更名為example3.log.2,再把example3.log文件改名為example3.log.1
3)?同理,當(dāng)example3.log文件的大小再次超過(guò)1K,先把example3.log.2文件更名為example3.log.3,把example3.log.1文件更名為example3.log.2,再把example3.log文件改名為example3.log.1
4)?當(dāng)example3.log文件的大小再次超過(guò)1K,先把example3.log.2文件更名為example3.log.3,舊的example3.log.3文件將被覆蓋;把example3.log.1文件更名為example3.log.2,舊的example3.log.2文件被覆蓋;最后把example3.log文件改名為example3.log.1并覆蓋掉舊的example3.log.1文件。
運(yùn)行結(jié)果將分為兩部分
在控制器中:
0?[main]?INFO?TestLog4j.TestLog4j?-?Just?testing?a?log?message?with?priority?set?to?INFO
11?[main]?WARN?TestLog4j.TestLog4j?-?Just?testing?a?log?message?with?priority?set?to?WARN
21?[main]?ERROR?TestLog4j.TestLog4j?-?Just?testing?a?log?message?with?priority?set?to?ERROR?21?[main]?FATAL?TestLog4j.TestLog4j?-?Just?testing?a?log?message?with?priority?set?to?FATAL
21?[main]?WARN?TestLog4j.TestLog4j?-?Testing?a?log?message?use?a?alternate?form
在文件example3.log中:
2003-12-18?04:23:02:INFO?main?TestLog4j.TestLog4j?-?Just?testing?a?log?message?with?priority?set?to?INFO
2003-12-18?04:23:02:WARN?main?TestLog4j.TestLog4j?-?Just?testing?a?log?message?with?priority?set?to?WARN
2003-12-18?04:23:02:ERROR?main?TestLog4j.TestLog4j?-?Just?testing?a?log?message?with?priority?set?to?ERROR
2003-12-18?04:23:02:FATAL?main?TestLog4j.TestLog4j?-?Just?testing?a?log?message?with?priority?set?to?FATAL
2003-12-18?04:23:02:WARN?main?TestLog4j.TestLog4j?-?Testing?a?log?message?use?a?alternate?form
1.4 配置log4j的總結(jié)
這個(gè)教程到這里,關(guān)于配置log4j的配置文件的基本原理已經(jīng)講完了,而且通過(guò)例3我們已經(jīng)可以完成基本的日志工作了。現(xiàn)在,我們就做一個(gè)總結(jié)。配置一個(gè)配置文件的基本步驟如下:
1)?定義一個(gè)Logger。在定義Logger時(shí)指定該Logger的級(jí)別級(jí)其輸出目的地。定義Logger的格式為
log4j.rootLogger?=?[?level?],?appendName1,?appendName2,?...appendNameN。
2)?定義appender的輸出目的地。定義一個(gè)appender的輸出目的地的格式為
log4j.appender.appenderName?=?fully.qualified.name.of.appender.class。
log4j提供的輸出端有ConsoleAppender、FileAppender?、DailyRollingFileAppender、RollingFileAppender和WriterAppender。
3)?定義appender的除布局模式外的其它相關(guān)參數(shù),如例3中第6、7、8定義了A2的相關(guān)參數(shù)。定義格式為
log4j.appender.appenderName.optionName1?=?value1
......
log4j.appender.appenderName.optionNameN?=?valueN
如果除了布局模式外不需要定義別的參數(shù),可跳過(guò)這一步(如例3中的A1)。
4)?定義appender的布局模式。定義一個(gè)appender的布局模式的格式為
log4j.appender.appenderName.layout?=?fully.qualified.name.of.layout.class。
布局模式其實(shí)也是步驟3)中的一個(gè)部分,只是布局模式參數(shù)是每一個(gè)appender必須定義的參數(shù)。Log4j提供的布局模式有HTMLLayout、PatternLayout和SimpleLayout。
5)?定義與所選的布局模式相關(guān)的設(shè)置信息,定義格式為
og4j.appender.appenderName.layout.optionName1?=?value1
......
log4j.appender.appenderName.layout.optionNameN?=?valueN
2 記錄器的層次Logger?hierarchy
2.1 何為記錄器的層次hierarchy
首先,我們先看一下何為層次,以我們最熟悉的繼承為例,下面是一張類圖
在這個(gè)繼承體系中,類B是類C的父類,類A是類C的祖先類,類D是類C的子類。這些類之間就構(gòu)成一種層次關(guān)系。在這些具有層次關(guān)系的類中,子類都可繼承它的父類的特征,如類B的對(duì)象能調(diào)用類A中的非private實(shí)例變量和函數(shù);而類C由于繼承自類B,所以類B的對(duì)象可以同時(shí)調(diào)用類A和類B中的非private實(shí)例變量和函數(shù)。
在log4j中,處于不同層次中的Logger也具有象類這樣的繼承關(guān)系。
2.2 記錄器的層次
如果一個(gè)應(yīng)用中包含了上千個(gè)類,那么也幾乎需要上千個(gè)Logger實(shí)例。如何對(duì)這上千個(gè)Logger實(shí)例進(jìn)行方便地配置,就是一個(gè)很重要的問(wèn)題。Log4J采用了一種樹狀的繼承層次巧妙地解決了這個(gè)問(wèn)題。在Log4J中Logger是具有層次關(guān)系的。它有一個(gè)共同的根,位于最上層,其它Logger遵循類似包的層次。下面我們將進(jìn)行介紹。
2.2.1 根記錄器root?logger
就象一個(gè)Java中的Object類一樣,log4j中的logger層次中有一個(gè)稱之為根記錄器的記錄器,其它所有的記錄器都繼承自這個(gè)根記錄器。根記錄器有兩個(gè)特征:
1)?根記錄器總是存在。就像Java中的Object類一樣,因?yàn)橛胠og4j輸出日志信息是通過(guò)記錄器來(lái)實(shí)現(xiàn)的,所以只要你應(yīng)用了log4j,根記錄器就肯定存在的。
2)?根記錄器沒(méi)有名稱,所以不能通過(guò)名稱來(lái)取得根記錄器。但在Logger類中提供了getRootLogger()的方法來(lái)取得根記錄器。
2.2.2 記錄器的層次
Logger遵循類似包的層次。如
static?Logger?rootLog?=?Logger.getRootLogger();
static?Logger?log1?=?Logger.getLogger("test4j");
static?Logger?log2?=?Logger.getLogger("test4j.test4j2");
static?Logger?log3?=?Logger.getLogger("test4j.test4j2.test4j2");
那么rootLog是log2的祖先子記錄器,log1是log2的父子記錄器,log3是log2的子記錄器。記錄器象Java中的類繼承一樣,子記錄器可以繼承父記錄器的設(shè)置信息,也可以可以覆寫相應(yīng)的信息。
首先先看一下記錄器層次中的繼承有什么用處。假設(shè)程序中的每個(gè)包都具有一些基本的日志信息,而包中的不同包可能會(huì)有些額外的日志信息要輸出,這種情況就可以象處理Java中的類一樣,運(yùn)用Logger中的層次關(guān)系來(lái)達(dá)到目的。假設(shè)有個(gè)名為A的包,我包下的所有類都要把日志信息輸出到控制臺(tái)中;A.B包除了輸出到控制臺(tái)外還要輸出到文件中;A.C包除了輸出到控制臺(tái)中還要輸出到HTML文檔中。這樣我們就可以通過(guò)定義一個(gè)父記錄器A,它負(fù)責(zé)把日志信息輸出到控制臺(tái)中;定義一個(gè)A的子記錄器A.B,它負(fù)責(zé)把日志信息輸出到文件中;定義一個(gè)A的子記錄器A.C,它負(fù)責(zé)把日志信息輸出到HTML文檔中。
記錄器遵循的是類似包的層次,這樣做為我們帶來(lái)了大大的方便。Logger類中的getLogger()方法可以取得Logger對(duì)象,這個(gè)方法有三種參數(shù)形式String、Class和URL,其實(shí)不論是用哪一種,最終都是通過(guò)記錄器的名字來(lái)取得記錄器對(duì)象的。如果要取得一個(gè)名為A.B的記錄器對(duì)象,我們可以Logger.getLogger("A.B")。但從上面的例子中,我們都是通過(guò)Logger.getLogger(TestLog4j.class.getName())這種方法來(lái)取得記錄器對(duì)象。這是為什么呢?現(xiàn)在我們假設(shè)A.B的包下有一個(gè)類BClass,那么我們調(diào)用BClass.class.getName()得到的是這個(gè)類的全名A.B.BClass。所以當(dāng)調(diào)用Logger.getLogger(BClass.class.getName())時(shí),最理想的情況是返回名為A.B.BClass的記錄器對(duì)象。但是如果不存在名為A.B.BClass的記錄器時(shí)它會(huì)怎樣呢?其實(shí)通過(guò)Logger類的getLogger方法取得記錄器時(shí)存在下面兩種情況:
1)?如果存在與所要找的名字完全相同的記錄器,則返回相應(yīng)的記錄器對(duì)象。
當(dāng)調(diào)用Logger.getLogger(BClass.class.getName())時(shí),如果定義了名為A.B.BClass的記錄器,它就返回該記錄器的對(duì)象。
2)?但如果找不到,它會(huì)嘗試返回在記錄器層次中與所要找的記錄器最接近的記錄器對(duì)象。
當(dāng)調(diào)用Logger.getLogger(BClass.class.getName())時(shí),如果沒(méi)有定義了名為A.B.BClass的記錄器,那會(huì)嘗試返回名為A.B的記錄器的對(duì)象;如果又沒(méi)有定義名為A.B的記錄器,它會(huì)嘗試返回名為A的記錄器的對(duì)象;如果也沒(méi)定義名為A的記錄器,它就會(huì)返回根記錄器的對(duì)象,而根記錄器是必須存在的,所以你總能得到一個(gè)記錄器對(duì)象。
好了,現(xiàn)在我們回到前面的問(wèn)題,我們?yōu)槭裁纯傄ㄟ^(guò)Logger.getLogger(BClass.class.getName())這種以類全名作為參數(shù)來(lái)取得記錄器對(duì)象呢?其實(shí)這是為了管理方便。因?yàn)槲覀冊(cè)诙x設(shè)計(jì)Logger時(shí)也遵循類似包的規(guī)則,使設(shè)計(jì)器的名稱與程序中的類包對(duì)應(yīng)。如上面的假設(shè)中我們的程序中有A包,A包下有B包和C包,B包下又有類BClass,那么我們就可使設(shè)計(jì)器的名為A、A.B、A.C、A.B.BClass,以此類推。那么當(dāng)我們通過(guò)類命名來(lái)取得設(shè)計(jì)器對(duì)象時(shí),總能取到與所要的設(shè)計(jì)器最接近的設(shè)計(jì)器對(duì)象。
2.3 如何應(yīng)用記錄器的層次
2.3.1 如果定義及獲取不同層次的記錄器
任何一個(gè)記錄器的使用都有兩個(gè)步驟:
1)?在配置文件中定義相應(yīng)的記錄器。
在配置文件中定義記錄器的格式有兩種
??定義根記錄器的格式為
log4j.rootLogger?=?[?level?],?appendName1,?appendName2,?...appendNameN
??定義一個(gè)非根記錄器的格式為
log4j.logger.loggerName1?=?[?level?],?appendName1,...appendNameN
......
log4j.logger.loggerNameM?=?[?level?],?appendName1,?...appendNameN
我們可以定義任意個(gè)非根記錄器。
2)?在代碼中調(diào)用Logger類的取得記錄器方法取得相應(yīng)的記錄器對(duì)象。
要取得根記錄器對(duì)象可通過(guò)Logger.getRootLogger()函數(shù),要取得非根記錄器可通過(guò)Logger.getLogger()函數(shù)。
理論知道就講到這里,紙上得來(lái)終覺(jué)淺,下面,我們來(lái)小小演練一下。
例4-a:
package?TestLog4j;
import?org.apache.log4j.Logger;
import?org.apache.log4j.PropertyConfigurator;
import?org.apache.log4j.Priority;
import?TestLog4j.TestLog4j2.TestLog4j2;
public?class?TestLog4j?
{
static?Logger?logger?=?Logger.getLogger(TestLog4j.class.getName()); //(2)
public?TestLog4j(){}
public?static?void?main(String[]?args)
{
//同時(shí)輸出到控制臺(tái)和一個(gè)文件的實(shí)例并實(shí)現(xiàn)了Logger的繼承
PropertyConfigurator.configure("F:\\nepalon\\log4j2.properties");
logger.debug("Start?of?the?main()?in?TestLog4j");
logger.info("Just?testing?a?log?message?with?priority?set?to?INFO");
logger.warn("Just?testing?a?log?message?with?priority?set?to?WARN");
logger.error("Just?testing?a?log?message?with?priority?set?to?ERROR");
logger.fatal("Just?testing?a?log?message?with?priority?set?to?FATAL");
logger.log(Priority.WARN,?"Testing?a?log?message?use?a?alternate?form");
logger.debug(TestLog4j.class.getName());
TestLog4j2?testLog4j2?=?new?TestLog4j2(); //(1)
testLog4j2.testLog();
}
}
在類TestLog4j中我們調(diào)用了另一個(gè)類TestLog4j2,下面看一下類TestLog4j2的代碼。
例4-b:
package?TestLog4j.TestLog4j2;
import?org.apache.log4j.Logger;
import?org.apache.log4j.PropertyConfigurator;
import?org.apache.log4j.Priority;
public?class?TestLog4j2?
{
static?Logger?logger?=?Logger.getLogger(TestLog4j2.class.getName()); //(1)
public?TestLog4j2(){}
public?void?testLog()
{
//同時(shí)輸出到控制臺(tái)和一個(gè)文件的實(shí)例
PropertyConfigurator.configure("F:\\nepalon\\log4j2.properties");
logger.debug("2Start?of?the?main()");
logger.info("2Just?testing?a?log?message?with?priority?set?to?INFO");
logger.warn("2Just?testing?a?log?message?with?priority?set?to?WARN");
logger.error("2Just?testing?a?log?message?with?priority?set?to?ERROR");
logger.fatal("2Just?testing?a?log?message?with?priority?set?to?FATAL");
logger.log(Priority.DEBUG,?"Testing?a?log?message?use?a?alternate?form");
logger.debug("2End?of?the?main()");
}
}
最后我們來(lái)看一下配置文件。
例4-c:
log4j2.properties文件內(nèi)容
#1區(qū)
####?Use?two?appenders,?one?to?log?to?console,?another?to?log?to?a?file
log4j.rootLogger?=?debug,?stdout
#2區(qū)
#Print?only?messages?of?priority?WARN?or?higher?for?your?category
log4j.logger.TestLog4j=?,?R
log4j.logger.TestLog4j.TestLog4j2=WARN
#3區(qū)
####?First?appender?writes?to?console
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
#?Pattern?to?output?the?caller's?file?name?and?line?number.
log4j.appender.stdout.layout.ConversionPattern=%5p?[%t]?(%F:%L)?-?%m%n
#4區(qū)
####?Second?appender?writes?to?a?file
log4j.appender.R=org.apache.log4j.RollingFileAppender
log4j.appender.R.File=F:\\nepalon\\classes\\TestLog4j\\example.log
#?Control?the?maximum?log?file?size
log4j.appender.R.MaxFileSize=100KB
#?Archive?log?files?(one?backup?file?here)
log4j.appender.R.MaxBackupIndex=1
log4j.appender.R.layout=org.apache.log4j.PatternLayout
log4j.appender.R.layout.ConversionPattern=%d{yyyy-MM-dd?hh:mm:ss}:%p?%t?%c?-?%m%n
先看一下運(yùn)行結(jié)果。
在控制臺(tái)中的結(jié)果為:
DEBUG?[main]?(?:?)?-?Start?of?the?main()?in?TestLog4j
INFO?[main]?(?:?)?-?Just?testing?a?log?message?with?priority?set?to?INFO
WARN?[main]?(?:?)?-?Just?testing?a?log?message?with?priority?set?to?WARN
ERROR?[main]?(?:?)?-?Just?testing?a?log?message?with?priority?set?to?ERROR
FATAL?[main]?(?:?)?-?Just?testing?a?log?message?with?priority?set?to?FATAL
WARN?[main]?(?:?)?-?Testing?a?log?message?use?a?alternate?form
DEBUG?[main]?(?:?)?-?TestLog4j.TestLog4j
WARN?[main]?(?:?)?-?2Just?testing?a?log?message?with?priority?set?to?WARN
ERROR?[main]?(?:?)?-?2Just?testing?a?log?message?with?priority?set?to?ERROR
FATAL?[main]?(?:?)?-?2Just?testing?a?log?message?with?priority?set?to?FATAL
輸出文件的結(jié)果為:
2003-12-19?04:19:44:DEBUG?main?TestLog4j.TestLog4j?-?Start?of?the?main()?in?TestLog4j
2003-12-19?04:19:44:INFO?main?TestLog4j.TestLog4j?-?Just?testing?a?log?message?with?priority?set?to?INFO
2003-12-19?04:19:44:WARN?main?TestLog4j.TestLog4j?-?Just?testing?a?log?message?with?priority?set?to?WARN
2003-12-19?04:19:44:ERROR?main?TestLog4j.TestLog4j?-?Just?testing?a?log?message?with?priority?set?to?ERROR
2003-12-19?04:19:44:FATAL?main?TestLog4j.TestLog4j?-?Just?testing?a?log?message?with?priority?set?to?FATAL
2003-12-19?04:19:44:WARN?main?TestLog4j.TestLog4j?-?Testing?a?log?message?use?a?alternate?form
2003-12-19?04:19:44:DEBUG?main?TestLog4j.TestLog4j?-?TestLog4j.TestLog4j
2003-12-19?04:19:44:WARN?main?TestLog4j.TestLog4j2.TestLog4j2?-?2Just?testing?a?log?message?with?priority?set?to?WARN
2003-12-19?04:19:44:ERROR?main?TestLog4j.TestLog4j2.TestLog4j2?-?2Just?testing?a?log?message?with?priority?set?to?ERROR
2003-12-19?04:19:44:FATAL?main?TestLog4j.TestLog4j2.TestLog4j2?-?2Just?testing?a?log?message?with?priority?set?to?FATAL
首先,先來(lái)看一下配置文件都有些什么東西。
1)?在1區(qū)中定義了一個(gè)根記錄器。這個(gè)根記錄器具有DEBUG級(jí)別并有一個(gè)名稱為stdout的輸出端appender。
2)?2區(qū)中的內(nèi)容是這一節(jié)的重點(diǎn),也是應(yīng)用到記錄器層次的地方,但其實(shí)也只有兩句,充分體現(xiàn)了log4j的簡(jiǎn)單性。在這里,我們定義了兩個(gè)名稱分別為TestLog4j和TestLog4j.TestLog4j2設(shè)計(jì)器。
??在定義TestLog4j記錄器時(shí)沒(méi)有指定級(jí)別,所以它的級(jí)別繼承自它的父記錄器,即要記錄器,所以它的級(jí)別也為DEBUG。在定義TestLog4j記錄器時(shí)又定義了一個(gè)名稱為R的輸出端,所以它的輸出端有兩個(gè),一個(gè)從根記錄器繼承而來(lái)的名為stdout的輸出端,另一個(gè)為在此定義的名為R的輸出端。在此需要注意的是,在定義記錄器時(shí)必須先定義記錄器的級(jí)別,然后才是記錄器的輸出端。如果只想定義輸出端而不定義級(jí)別,則雖然級(jí)別可以為空,但逗號(hào)分隔符不能省略。如定義TestLog4j記錄器的做法。
??在定義TestLog4j.TestLog4j2記錄器時(shí)又指定了它的級(jí)別,由于一個(gè)記錄器的級(jí)別只能有一個(gè),所以新指定的級(jí)別將覆寫掉它的父記錄器的級(jí)別(這就象Java中的多態(tài))。我們沒(méi)有定義TestLog4j.TestLog4j2記錄器的輸出端,所以它的輸出端將從它的父記錄器中繼承而來(lái)。它的父記錄器為estLog4j記錄器,所以它和estLog4j記錄器一樣具有兩個(gè)名稱分別為stdout和R的輸出端。
3)?剩下的3區(qū)和4區(qū)分別設(shè)置了兩個(gè)輸出端的參數(shù)值。
接下來(lái),回到我們的代碼,看一下是如何取得記錄器,在取記錄器時(shí)又發(fā)生了什么。
1)?例4-a中的代碼(2)中,語(yǔ)句Logger.getLogger()中的參數(shù)TestLog4j.class.getName()的值為TestLog4j.?TestLog4j,所以此語(yǔ)句的結(jié)果是取得一個(gè)名為TestLog4j.?TestLog4j的記錄器的對(duì)象。但在配置文件中并沒(méi)有定義這樣的記錄器,所以最終將返回與所需的名稱TestLog4j.?TestLog4j最接近的記錄器對(duì)象,即名為TestLog4j的記錄器的對(duì)象。
2)?例4-b中的代碼(1)的原理與例4-a中的代碼(2)相似,期望取得的是名為TestLog4j.TestLog4j2.?TestLog4j2的記錄器對(duì)象,但最終返回的是TestLog4j.TestLog4j2記錄器的對(duì)象。
三 log4j與J2EE的結(jié)合
到目前為止,這篇文章講的都是如何在application中應(yīng)用log4j,而Java現(xiàn)在的應(yīng)用主流是J2EE和J2ME。現(xiàn)在,我們來(lái)看一下要如何在J2EE開發(fā)中應(yīng)用log4j。其實(shí)在Web?application中應(yīng)用log4j也很簡(jiǎn)單,與在application中應(yīng)用log4j不同之處就是要在所有應(yīng)用log4j的代碼之前對(duì)log4j進(jìn)行初始化。所以,我們?cè)趙eb?application中就要把log4j的初始化工作獨(dú)立出來(lái),把它放在Servlet中。下面,我們看一個(gè)例子。
例5-a:
進(jìn)行初始化的Servlet:
import?org.apache.log4j.PropertyConfigurator;
import?javax.servlet.http.HttpServlet;
import?javax.servlet.http.HttpServletRequest;
import?javax.servlet.http.HttpServletResponse;
/**
*?log4j.jar的初始化類,參考web.xml
*/
public?class?Log4jInit?extends?HttpServlet?
{
public?void?init()?
{
//通過(guò)web.xml來(lái)動(dòng)態(tài)取得配置文件
String?prefix?=?getServletContext().getRealPath("/");
String?file?=?getInitParameter("log4j-init-file");
//?如果沒(méi)有給出相應(yīng)的配置文件,則不進(jìn)行初始化
if(file?!=?null)?
{
PropertyConfigurator.configure(prefix+file); //(1)
}
}
public?void?doGet(HttpServletRequest?req,?HttpServletResponse?res)?
{}?
}
下面來(lái)看一下這個(gè)Servlet在web.xml中的定義。
例5-b:
<servlet>
<servlet-name>log4j-init</servlet-name>
<servlet-class>TestLog4j.Log4jInit</servlet-class>
<init-param>
<param-name>log4j-init-file</param-name>
<param-value>sort.properties</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
因?yàn)閘og4j的初始化要在所有的log4j調(diào)用之前完成,所以在web.xml文件中,我們一定要把對(duì)應(yīng)的Servlet定義的load-on-startup應(yīng)設(shè)為1,以便在Web容器啟動(dòng)時(shí)即裝入該Servlet。
完成了這兩個(gè)步驟這后,我們就可以象在application開發(fā)中一樣在web?application任何地方應(yīng)用log4j了。下面是在javabean中的應(yīng)用的一個(gè)例子。
例5-c:
import?org.apache.log4j.Logger;
public?class?InfoForm?
{
static?Logger?logger?=?Logger.getLogger(InfoForm.class);
protected?String?title;
protected?String?content;
public?InfoForm()?{}
public?void?setTitle(Object?value)?
{
logger.debug("nepalon:title?=?"?+?title);
title?=?value;
}
public?String?getTitle()?
{
logger.debug("nepalon:title?=?"?+?title);
return?title;
}
public?void?setContent(String?value)?
{
content?=?value;
logger.debug("nepalon:?content()?=?"?+?content);
}
public?String?getContent()?
{
logger.debug("nepalon:?content?=?\n"?+?content);
return?content;
}
}