log4j?簡(jiǎn)明手冊(cè)
Ceki?Gülcü?
March?2002?
Copyright©?2000-2004?The?Apache?Software?Foundation.?版權(quán)所有。Log4j軟件是在遵守Apache?Software?License?1.1版的條例下發(fā)行的,Apache?Software?License的復(fù)制件被包括在log4j發(fā)布的LICENSE.txt文件里。這個(gè)簡(jiǎn)短手冊(cè)也借用了The?complete?log4j?manual?里的一些內(nèi)容,The?complete?log4j?manual包含最新的更為詳盡的信息。??The?complete?log4j?manual
摘要
這個(gè)文檔資料描述了log4j?API,它的獨(dú)特的特性和設(shè)計(jì)原理。Log4j是由許多作者共同參與的開(kāi)放源代碼項(xiàng)目。它允許開(kāi)發(fā)人員以任意的精細(xì)程度控制哪些日志說(shuō)明被輸出。通過(guò)使用外部的配置文件,可以在運(yùn)行時(shí)配置它。最好的是,log4j?開(kāi)發(fā)包很容易上手。注意,它也可能會(huì)使一些開(kāi)發(fā)人員著迷。
簡(jiǎn)?介
幾乎每個(gè)大的應(yīng)用程序都有它自己的日志和跟蹤程序的API。順應(yīng)這一規(guī)則,E.U.?SEMPER項(xiàng)目組決定編寫(xiě)它自己的程序跟蹤API(tracing?API)。這開(kāi)始于1996年早期。經(jīng)過(guò)無(wú)數(shù)的工作,更改和性能加強(qiáng),這個(gè)API終于成為一個(gè)十分受歡迎的Java日志軟件包,那就是log4j。這個(gè)軟件包的發(fā)行遵守open?source動(dòng)議認(rèn)證的Apache?Software?License。最新的log4j版本包括全部的源代碼,類文件和文檔資料,可以在?http://logging.apache.org/log4j/找到它們。另外,log4j已經(jīng)被轉(zhuǎn)換成?C,?C++,?C#,?Perl,?Python,?Ruby,?和?Eiffel?語(yǔ)言。
把log?statements插入到你的代碼中是一種排錯(cuò)的低技能辦法。這也許是唯一的方法,因?yàn)榕佩e(cuò)工具并不總是可以被使用或者適用于你的程序。對(duì)于多線程的應(yīng)用程序和多數(shù)發(fā)行的應(yīng)用程序,通常就是這樣的情形。
經(jīng)驗(yàn)告訴我們logging是開(kāi)發(fā)過(guò)程中重要的一環(huán)。它具有多種優(yōu)點(diǎn)。首先,它能精確地提供運(yùn)行時(shí)的上下文(context)。一旦在程序中加入了Log?代碼,它就能自動(dòng)的生成并輸出logging信息而不需要人為的干預(yù)。另外,log信息的輸出可以被保存到一個(gè)固定的地方,以備以后研究。除了在開(kāi)發(fā)過(guò)程中發(fā)揮它的作用外,一個(gè)性能豐富的日志記錄軟件包能當(dāng)作一個(gè)審計(jì)工具(audit?tool)使用。
Brian?W.?Kernighan?和?Rob?Pike?在他們的"The?Practice?of?Programming"?書(shū)中這樣寫(xiě)到:??"The?Practice?of?Programming"
作為個(gè)人的選擇,除了得到一大堆程序跟蹤信息或一兩個(gè)變量值以外,我們傾
向於不使用排錯(cuò)器。一個(gè)原因是在詳細(xì)而復(fù)雜的數(shù)據(jù)結(jié)構(gòu)和控制流程中很容易
迷失;我們發(fā)現(xiàn)認(rèn)真思考并在關(guān)鍵處加入自我檢查代碼和輸出指令,比起一步
步看程序要效率高。在日志說(shuō)明里查找比在明智地放置自我檢查代碼后的輸出
里查找要費(fèi)時(shí)。而決定在哪里放置打印指令要比在日志說(shuō)明里一步步找到關(guān)鍵
的代碼要省時(shí)間。更重要的是,自我檢查的排錯(cuò)指令和程序并存;而排錯(cuò)
sessions是暫時(shí)的。
Logging確實(shí)也有它的缺陷。它降低了程序運(yùn)行的速度。它太冗長(zhǎng),查看時(shí)很容易錯(cuò)過(guò)。為了減少這些負(fù)面影響,log4j?被設(shè)計(jì)得可靠,高效和靈活。因?yàn)椋涗浫罩竞苌偈且粋€(gè)應(yīng)用程序的主要焦點(diǎn),log4j?API?盡量做到容易被理解和使用。
Loggers,?Appenders?and?Layouts
Log4j?有三個(gè)主要組件:loggers,?appenders和layouts。這三類組件一起應(yīng)用,可以讓開(kāi)發(fā)人員能夠根據(jù)日志的類型和級(jí)別進(jìn)行記錄,并且能在程序運(yùn)行時(shí)控制log信息輸出的格式和往什么地方輸出信息。
Logger?hierarchy
任何logging?API?與簡(jiǎn)單的System.out.println輸出調(diào)試信息方法比較,最主要的優(yōu)點(diǎn)在于它能夠關(guān)閉一些調(diào)試信息輸出而不影響其他人的調(diào)試。這種能力的實(shí)現(xiàn)是假設(shè)這些logging空間,也就是所有的可能發(fā)生的日志說(shuō)明空間,可以根據(jù)程序開(kāi)發(fā)人員選擇的標(biāo)準(zhǔn)進(jìn)行分類。這一觀察以前使得我們選擇了category作為這個(gè)軟件包的中心概念。但是,在log4j?1.2版本以后,Logger類取代了Category類。對(duì)于那些熟悉早先版本的log4j的開(kāi)發(fā)人員來(lái)說(shuō),Logger類只不過(guò)是Category類的一個(gè)別名。
Loggers是被命名的實(shí)體。Logger的名字大小寫(xiě)有區(qū)別(case-sensitive),并且它們遵守階層式的命名規(guī)則:
Named?Hierarchy?
如果一個(gè)logger?的名字后面跟著一個(gè)點(diǎn)號(hào)(dot),它就是點(diǎn)號(hào)(dot)后面的那個(gè)logger的前輩(?ancestor),是這個(gè)晚輩(descendant)?的前綴。如果在它自己和這個(gè)晚輩之間沒(méi)有其它的前輩,它和這個(gè)晚輩之間就是父子關(guān)系。
?
例如,叫做"com.foo"的logger是叫做?"com.foo.Bar"的logger的父輩?。同樣地,"java"是"java.util"?的父輩,是"java.util.Vector"的前輩。大多數(shù)開(kāi)發(fā)人員都熟悉這種命名方法。??"com.foo"??"com.foo.Bar"??"java"??"java.util"??"java.util.Vector"
根(root)logger?位于logger?階層的最上層。它在兩個(gè)方面很特別:
它總是存在的,
不能通過(guò)使用它的名字直接得到它。
通過(guò)這個(gè)類的靜態(tài)方法Logger.getRootLogger得到它(指RootLogger)。所有其他的loggers是通過(guò)靜態(tài)方法Logger.getLogger來(lái)實(shí)例化并獲取的。這個(gè)方法Logger.getLogger把所想要的logger的名字作為參數(shù)。?Logger類的一些其它基本方法在下面列出:
package?org.apache.log4j;?
public?class?Logger?{?
??//?Creation?and?retrieval?methods:?
??public?static?Logger?getRootLogger();?
??public?static?Logger?getLogger(String?name);?
??//?printing?methods:?
??public?void?debug(Object?message);?
??public?void?info(Object?message);?
??public?void?warn(Object?message);?
??public?void?error(Object?message);?
??public?void?fatal(Object?message);?
??//?generic?printing?method:?
??public?void?log(Level?l,?Object?message);?
}
?
Loggers可以被指派優(yōu)先級(jí)別。DEBUG,?INFO,?WARN,?ERROR?和FATAL這組級(jí)別在org.apache.log4j.Level類中有定義。你也可以通過(guò)Level類的子類去定義你自己的優(yōu)先級(jí)別,盡管我們不鼓勵(lì)你這樣做。在后面我們會(huì)講到一個(gè)更好的方法。
如果一個(gè)logger沒(méi)有被指定優(yōu)先級(jí)別,它將繼承最接近的祖先所被指定的優(yōu)先級(jí)別。下面是更多關(guān)于優(yōu)先級(jí)別的信息:
Level?Inheritance?
對(duì)于一個(gè)給定的logger?C,它繼承的級(jí)別等于logger階層里,從C開(kāi)始往root?logger上去的第一個(gè)non-null級(jí)別。
?
要保證所有的loggers最終都繼承一個(gè)優(yōu)先級(jí)別,root?logger總是有一個(gè)被指派的優(yōu)先級(jí)。
下面是具有各種指派優(yōu)先級(jí)別值的四個(gè)表格,以及根據(jù)上面的規(guī)則所得出的繼承優(yōu)先級(jí)別。
Logger
name(名稱)?指派
級(jí)別?繼承
級(jí)別?
根?Proot?Proot?
X??none?Proot?
X.Y??none?Proot?
X.Y.Z?none?Proot?
例子?
在上面的示例1中,只有root?logger被指派了級(jí)別。這個(gè)級(jí)別的值,Proot,被其它的loggers?X,?X.Y?和?X.Y.Z繼承了。
Logger
name(名稱)?指派
級(jí)別?繼承
級(jí)別?
根?Proot?Proot?
X??Px?Px?
X.Y??Pxy?Pxy?
X.Y.Z?Pxyz?Pxyz?
例子?
在上面的示例2中,所有的loggers都有一個(gè)指派的級(jí)別值。不需要級(jí)別繼承。
Logger
name(名稱)?指派
級(jí)別?繼承
級(jí)別?
根?Proot?Proot?
X??Px?Px?
X.Y??none?Px?
X.Y.Z?Pxyz?Pxyz?
例子?
在示例3中,loggers?root,?X?和?X.Y.Z?分別被指派級(jí)別值Proot,?Px?和Pxyz。Logger?X.Y?從它的父輩X那里繼承它的級(jí)別值。Logger
name(名稱)?指派
級(jí)別?繼承
級(jí)別?
根?Proot?Proot?
X??Px?Px?
X.Y??none?Px?
X.Y.Z?none?Px?
例子?
在示例4中,loggers?root和X?分別被指派級(jí)別值Proot和Px。Logger?X.Y和X.Y.Z繼承它們最接近的父輩X的被指派的級(jí)別值。
日志請(qǐng)求是通過(guò)調(diào)用一個(gè)日志實(shí)例的打印方法(之一)而產(chǎn)生的。這些打印方法是?log4j/Logger.html#debug(java.lang.Object)">debug,?info,?warn,?error,?fatal?和?log。
根據(jù)定義,打印方法決定一個(gè)日志請(qǐng)求的級(jí)別。例如,如果c是一個(gè)日志實(shí)例,那么語(yǔ)句c.info("..")?就是級(jí)別為INFO的一個(gè)日志請(qǐng)求。??c.info("..")
只有一個(gè)日志請(qǐng)求(A?logging?request)的級(jí)別高于或等于它的logger級(jí)別的時(shí)候才能夠被執(zhí)行。否則,則被認(rèn)為這個(gè)日志請(qǐng)求不能被執(zhí)行。一個(gè)沒(méi)有被定義優(yōu)先級(jí)別的logger將從層次關(guān)系中的前輩那里繼承優(yōu)先級(jí)別。這個(gè)規(guī)則總結(jié)如下:
Basic?Selection?Rule?
在一個(gè)級(jí)別為q(被指定的或繼承的)的logger里,一個(gè)級(jí)別為p的日志請(qǐng)求,只有在p?>=?q?時(shí)才能夠被執(zhí)行。
?
這個(gè)規(guī)則是log4j的核心。它假設(shè)級(jí)別是有先后順序的。對(duì)于標(biāo)準(zhǔn)的優(yōu)先級(jí)別來(lái)說(shuō),DEBUG?<?INFO?<?WARN?<?ERROR?<?FATAL。
這里是一個(gè)關(guān)于這個(gè)規(guī)則的例子:
???//?get?a?logger?instance?named?"com.foo"
???Logger??logger?=?Logger.getLogger("com.foo");
???//?Now?set?its?level.?Normally?you?do?not?need?to?set?the
???//?level?of?a?logger?programmatically.?This?is?usually?done
???//?in?configuration?files.
???logger.setLevel(Level.INFO);
???Logger?barlogger?=?Logger.getLogger("com.foo.Bar");
???//?This?request?is?enabled,?because?WARN?>=?INFO.
???logger.warn("Low?fuel?level.");
???//?This?request?is?disabled,?because?DEBUG?<?INFO.
???logger.debug("Starting?search?for?nearest?gas?station.");
???//?The?logger?instance?barlogger,?named?"com.foo.Bar",
???//?will?inherit?its?level?from?the?logger?named
???//?"com.foo"?Thus,?the?following?request?is?enabled
???//?because?INFO?>=?INFO.
???barlogger.info("Located?nearest?gas?station.");
???//?This?request?is?disabled,?because?DEBUG?<?INFO.
???barlogger.debug("Exiting?gas?station?search");
?
以一樣的叁數(shù)名字調(diào)用getLogger方法,返回的reference總是指向完全相同的logger對(duì)象。
例如,在這里:???Logger?x?=?Logger.getLogger("wombat");
???Logger?y?=?Logger.getLogger("wombat");
?
x和y指向完全相同的logger對(duì)象。
因此,通過(guò)這種方式可以配置一個(gè)logger,而不需要傳遞references就能在其他地方得到相同的實(shí)例。在生物的父子關(guān)系中父母總是排放在孩子們前面,?log4j?loggers與此有相互矛盾的地方,那就是log4j?loggers可以以任何順序被產(chǎn)生和配置。特別的是,一個(gè)"parent"?logger?會(huì)找到并連接他的后代,即使他是在他們之后被定義。
Log4j環(huán)境通常是在程序被初始化的時(shí)候被配置的。最好的方式是通過(guò)閱讀一個(gè)配置文件去配置。我們會(huì)馬上討論到這方面的內(nèi)容。
Log4j使得通過(guò)軟件組件的名稱去定義loggers的名字很容易。這可以通過(guò)在每個(gè)類中靜態(tài)地instantiating一個(gè)logger,讓logger的名字與這個(gè)合格的java類文件名相同來(lái)完成。這是一種有用并且直觀的定義loggers的方式。因?yàn)槿罩镜妮敵鰩в挟a(chǎn)生它們的logger的名字,這種命名策略使我們能夠很方便地識(shí)別這些log信息的來(lái)源。不過(guò),盡管這是通用的一種loggers命名策略,Log4j沒(méi)有限制怎樣對(duì)loggers進(jìn)行命名。開(kāi)發(fā)程序員可以根據(jù)自己的喜好隨意定義?loggers。??software?component
當(dāng)然,至今所知的最好的命名策略還是以它們所在的類的名稱來(lái)命名?loggers。
Appenders?and?Layouts
基于自身的logger選擇性地使用或不使用日志請(qǐng)求(logging?requests?)的能力僅僅整個(gè)Log4j能力的一部分。Log4j允許將log信息輸出到許多不同的輸出設(shè)備中。用log4j的語(yǔ)言來(lái)說(shuō),一個(gè)log信息輸出目的地就叫做一個(gè)appender。目前,log4j?的appenders可以將log信息輸出到console,files,GUI?components,remote?socket?servers,?JMS,NT?Event?Loggers,和?remote?UNIX?Syslog?daemons。它還可以同時(shí)將log信息輸出到多個(gè)輸出設(shè)備中。???NT?Event?Loggers
多個(gè)appenders可以和一個(gè)logger連接在一起。
使用addAppender方法把一個(gè)appender加入到給定的logger上。一個(gè)給定的?logger的每一個(gè)被允許的日志請(qǐng)求都會(huì)被傳遞給這個(gè)logger的所有appenders,以及階層中高級(jí)別的appenders。換句話說(shuō)appenders是從logger階層中不斷添加地被繼承的。例如,一個(gè)?console?appender加給了root?logger,那么,這個(gè)root?logger所有被允許輸出的日志信息將被輸出到console。如果你又給一個(gè)名字為C的logger添加了一個(gè)?file?appender,那么C?以及C的子輩的所有被允許的日志信息將被同時(shí)輸出到?file?appender和console?appender。可以通過(guò)把a(bǔ)dditivity?flag設(shè)置為false來(lái)覆蓋這個(gè)默認(rèn)的行為從而使appender的繼承關(guān)系不再是添加性的。??Each?enabled?logging?request?for?a?given?logger?will?be?forwarded?to?all?the?appenders?in?that?logger?as?well?as?the
?appenders
?higher?in?the?hierarchy.??setting?the?additivity?flag
支配appender添加性的規(guī)則總結(jié)如下:
Appender?Additivity?
Logger?C的log輸出信息將被輸出到C的所有appenders和它的前輩的?appenders。這就是"appender?additivity"的意思。
但是,如果logger?C的前輩,比如說(shuō)P,P的additivity?flag被設(shè)置為?false,那么,C的輸出信息將被輸出到C的所有appenders中去,以及它的前輩的??截止在P那里,包括P在內(nèi)的,appenders中去,但是不會(huì)輸出到P的前輩的?appenders中去。
默認(rèn)情況下,Loggers的additivity?flag設(shè)置為true。
?
下面的表格顯示一個(gè)示例:
Logger
name(名稱)?添加的
Appenders??Additivity
旗標(biāo)?輸出目標(biāo)?注釋?
根?A1??not?applicable??A1??Root?logger是無(wú)名的,但是可以通過(guò)Logger.getRootLogger()?來(lái)訪問(wèn)。Root?logger沒(méi)有附帶默認(rèn)的appender。?
x??A-x1,?A-x2??true??A1,?A-x1,?A-x2??"x"?和root?logger里的Appenders。?
x.y??none??true??A1,?A-x1,?A-x2??"x"?和root?logger里的Appenders。?
x.y.z??A-xyz1??true??A1,?A-x1,?A-x2,?A-xyz1??"x.y.z",?"x"?和root?logger里的Appenders。?
安全?A-sec??false??A-sec??因?yàn)閍dditivity?flag被設(shè)置為?false,所以沒(méi)有appender繼承積累。?
security.access??none??true??A-sec??因?yàn)?security"?logger里的additivity?flag被設(shè)置為false,所以僅僅只有"security"?logger的appenders。?
通常,用戶不僅希望自己指定log信息的輸出目的地,而且,他們還希望指定?log信息的輸出格式。這可以通過(guò)和appender相關(guān)的layout實(shí)現(xiàn)。Layout負(fù)責(zé)根據(jù)用戶的需要去格式化log信息的輸出,而appender負(fù)責(zé)將一個(gè)格式化過(guò)的?log信息輸出到它的目的地。PatternLayout?是標(biāo)準(zhǔn)log4j發(fā)行包中的一部分,它讓用戶根據(jù)和C語(yǔ)言中的printf方法相似的轉(zhuǎn)換模式指定輸出格式。
例如,具有"%r?[%t]?%-5p?%c?-?%m%n"?轉(zhuǎn)換格式的PatternLayout?將輸出以下的式樣:
176?[main]?INFO?org.foo.Bar?-?Located?nearest?gas?station.
第一個(gè)區(qū)域是從程序開(kāi)始運(yùn)行到輸出日志信息所用的毫秒數(shù)。第二個(gè)區(qū)域是產(chǎn)生日志請(qǐng)求的線程。第三個(gè)區(qū)域是這個(gè)log語(yǔ)句的優(yōu)先級(jí)別。第四個(gè)區(qū)域是和日志請(qǐng)求相關(guān)聯(lián)的logger名字。在'-'?之后的文字是這個(gè)log信息的內(nèi)容。
同樣重要的是,log4j?將根據(jù)用戶指定的標(biāo)準(zhǔn)來(lái)表達(dá)log信息的內(nèi)容。例如,如果你經(jīng)常需要日志記錄Oranges,Oranges是你當(dāng)前項(xiàng)目中使用的一個(gè)對(duì)象類型,那么你可以注冊(cè)一個(gè)OrangeRenderer,這樣每當(dāng)需要日志記錄一個(gè)?orange時(shí),OrangeRenderer就會(huì)被調(diào)用。
對(duì)象的表達(dá)遵照類階層(class?hierarchy)形式。例如,假設(shè)oranges是?fruits,你注冊(cè)了一個(gè)FruitRenderer,那么,包括oranges在內(nèi)的所有的fruits?都將由FruitRenderer來(lái)表達(dá),除非你自己為orange注冊(cè)了一個(gè)特定的?OrangeRenderer。
Object?renderers必須實(shí)施ObjectRenderer界面。
配?置
在程序代碼中插入這些日志請(qǐng)求需要相當(dāng)大的工作量。調(diào)查顯示,大約%4左右的代碼是logging。因此,即便是中等大小的應(yīng)用程序也需要在它們的代碼中至少包含有幾千行的log語(yǔ)句。就從這個(gè)數(shù)目來(lái)看,管理這些log語(yǔ)句而不用人工地去修改它們是十分重要的。
Log4j環(huán)境是完全能夠通過(guò)編程來(lái)配置的。但是使用配置文件去配置則更靈活。目前,Log4j的配置文件是以XML格式和JAVA?properties?(key=value)?格式編寫(xiě)的。
假設(shè)我們有個(gè)叫MyApp的程序使用log4j,讓我們來(lái)看看這是怎樣做到的:
?import?com.foo.Bar;
?//?Import?log4j?classes.
?import?org.apache.log4j.Logger;
?import?org.apache.log4j.BasicConfigurator;
?public?class?MyApp?{
???//?Define?a?static?logger?variable?so?that?it?references?the
???//?Logger?instance?named?"MyApp".
???static?Logger?logger?=?Logger.getLogger(MyApp.class);
???public?static?void?main(String[]?args)?{
?????//?Set?up?a?simple?configuration?that?logs?on?the?console.
?????BasicConfigurator.configure();
?????logger.info("Entering?application.");
?????Bar?bar?=?new?Bar();
?????bar.doIt();
?????logger.info("Exiting?application.");
???}
?}
?
MyApp類首先引入log4j的相關(guān)類,然后定義一個(gè)命名為MyApp的靜態(tài)logger變量,而這個(gè)名字恰好和MyApp的類名一樣。
MyApp類還使用了被定義在com.foo包中的Bar類:
?package?com.foo;
?import?org.apache.log4j.Logger;
?public?class?Bar?{
???static?Logger?logger?=?Logger.getLogger(Bar.class);
???public?void?doIt()?{
?????logger.debug("Did?it?again!");
???}
?}
?
通過(guò)調(diào)用BasicConfigurator.configure?方法產(chǎn)生一個(gè)相當(dāng)簡(jiǎn)單的log4j的設(shè)置。這個(gè)方法將一個(gè)?ConsoleAppender添加到root?logger,從而讓log信息輸出到?console。通過(guò)把PatternLayout設(shè)置為?%-4r?[%t]?%-5p?%c?%x?-?%m%n來(lái)確定輸出格式。
注意,默認(rèn)的root?logger被指派為L(zhǎng)evel.DEBUG。
MyApp的輸出是這樣的:
0????[main]?INFO??MyApp??-?Entering?application.
36???[main]?DEBUG?com.foo.Bar??-?Did?it?again!
51???[main]?INFO??MyApp??-?Exiting?application.
下面的圖形描繪了在調(diào)用BasicConfigurator.configure方法之后,MyApp的對(duì)象圖表。
?
注意,log4j?的子代loggers只和它們現(xiàn)有的前輩鏈接。在這里,名字叫?com.foo.Bar的logger直接和root?logger鏈接,因此繞過(guò)了沒(méi)有被使用的com?或com.foo?loggers。這樣極大地提高了log4j的性能并減少了內(nèi)存(memory)的使用。
通過(guò)調(diào)用BasicConfigurator.configure方法來(lái)配置MyApp類。其它的類只需要引入org.apache.log4j.Logger類,獲取它們想要使用的loggers,就可以輸出?log。
先前的例子總是輸出同樣的log信息。幸運(yùn)的是,很容易修改MyApp程序就可以在程序運(yùn)行時(shí)對(duì)log輸出進(jìn)行控制。下面是略加修改后的版本:
?import?com.foo.Bar;
?import?org.apache.log4j.Logger;
?import?org.apache.log4j.PropertyConfigurator;
?public?class?MyApp?{
???static?Logger?logger?=?Logger.getLogger(MyApp.class.getName());
???public?static?void?main(String[]?args)?{
?????//?BasicConfigurator?replaced?with?PropertyConfigurator.
?????PropertyConfigurator.configure(args[0]);
?????logger.info("Entering?application.");
?????Bar?bar?=?new?Bar();
?????bar.doIt();
?????logger.info("Exiting?application.");
???}
?}
?
這個(gè)例子中MyApp指示PropertyConfigurator方法去解讀配置文件并設(shè)置相應(yīng)的logging?。
這里是一個(gè)配置文件的示例,這個(gè)配置文件產(chǎn)生和前面BasicConfigurator例子完全一樣的輸出結(jié)果:
#?Set?root?logger?level?to?DEBUG?and?its?only?appender?to?A1.
log4j.rootLogger=DEBUG,?A1
#?A1?is?set?to?be?a?ConsoleAppender.
log4j.appender.A1=org.apache.log4j.ConsoleAppender
#?A1?uses?PatternLayout.
log4j.appender.A1.layout=org.apache.log4j.PatternLayout
log4j.appender.A1.layout.ConversionPattern=%-4r?[%t]?%-5p?%c?%x?-?%m%n
?
假設(shè)我們不再需要com.foo軟件包里任何組件的日志輸出,下面的配置文件展示了達(dá)到這一目的的一種可能的方法:
log4j.rootLogger=DEBUG,?A1
log4j.appender.A1=org.apache.log4j.ConsoleAppender
log4j.appender.A1.layout=org.apache.log4j.PatternLayout
#?Print?the?date?in?ISO?8601?format
log4j.appender.A1.layout.ConversionPattern=%d?[%t]?%-5p?%c?-?%m%n
#?Print?only?messages?of?level?WARN?or?above?in?the?package?com.foo.
log4j.logger.com.foo=WARN
?
由這個(gè)文件所配置的MyApp的日志輸出如下:
2000-09-07?14:07:41,508?[main]?INFO??MyApp?-?Entering?application.
2000-09-07?14:07:41,529?[main]?INFO??MyApp?-?Exiting?application.
因?yàn)閘ogger?com.foo.Bar?沒(méi)有指定的優(yōu)先級(jí)別,它就從com.foo中繼承優(yōu)先級(jí)別,而com.foo的優(yōu)先級(jí)別在配置文件中被設(shè)置為WARN。?Bar.doIt?方法里的?log語(yǔ)句的級(jí)別為DEBUG,比WARN級(jí)別低。所以,doIt()方法的日志請(qǐng)求就被壓制住了。
這里是另一個(gè)使用多個(gè)appenders的配置文件。
log4j.rootLogger=debug,?stdout,?R
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
log4j.appender.R=org.apache.log4j.RollingFileAppender
log4j.appender.R.File=example.log
log4j.appender.R.MaxFileSize=100KB
#?Keep?one?backup?file
log4j.appender.R.MaxBackupIndex=1
log4j.appender.R.layout=org.apache.log4j.PatternLayout
log4j.appender.R.layout.ConversionPattern=%p?%t?%c?-?%m%n
?
調(diào)用以這個(gè)配置文件增強(qiáng)了的MyApp會(huì)把下列輸出信息輸出到控制臺(tái)(console)上。
?INFO?[main]?(MyApp2.java:12)?-?Entering?application.
DEBUG?[main]?(Bar.java:8)?-?Doing?it?again!
?INFO?[main]?(MyApp2.java:15)?-?Exiting?application.
另外,當(dāng)root?logger增加了第二個(gè)appender時(shí),log信息將同時(shí)也被輸出到?example.log文件中。當(dāng)example.log文件達(dá)到100KB?后,example.log文件將被rolled?over。當(dāng)roll-over?發(fā)生時(shí),example.log?的老版本將自動(dòng)被移到?example.log.1中去。
注意,要獲得這些不同的logging行為并不需要重新編譯代碼。我們還可以簡(jiǎn)單地通過(guò)修改log配置文件把log信息輸出到UNIX?Syslog?daemon中,把所有?com.foo的日志輸出轉(zhuǎn)指向NT?Event?logger?中,或者把log事件輸出到遠(yuǎn)程?log4j服務(wù)器中,當(dāng)然它要根據(jù)局部服務(wù)器規(guī)則進(jìn)行l(wèi)og,例如可以把log事件輸出到第二個(gè)log4j服務(wù)器中去。
默認(rèn)的初始化過(guò)程
Log4j庫(kù)沒(méi)有對(duì)它的環(huán)境作任何假設(shè)。特別是,沒(méi)有默認(rèn)的log4j?appenders。不過(guò)在一些精細(xì)定義過(guò)的情況下,這個(gè)Logger類的靜態(tài)的initializer會(huì)試圖自動(dòng)配置log4j。?Java語(yǔ)言確保一個(gè)類的靜態(tài)的initializer在這個(gè)類被裝載到內(nèi)存里時(shí)被調(diào)用一次,而且僅僅一次。這點(diǎn)很重要,要記住不同的classloaders會(huì)裝載同一個(gè)類的不同復(fù)制版。這些同一個(gè)類的不同復(fù)制版在JVM看來(lái)是完全不相關(guān)的。
默認(rèn)的初始化在這樣的環(huán)境中很有用處,那就是同一個(gè)程序依據(jù)運(yùn)行時(shí)的環(huán)境作不同用處。例如,同樣一個(gè)程序可以在web-server的控制下作為單獨(dú)的程序,作為一個(gè)applet,或者作為一個(gè)servlet被使用。
默認(rèn)的初始化運(yùn)算法則定義如下:
把log4j.defaultInitOverride的系統(tǒng)屬性設(shè)置為?"false"以外的任何值將會(huì)造成?log4j跳過(guò)默認(rèn)的初始化過(guò)程。
把resource這個(gè)string變量設(shè)置為log4j.configuration系統(tǒng)屬性的值。最好的方法指定默認(rèn)初始化文件是通過(guò)log4j.configuration系統(tǒng)屬性來(lái)指定。在log4j.configuration系統(tǒng)屬性沒(méi)有被定義的情況下,把resource這個(gè)string變量設(shè)置成它的默認(rèn)值"log4j.properties"。
把resource變量轉(zhuǎn)換為一個(gè)URL。
如果這個(gè)resource變量不能轉(zhuǎn)換為一個(gè)URL,例如,因?yàn)?MalformedURLException的緣故,那么就通過(guò)調(diào)用?org.apache.log4j.helpers.Loader.getResource(resource,?Logger.class)在?classpath上搜尋resource,它會(huì)返回一個(gè)URL。注意,?string?"log4j.properties"是一個(gè)不合式的URL。??org.apache.log4j.helpers.Loader.getResource(resource,?Logger.class)
有關(guān)搜尋地址列單,請(qǐng)參看Loader.getResource(java.lang.String)。
如果不能找到URL,那就放棄默認(rèn)的初始化。否則,從URL配置log4j?。
Configurator.html">PropertyConfigurator將被用于解讀URL來(lái)配置log4j,除非這個(gè)URL以".xml"擴(kuò)展符結(jié)束,若這個(gè)URL以".xml"擴(kuò)展符結(jié)束,DOMConfigurator則被使用。你可以選擇性地指定一個(gè)客戶自己的configurator。log4j.configuratorClass系統(tǒng)屬性的值就是你客戶自己的configurator的類名。你指定的客戶configurator必須?實(shí)施Configurator接口。
配置示例
Tomcat下默認(rèn)的初始化
默認(rèn)的log4j初始化在web-server環(huán)境中特別有用。在Tomcat?3.x?and?4.x下,你應(yīng)該把log4j.properties放置在你的網(wǎng)絡(luò)程序的WEB-INF/classes目錄下面。?Log4j自己會(huì)去找到屬性文件并初始化。這樣做又簡(jiǎn)單又有效。
你可以選擇在Tomcat啟動(dòng)之前設(shè)置系統(tǒng)屬性log4j.configuration?。對(duì)于?Tomcat?3.x?,TOMCAT_OPTS?環(huán)境變量被用來(lái)設(shè)置命令行選項(xiàng)。對(duì)于?Tomcat?4.0,使用CATALINA_OPTS環(huán)境變量而不是TOMCAT_OPTS?。
例子
Unix?shell?命令
export?TOMCAT_OPTS="-Dlog4j.configuration=foobar.txt"
告訴log4j?使用文件foobar.txt?作為默認(rèn)的配置文件。這個(gè)文件應(yīng)該被放置在你的網(wǎng)絡(luò)應(yīng)用程序的WEB-INF/classes目錄下面。文件將通過(guò)?PropertyConfigurator被讀取。每個(gè)網(wǎng)絡(luò)應(yīng)用程序使用不同的默認(rèn)配置文件,因?yàn)槊總€(gè)文件都是和每個(gè)網(wǎng)絡(luò)應(yīng)用程序相關(guān)的。
例子
Unix?shell?命令
???export?TOMCAT_OPTS="-Dlog4j.debug?-Dlog4j.configuration=foobar.xml"
告訴log4j輸出log4j-內(nèi)部排錯(cuò)信息,并使用文件foobar.xml?作為默認(rèn)的配置文件。這個(gè)文件應(yīng)該被放置在你的網(wǎng)絡(luò)應(yīng)用程序的WEB-INF/classes目錄下面。因?yàn)槲募?xml擴(kuò)展符結(jié)尾,將使用DOMConfigurator來(lái)讀取。每個(gè)網(wǎng)絡(luò)應(yīng)用程序使用不同的默認(rèn)配置文件,因?yàn)槊總€(gè)文件都是和每個(gè)網(wǎng)絡(luò)應(yīng)用程序相關(guān)的。
例子
Windows?shell?命令
???set?TOMCAT_OPTS=-Dlog4j.configuration=foobar.lcf?-Dlog4j.configuratorClass=com.foo.BarConfigurator
告訴log4j使用文件foobar.lcf?作為默認(rèn)的配置文件。這個(gè)文件應(yīng)該被放置在你的網(wǎng)絡(luò)應(yīng)用程序的WEB-INF/classes?目錄下面。根據(jù)log4j.configuratorClass?系統(tǒng)屬性的定義?,文件將通過(guò)將使用客戶自己的configurator???com.foo.BarConfigurator被讀取。每個(gè)網(wǎng)絡(luò)應(yīng)用程序使用不同的默認(rèn)配置文件,因?yàn)槊總€(gè)文件都是和一個(gè)網(wǎng)絡(luò)應(yīng)用程序相關(guān)的。
例子
Windows?shell?命令
???set?TOMCAT_OPTS=-Dlog4j.configuration=file:/c:/foobar.lcf
告訴log4j使用文件c:\foobar.lcf?作為默認(rèn)的配置文件。這個(gè)配置文件完全由?URL?file:/c:/foobar.lcf指定。因此,這個(gè)相同的配置文件將被所有網(wǎng)絡(luò)應(yīng)用程序使用。??c:\foobar.lcf
不同的網(wǎng)絡(luò)應(yīng)用程序通過(guò)它們各自的classloaders裝載log4j的類。因此,每個(gè)?log4j環(huán)境的image會(huì)獨(dú)自地,沒(méi)有任何相互協(xié)調(diào)地行動(dòng)。例如,在多個(gè)網(wǎng)絡(luò)應(yīng)用程序的配置中,F(xiàn)ileAppenders若定義得完全相同,它們就會(huì)編寫(xiě)相同的文件。這樣的結(jié)果就不那么令人滿意。你必須保證不同的網(wǎng)絡(luò)應(yīng)用程序的log4j配置不使用相同的系統(tǒng)資源。
初始化servlet
還可以使用一個(gè)特別的servlet來(lái)進(jìn)行l(wèi)og4j初始化。這里就是個(gè)示例:
package?com.foo;
import?org.apache.log4j.PropertyConfigurator;
import?javax.servlet.http.HttpServlet;
import?javax.servlet.http.HttpServletRequest;
import?javax.servlet.http.HttpServletResponse;
import?java.io.PrintWriter;
import?java.io.IOException;
public?class?Log4jInit?extends?HttpServlet?{
??public
??void?init()?{
????String?prefix?=??getServletContext().getRealPath("/");
????String?file?=?getInitParameter("log4j-init-file");
????//?if?the?log4j-init-file?is?not?set,?then?no?point?in?trying
????if(file?!=?null)?{
??????PropertyConfigurator.configure(prefix+file);
????}
??}
??public
??void?doGet(HttpServletRequest?req,?HttpServletResponse?res)?{
??}
}
?
在web.xml文件里為你的網(wǎng)絡(luò)應(yīng)用程序定義下面的servlet。
??<servlet>
????<servlet-name>log4j-init</servlet-name>
????<servlet-class>com.foo.Log4jInit</servlet-class>
????<init-param>
??????<param-name>log4j-init-file</param-name>
??????<param-value>WEB-INF/classes/log4j.lcf</param-value>
????</init-param>
????<load-on-startup>1</load-on-startup>
??</servlet>
?
編寫(xiě)一個(gè)initialization?servlet?是最靈活的方式來(lái)初始化log4j。不受任何限制,你可以在這個(gè)servlet的init()方法里放入任何代碼。
Nested?Diagnostic?Contexts
實(shí)際情況下的大多數(shù)系統(tǒng)都需要同時(shí)處理多個(gè)客戶端問(wèn)題。在這種系統(tǒng)的典型的多線程實(shí)施中,通常是不同的線程去分別處理不同的客戶需求。Logging特別適合于復(fù)雜的程序跟蹤和排錯(cuò)。一個(gè)通常的處理辦法是通過(guò)給每個(gè)客戶產(chǎn)生一個(gè)新的分離開(kāi)的logger來(lái)達(dá)到把不同的客戶的日志輸出信息區(qū)分開(kāi)來(lái)。但這促進(jìn)了loggers的增殖,加大了logging的管理負(fù)擔(dān)。
一個(gè)更簡(jiǎn)潔的技術(shù)是獨(dú)特地標(biāo)記來(lái)自于同一個(gè)客戶的每一個(gè)日志請(qǐng)求。Neil?Harrison?在他的書(shū)中"Patterns?for?Logging?Diagnostic?Messages,"?in?Pattern?Languages?of?Program?Design?3,?edited?by?R.?Martin,?D.?Riehle,?and?F.?
Buschmann?(Addison-Wesley,?1997)?對(duì)這個(gè)方法進(jìn)行了描述。??Pattern?Languages?of?Program?Design?3
要獨(dú)特地標(biāo)記每個(gè)日志請(qǐng)求,用戶把上下文信息送入NDC,NDC是?Nested?Diagnostic?Context的縮寫(xiě)。NDC類展示如下。
??public?class?NDC?{
????//?Used?when?printing?the?diagnostic
????public?static?String?get();
????//?Remove?the?top?of?the?context?from?the?NDC.
????public?static?String?pop();
????//?Add?diagnostic?context?for?the?current?thread.
????public?static?void?push(String?message);
????//?Remove?the?diagnostic?context?for?this?thread.
????public?static?void?remove();
??}
NDC類是作為一個(gè)保存線程上下文的stack來(lái)獨(dú)個(gè)線程(per?thread)?管理的。注意,org.apache.log4j.NDC類中所有的方法都是靜態(tài)的。假設(shè)NDC打印功能被打開(kāi),每一次若有日志請(qǐng)求,相應(yīng)的log4j組件就把這個(gè)當(dāng)前線程的整個(gè)?NDC?stack包括在日志輸出中打印出來(lái)。這樣做不需要用戶干預(yù),用戶只需要在代碼中明確指定的幾點(diǎn)通過(guò)push和pop方法將正確的信息放到NDC中就行了。相反,per-client?logger方法需要在代碼中作很多更改。
為了說(shuō)明這一點(diǎn),我們舉個(gè)有關(guān)一個(gè)servlet把信息內(nèi)容發(fā)送到多個(gè)客戶的例子。這個(gè)Servlet程序在開(kāi)始接到客戶端的請(qǐng)求,執(zhí)行其它代碼之前,首先創(chuàng)建一個(gè)NDC。該上下文信息可能是客戶端的主機(jī)名,以及其他請(qǐng)求中固有的信息,通常是包含在cookies中的信息。因此即便這個(gè)Servlet程序可能同時(shí)要服務(wù)于多個(gè)客戶,由相同的代碼啟動(dòng)的這些logs,比如屬于同一個(gè)logger,它們?nèi)匀荒軌虮粎^(qū)分開(kāi)來(lái),因?yàn)椴煌目蛻舳苏?qǐng)求具有不同的NDC?stack。這與在客戶請(qǐng)求期間把一個(gè)實(shí)例化的logger傳遞給所有要被執(zhí)行的代碼的復(fù)雜性形成了反差。
然而,一些復(fù)雜的應(yīng)用程序,比如虛擬網(wǎng)絡(luò)服務(wù)器,必須依據(jù)虛擬主機(jī)的上下文語(yǔ)言環(huán)境,以及發(fā)布請(qǐng)求的軟體組件來(lái)作不同的log。最近的log4j發(fā)行版支持多階層樹(shù)。這一功能的加強(qiáng)允許每個(gè)虛擬主機(jī)擁有它自己的logger階層版本。
性能
一個(gè)經(jīng)常提出的爭(zhēng)議就是logging的運(yùn)算開(kāi)銷。這種關(guān)注是有道理的,因?yàn)榧幢闶且粋€(gè)中等大小的應(yīng)用程序至少也會(huì)產(chǎn)生幾千個(gè)log輸出。許多工作都花費(fèi)在測(cè)量和改進(jìn)logging性能上。Log4j聲明它是快速和靈活的:速度第一,靈活性第二。
用戶需要清楚地了解下面這些與性能相關(guān)的問(wèn)題:
Logging?performance?when?logging?is?turned?off.?
當(dāng)logging被完全關(guān)閉或只是set?of?levels被關(guān)閉,日志請(qǐng)求的開(kāi)銷是方法的調(diào)用和整數(shù)的比較。在一個(gè)233?MHz?Pentium?II機(jī)器上,這種開(kāi)銷通常在5?to?50?毫微秒范圍內(nèi)。??set?of?levels
不過(guò),方法的調(diào)用包含有參數(shù)的建造上的“隱閉”開(kāi)銷。
例如下面的logger?cat程序段中:
?????logger.debug("Entry?number:?"?+?i?+?"?is?"?+?String.valueOf(entry[i]));
????
不管message被日志記錄與否,構(gòu)造message參數(shù)的開(kāi)銷還是有的,比如說(shuō),把整數(shù)i?和數(shù)組entry[i]轉(zhuǎn)化為String,連接中間字串。參數(shù)構(gòu)造的這種開(kāi)銷可能很高,它依賴于所介入的參數(shù)數(shù)量有多少。
為了避免這種參數(shù)構(gòu)造開(kāi)銷,把以上的代碼段改寫(xiě)為:
??????if(logger.isDebugEnabled()?{
????????logger.debug("Entry?number:?"?+?i?+?"?is?"?+?String.valueOf(entry[i]));
??????}
???
如果排錯(cuò)功能不被使用,就不會(huì)有參數(shù)構(gòu)造上的開(kāi)銷。但是,另一方面,如果?logger的排錯(cuò)功能被起用,就會(huì)有倆倍的開(kāi)銷用于評(píng)估logger是否被起用:一次是判斷debugEnabled,一次是判斷debug是否被啟用。但這不是極重的負(fù)擔(dān),因?yàn)樵u(píng)估logger的時(shí)間只有整個(gè)log語(yǔ)句執(zhí)行時(shí)間的1%
在log4j中,把日志請(qǐng)求作為L(zhǎng)ogger類的實(shí)例。Logger是類而不是接口,這主要是為了減少程序調(diào)用的開(kāi)銷,但犧牲了接口所能帶來(lái)的靈活性。
有些用戶使用預(yù)處理或compile-time技術(shù)來(lái)編譯所有l(wèi)og語(yǔ)句。這樣logging方面的性能是很好。但是,因?yàn)閞esulting?application?binary沒(méi)有包含任何log語(yǔ)句,你不能對(duì)這個(gè)二進(jìn)制程序起用logging。在我看來(lái),這是為了小的性能增加而付出大的代價(jià)。
The?performance?of?deciding?whether?to?log?or?not?to?log?when?logging?is?turned?on.?
本質(zhì)上影響性能的因素是logger的層次關(guān)系。當(dāng)logging功能被打開(kāi)時(shí),log4j仍然需要把log請(qǐng)求的級(jí)別去與request?logger的級(jí)別作比較。不過(guò),有些loggers?并沒(méi)有指派的優(yōu)先級(jí)別,但它可以從它的上一層logger那里繼承優(yōu)先級(jí)別。因此在繼承優(yōu)先級(jí)之前,logger可能需要搜索它的ancestors。
Log4j在這方面做了很大的努力,以便使這種階層的優(yōu)先級(jí)別搜尋(hierarchy?walk?)盡可能的快速。例如,子代loggers僅僅只和它們現(xiàn)有的ancestors鏈接。在前面的BasicConfigurator示例中,叫做com.foo.Bar的logger?直接與?root?logger鏈接,繞過(guò)了不存在的com或com.foo?loggers。這極大地提高了優(yōu)先級(jí)別搜尋的速度。
階層的優(yōu)先級(jí)搜尋(walking?the?hierarchy?)的開(kāi)銷在于它比logging完全關(guān)閉時(shí)要慢三倍。
Actually?outputting?log?messages?
這里講的是log輸出的格式化和把log信息發(fā)送到目標(biāo)所在地的開(kāi)銷。Log4j在這方面也下了大力氣讓格式化能盡快執(zhí)行。對(duì)appenders也是一樣。通常情況下,格式化語(yǔ)句的開(kāi)銷可能是100到300微秒的處理時(shí)間。確切數(shù)字請(qǐng)參看?org.apache.log4.performance.Logging?。
盡管log4j具有許多功能特性,但速度是第一設(shè)計(jì)目標(biāo)。為了提高性能,一些?log4j的部件曾經(jīng)被重寫(xiě)過(guò)許多次。即使這樣,log4j的貢獻(xiàn)者們不斷提出新的優(yōu)化辦法。你應(yīng)該很驚喜地發(fā)現(xiàn)當(dāng)以SimpleLayout來(lái)配置時(shí),性能測(cè)試顯示使用?log4j日志和使用System.out.println日志同樣快。
結(jié)論
Log4j是用Java編寫(xiě)的一個(gè)非常流行的logging開(kāi)發(fā)包。它的一個(gè)顯著特性之一是在loggers里運(yùn)用了繼承的概念。使用這種logger的層次關(guān)系,就可能準(zhǔn)確地控制每一個(gè)log語(yǔ)句的輸出。這樣減少了log信息的輸出量并降低了logging的開(kāi)銷。
Log4j?API的優(yōu)點(diǎn)之一是它的可管理性。一旦log語(yǔ)句被插入到代碼中,他們就能被配置文件控制而無(wú)需重新編譯源代碼。Log信息的輸出能夠有選擇地被起用或關(guān)閉,用戶能夠按照自己選擇的格式將這些log信息輸出到許多不同的輸出設(shè)備中。Log4j軟件包的設(shè)計(jì)是在代碼中保留log語(yǔ)句的同時(shí)不造成很大的性能損失。
感謝
Many?thanks?to?N.?Asokan?for?reviewing?the?article.?He?is?also?one?of?the?originators?of?the?logger?concept.?I?am?indebted?to?
Nelson?Minar?for?encouraging?me?to?write?this?article.?He?has?also?made?many?useful?suggestions?and?corrections?to
?this?article.?Log4j?is?the?result?of?a?collective?effort.?My?special?thanks?go?to?all?the?authors?who?have?contributed?to?the
?project.?Without?exception,?the?best?features?in?the?package?have?all?originated?in?the?user?community.?