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è)簡短手冊也借用了The complete log4j manual 里的一些內(nèi)容,The complete log4j manual包含最新的更為詳盡的信息。 The complete log4j manual
這個(gè)文檔資料描述了log4j API,它的獨(dú)特的特性和設(shè)計(jì)原理。Log4j是由許多作者共同參與的開放源代碼項(xiàng)目。它允許開發(fā)人員以任意的精細(xì)程度控制哪些日志說明被輸出。通過使用外部的配置文件,可以在運(yùn)行時(shí)配置它。最好的是,log4j 開發(fā)包很容易上手。注意,它也可能會使一些開發(fā)人員著迷。
幾乎每個(gè)大的應(yīng)用程序都有它自己的日志和跟蹤程序的API。順應(yīng)這一規(guī)則,E.U. SEMPER項(xiàng)目組決定編寫它自己的程序跟蹤API(tracing API)。這開始于1996年早期。經(jīng)過無數(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 語言。
把log statements插入到你的代碼中是一種排錯(cuò)的低技能辦法。這也許是唯一的方法,因?yàn)榕佩e(cuò)工具并不總是可以被使用或者適用于你的程序。對于多線程的應(yīng)用程序和多數(shù)發(fā)行的應(yīng)用程序,通常就是這樣的情形。
經(jīng)驗(yàn)告訴我們logging是開發(fā)過程中重要的一環(huán)。它具有多種優(yōu)點(diǎn)。首先,它能精確地提供運(yùn)行時(shí)的上下文( context )。一旦在程序中加入了Log 代碼,它就能自動(dòng)的生成并輸出logging信息而不需要人為的干預(yù)。另外,log信息的輸出可以被保存到一個(gè)固定的地方,以備以后研究。除了在開發(fā)過程中發(fā)揮它的作用外,一個(gè)性能豐富的日志記錄軟件包能當(dāng)作一個(gè)審計(jì)工具(audit tool)使用。
Brian W. Kernighan 和 Rob Pike 在他們的"The Practice of Programming" 書中這樣寫到: "The Practice of Programming"
作為個(gè)人的選擇,除了得到一大堆程序跟蹤信息或一兩個(gè)變量值以外,我們傾
向於不使用排錯(cuò)器。一個(gè)原因是在詳細(xì)而復(fù)雜的數(shù)據(jù)結(jié)構(gòu)和控制流程中很容易
迷失;我們發(fā)現(xiàn)認(rèn)真思考并在關(guān)鍵處加入自我檢查代碼和輸出指令,比起一步
步看程序要效率高。在日志說明里查找比在明智地放置自我檢查代碼后的輸出
里查找要費(fèi)時(shí)。而決定在哪里放置打印指令要比在日志說明里一步步找到關(guān)鍵
的代碼要省時(shí)間。更重要的是,自我檢查的排錯(cuò)指令和程序并存;而排錯(cuò)
sessions是暫時(shí)的。
Logging確實(shí)也有它的缺陷。它降低了程序運(yùn)行的速度。它太冗長,查看時(shí)很容易錯(cuò)過。為了減少這些負(fù)面影響,log4j 被設(shè)計(jì)得可靠,高效和靈活。因?yàn)椋涗浫罩竞苌偈且粋€(gè)應(yīng)用程序的主要焦點(diǎn),log4j API 盡量做到容易被理解和使用。
Log4j 有三個(gè)主要組件: loggers , appenders 和 layouts 。這三類組件一起應(yīng)用,可以讓開發(fā)人員能夠根據(jù)日志的類型和級別進(jìn)行記錄,并且能在程序運(yùn)行時(shí)控制log信息輸出的格式和往什么地方輸出信息。
任何logging API 與簡單的System.out.println
輸出調(diào)試信息方法比較,最主要的優(yōu)點(diǎn)在于它能夠關(guān)閉一些調(diào)試信息輸出而不影響其他人的調(diào)試。這種能力的實(shí)現(xiàn)是假設(shè)這些logging空間,也就是所有的可能發(fā)生的日志說明空間,可以根據(jù)程序開發(fā)人員選擇的標(biāo)準(zhǔn)進(jìn)行分類。這一觀察以前使得我們選擇了 category 作為這個(gè)軟件包的中心概念。但是,在log4j 1.2版本以后,
類取代了Logger
類。對于那些熟悉早先版本的log4j的開發(fā)人員來說,Logger類只不過是Category類的一個(gè)別名。Category
Loggers是被命名的實(shí)體。Logger的名字大小寫有區(qū)別(case-sensitive),并且它們遵守階層式的命名規(guī)則:
|
例如,叫做"com.foo"的logger是叫做 "com.foo.Bar"的logger的父輩 。同樣地,"java"是"java.util" 的父輩,是"java.util.Vector"的前輩。大多數(shù)開發(fā)人員都熟悉這種命名方法。 "com.foo"
"com.foo.Bar"
"java"
"java.util"
"java.util.Vector"
根(root)logger 位于logger 階層的最上層。它在兩個(gè)方面很特別:
通過這個(gè)類的靜態(tài)方法Logger.getRootLogger得到它(指RootLogger)。所有其他的loggers是通過靜態(tài)方法Logger.getLogger來實(shí)例化并獲取的。這個(gè)方法Logger.getLogger把所想要的logger的名字作為參數(shù)。 Logger類的一些其它基本方法在下面列出:
package org.apache.log4j; |
Loggers 可以 被指派優(yōu)先級別。Level.html#DEBUG">DEBUG, INFO, WARN, ERROR 和FATAL這組級別在org.apache.log4j.Level
類中有定義。你也 可以 通過Level類的子類去定義你自己的優(yōu)先級別,盡管我們不鼓勵(lì)你這樣做。在后面我們會講到一個(gè)更好的方法。
如果一個(gè)logger沒有被指定優(yōu)先級別,它將繼承最接近的祖先所被指定的優(yōu)先級別。下面是更多關(guān)于優(yōu)先級別的信息:
|
要保證所有的loggers最終都繼承一個(gè)優(yōu)先級別,root logger總是有一個(gè)被指派的優(yōu)先級。
下面是具有各種指派優(yōu)先級別值的四個(gè)表格,以及根據(jù)上面的規(guī)則所得出的繼承優(yōu)先級別。
Logger name(名稱) |
指派 級別 |
繼承 級別 |
---|---|---|
根 | Proot | Proot |
X | none | Proot |
X.Y | none | Proot |
X.Y.Z | none | Proot |
在上面的示例1中,只有root logger被指派了級別。這個(gè)級別的值,Proot
,被其它的loggers X, X.Y
和 X.Y.Z
繼承了。
Logger name(名稱) |
指派 級別 |
繼承 級別 |
---|---|---|
根 | Proot | Proot |
X | Px | Px |
X.Y | Pxy | Pxy |
X.Y.Z | Pxyz | Pxyz |
在上面的示例2中,所有的loggers都有一個(gè)指派的級別值。不需要級別繼承。
Logger name(名稱) |
指派 級別 |
繼承 級別 |
---|---|---|
根 | Proot | Proot |
X | Px | Px |
X.Y | none | Px |
X.Y.Z | Pxyz | Pxyz |
在示例3中,loggers root
, X 和
分別被指派級別值X.Y
.ZProot
, Px
和Pxyz
。Logger X.Y 從它的父輩X那里繼承它的級別值。
Logger name(名稱) |
指派 級別 |
繼承 級別 |
---|---|---|
根 | Proot | Proot |
X | Px | Px |
X.Y | none | Px |
X.Y.Z | none | Px |
在示例4中,loggers root
和X 分別被指派級別值Proot
和Px
。Logger X.Y
和X.Y.Z
繼承它們最接近的父輩X的被指派的級別值。
日志請求是通過調(diào)用一個(gè)日志實(shí)例的打印方法(之一)而產(chǎn)生的。這些打印方法是 info, warn, error, fatal 和 log。
根據(jù)定義,打印方法決定一個(gè)日志請求的級別。例如,如果c是一個(gè)日志實(shí)例,那么語句c.info("..") 就是級別為INFO的一個(gè)日志請求。 c.info("..")
只有一個(gè)日志請求(A logging request)的級別高于或等于它的logger級別的時(shí)候才 能夠被執(zhí)行 。否則,則被認(rèn)為這個(gè)日志請求 不能被執(zhí)行 。一個(gè)沒有被定義優(yōu)先級別的logger將從層次關(guān)系中的前輩那里繼承優(yōu)先級別。這個(gè)規(guī)則總結(jié)如下:
|
這個(gè)規(guī)則是log4j的核心。它假設(shè)級別是有先后順序的。對于標(biāo)準(zhǔn)的優(yōu)先級別來說,DEBUG < INFO < WARN < ERROR < FATAL。
這里是一個(gè)關(guān)于這個(gè)規(guī)則的例子:
// get a logger instance named "com.foo" |
以一樣的叁數(shù)名字調(diào)用getLogger方法,返回的reference總是指向完全相同的logger對象。
例如,在這里:
Logger x = Logger.getLogger("wombat"); |
因此,通過這種方式可以配置一個(gè)logger,而不需要傳遞references就能在其他地方得到相同的實(shí)例。在生物的父子關(guān)系中父母總是排放在孩子們前面, log4j loggers與此有相互矛盾的地方,那就是log4j loggers可以以任何順序被產(chǎn)生和配置。特別的是,一個(gè)"parent" logger 會找到并連接他的后代,即使他是在他們之后被定義。
Log4j環(huán)境通常是在程序被初始化的時(shí)候被配置的。最好的方式是通過閱讀一個(gè)配置文件去配置。我們會馬上討論到這方面的內(nèi)容。
Log4j使得通過軟件組件的名稱去定義loggers的名字很容易。這可以通過在每個(gè)類中靜態(tài)地instantiating一個(gè)logger,讓logger的名字與這個(gè)合格的java類文件名相同來完成。這是一種有用并且直觀的定義loggers的方式。因?yàn)槿罩镜妮敵鰩в挟a(chǎn)生它們的logger的名字,這種命名策略使我們能夠很方便地識別這些log信息的來源。不過,盡管這是通用的一種loggers命名策略,Log4j沒有限制怎樣對loggers進(jìn)行命名。開發(fā)程序員可以根據(jù)自己的喜好隨意定義 loggers。 software component
當(dāng)然,至今所知的最好的命名策略還是以它們所在的類的名稱來命名 loggers。
基于自身的logger選擇性地使用或不使用日志請求(logging requests )的能力僅僅整個(gè)Log4j能力的一部分。Log4j允許將log信息輸出到許多不同的輸出設(shè)備中。用log4j的語言來說,一個(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è)被允許的日志請求都會被傳遞給這個(gè)logger的所有appenders,以及階層中高級別的appenders。換句話說appenders是從logger階層中不斷添加地被繼承的。例如,一個(gè) console appender加給了root logger,那么,這個(gè)root logger所有被允許輸出的日志信息將被輸出到console。如果你又給一個(gè)名字為C的logger添加了一個(gè) file appender,那么C 以及C的子輩的所有被允許的日志信息將被同時(shí)輸出到 file appender 和 console appender??梢酝ㄟ^把a(bǔ)dditivity flag設(shè)置為false
來覆蓋這個(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é)如下:
|
下面的表格顯示一個(gè)示例:
Logger name(名稱) |
添加的 Appenders |
Additivity 旗標(biāo) |
輸出目標(biāo) | 注釋 |
---|---|---|---|---|
根 | A1 | not applicable | A1 | Root logger是無名的,但是可以通過Logger.getRootLogger() 來訪問。Root logger沒有附帶默認(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 ,所以沒有appender繼承積累。 |
security.access | none | true | A-sec | 因?yàn)?security" logger里的additivity flag被設(shè)置為false ,所以僅僅只 有"security" logger的appenders。 |
通常,用戶不僅希望自己指定log信息的輸出目的地,而且,他們還希望指定 log信息的輸出格式。這可以通過和appender相關(guān)的 layout 實(shí)現(xiàn)。Layout負(fù)責(zé)根據(jù)用戶的需要去格式化log信息的輸出,而appender負(fù)責(zé)將一個(gè)格式化過的 log信息輸出到它的目的地。PatternLayout 是標(biāo)準(zhǔn)log4j發(fā)行包中的一部分,它讓用戶根據(jù)和C語言中的printf
方法相似的轉(zhuǎn)換模式指定輸出格式。
例如,具有"%r [%t] %-5p %c - %m%n" 轉(zhuǎn)換格式的PatternLayout 將輸出以下的式樣:
176 [main] INFO org.foo.Bar - Located nearest gas station.
第一個(gè)區(qū)域是從程序開始運(yùn)行到輸出日志信息所用的毫秒數(shù)。第二個(gè)區(qū)域是產(chǎn)生日志請求的線程。第三個(gè)區(qū)域是這個(gè)log語句的優(yōu)先級別。第四個(gè)區(qū)域是和日志請求相關(guān)聯(lián)的logger名字。在'-' 之后的文字是這個(gè)log信息的內(nèi)容。
同樣重要的是,log4j 將根據(jù)用戶指定的標(biāo)準(zhǔn)來表達(dá)log信息的內(nèi)容。例如,如果你經(jīng)常需要日志記錄Oranges
,Oranges是你當(dāng)前項(xiàng)目中使用的一個(gè)對象類型,那么你可以注冊一個(gè)OrangeRenderer
,這樣每當(dāng)需要日志記錄一個(gè) orange時(shí),OrangeRenderer就會被調(diào)用。
對象的表達(dá)遵照類階層(class hierarchy)形式。例如,假設(shè)oranges是 fruits,你注冊了一個(gè)
,那么,包括oranges在內(nèi)的所有的fruits 都將由FruitRenderer來表達(dá),除非你自己為orange注冊了一個(gè)特定的 FruitRenderer
OrangeRenderer
。
Object renderers必須實(shí)施ObjectRenderer界面。
在程序代碼中插入這些日志請求需要相當(dāng)大的工作量。調(diào)查顯示,大約%4左右的代碼是logging。因此,即便是中等大小的應(yīng)用程序也需要在它們的代碼中至少包含有幾千行的log語句。就從這個(gè)數(shù)目來看,管理這些log語句而不用人工地去修改它們是十分重要的。
Log4j環(huán)境是完全能夠通過編程來配置的。但是使用配置文件去配置則更靈活。目前,Log4j的配置文件是以XML格式和JAVA properties (key=value) 格式編寫的。
假設(shè)我們有個(gè)叫MyApp
的程序使用log4j,讓我們來看看這是怎樣做到的:
import com.foo.Bar; |
類首先引入log4j的相關(guān)類,然后定義一個(gè)命名為MyApp的靜態(tài)logger變量,而這個(gè)名字恰好和MyApp的類名一樣。MyApp
MyApp
類還使用了被定義在com.foo
包中的Bar
類:
package com.foo; |
通過調(diào)用BasicConfigurator.configure 方法產(chǎn)生一個(gè)相當(dāng)簡單的log4j的設(shè)置。這個(gè)方法將一個(gè) ConsoleAppender添加到root logger,從而讓log信息輸出到 console。通過把PatternLayout設(shè)置為 %-4r [%t] %-5p %c %x - %m%n來確定輸出格式。
注意,默認(rèn)的root logger被指派為Level.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
的對象圖表。
注意,log4j 的子代loggers只和它們現(xiàn)有的前輩鏈接。在這里,名字叫
的logger直接和com
.foo.Barroot
logger鏈接,因此繞過了沒有被使用的com 或com.foo
loggers。這樣極大地提高了log4j的性能并減少了內(nèi)存(memory)的使用。
通過調(diào)用BasicConfigurator.configure
方法來配置MyApp
類。其它的類只需要引入org.apache.log4j.Logger
類,獲取它們想要使用的loggers,就可以輸出 log。
先前的例子總是輸出同樣的log信息。幸運(yùn)的是,很容易修改MyApp
程序就可以在程序運(yùn)行時(shí)對log輸出進(jìn)行控制。下面是略加修改后的版本:
import com.foo.Bar; |
這個(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. |
假設(shè)我們不再需要com.foo
軟件包里任何組件的日志輸出,下面的配置文件展示了達(dá)到這一目的的一種可能的方法:
log4j.rootLogger=DEBUG, A1 |
由這個(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
沒有指定的優(yōu)先級別,它就從com.foo中繼承優(yōu)先級別,而com.foo的優(yōu)先級別在配置文件中被設(shè)置為WARN。 com.foo
.BarBar.doIt
方法里的 log語句的級別為DEBUG,比WARN級別低。所以,doIt()
方法的日志請求就被壓制住了。
這里是另一個(gè)使用多個(gè)appenders的配置文件。
log4j.rootLogger=debug, stdout, R |
調(diào)用以這個(gè)配置文件增強(qiáng)了的MyApp會把下列輸出信息輸出到控制臺(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í)也被輸出到
文件中。當(dāng)example.log文件達(dá)到100KB 后,example.log文件將被rolled over。當(dāng)roll-over 發(fā)生時(shí),example.log 的老版本將自動(dòng)被移到 example.log
example.log.1
中去。
注意,要獲得這些不同的logging行為并不需要重新編譯代碼。我們還可以簡單地通過修改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ù)器中去。
Log4j庫沒有對它的環(huán)境作任何假設(shè)。特別是,沒有默認(rèn)的log4j appenders。不過在一些精細(xì)定義過的情況下,這個(gè)Logger
類的靜態(tài)的initializer會試圖自動(dòng)配置log4j。 Java語言確保一個(gè)類的靜態(tài)的initializer在這個(gè)類被裝載到內(nèi)存里時(shí)被調(diào)用一次,而且僅僅一次。這點(diǎn)很重要,要記住不同的classloaders會裝載同一個(gè)類的不同復(fù)制版。這些同一個(gè)類的不同復(fù)制版在JVM看來是完全不相關(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)算法則定義如下:
resource
這個(gè)string變量設(shè)置為log4j.configuration系統(tǒng)屬性的值。 最好的方法指定默認(rèn)初始化文件是通過log4j.configuration系統(tǒng)屬性來指定。 在log4j.configuration系統(tǒng)屬性沒有被定義的情況下,把resource這個(gè)string變 量設(shè)置成它的默認(rèn)值"log4j.properties"。
resource
變量轉(zhuǎn)換為一個(gè)URL。
resource
變量不能轉(zhuǎn)換為一個(gè)URL,例如,因?yàn)?MalformedURLException
的緣故,那么就通過調(diào)用 org.apache.log4j.helpers.Loader.getResource(resource, Logger.class)在 classpath上搜尋resource,它會返回一個(gè)URL。注意, string "log4j.properties"是一個(gè)不合式的URL。 org.apache.log4j.helpers.Loader.getResource(resource, Logger.class)
有關(guān)搜尋地址列單,請參看Loader.getResource(java.lang.String)。
默認(rèn)的log4j初始化在web-server環(huán)境中特別有用。在Tomcat 3.x and 4.x下,你應(yīng)該把log4j.properties
放置在你的網(wǎng)絡(luò)程序的WEB-INF/classes
目錄下面。 Log4j自己會去找到屬性文件并初始化。這樣做又簡單又有效。
你可以選擇在Tomcat啟動(dòng)之前設(shè)置系統(tǒng)屬性log4j.configuration 。對于 Tomcat 3.x ,
環(huán)境變量被用來設(shè)置命令行選項(xiàng)。對于 Tomcat 4.0,使用TOMCAT_OPTS
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
目錄下面。文件將通過 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來讀取。每個(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)屬性的定義 ,文件將通過將使用客戶自己的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)用程序通過它們各自的classloaders裝載log4j的類。因此,每個(gè) log4j環(huán)境的image會獨(dú)自地,沒有任何相互協(xié)調(diào)地行動(dòng)。例如,在多個(gè)網(wǎng)絡(luò)應(yīng)用程序的配置中,FileAppenders
若定義得完全相同,它們就會編寫相同的文件。這樣的結(jié)果就不那么令人滿意。你必須保證不同的網(wǎng)絡(luò)應(yīng)用程序的log4j配置不使用相同的系統(tǒng)資源。
初始化servlet
還可以使用一個(gè)特別的servlet來進(jìn)行l(wèi)og4j初始化。這里就是個(gè)示例:
package com.foo; |
在web.xml文件里為你的網(wǎng)絡(luò)應(yīng)用程序定義下面的servlet。
<servlet> |
編寫一個(gè)initialization servlet 是最靈活的方式來初始化log4j。不受任何限制,你可以在這個(gè)servlet的init()
方法里放入任何代碼。
實(shí)際情況下的大多數(shù)系統(tǒng)都需要同時(shí)處理多個(gè)客戶端問題。在這種系統(tǒng)的典型的多線程實(shí)施中,通常是不同的線程去分別處理不同的客戶需求。Logging特別適合于復(fù)雜的程序跟蹤和排錯(cuò)。一個(gè)通常的處理辦法是通過給每個(gè)客戶產(chǎn)生一個(gè)新的分離開的logger來達(dá)到把不同的客戶的日志輸出信息區(qū)分開來。但這促進(jìn)了loggers的增殖,加大了logging的管理負(fù)擔(dān)。
一個(gè)更簡潔的技術(shù)是獨(dú)特地標(biāo)記來自于同一個(gè)客戶的每一個(gè)日志請求。Neil Harrison 在他的書中"Patterns for Logging Diagnostic Messages," in Pattern Languages of Program Design 3, edited by R. Martin, D. Riehle, and F. Buschmann (Addison-Wesley, 1997) 對這個(gè)方法進(jìn)行了描述。 Pattern Languages of Program Design 3
要獨(dú)特地標(biāo)記每個(gè)日志請求,用戶把上下文信息送入NDC,NDC是 Nested Diagnostic Context 的縮寫。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 來獨(dú)個(gè)線程(per thread) 管理的。注意,org.apache.log4j.NDC
類中所有的方法都是靜態(tài)的。假設(shè)NDC打印功能被打開,每一次若有日志請求,相應(yīng)的log4j組件就把這個(gè)當(dāng)前線程的 整個(gè) NDC stack包括在日志輸出中打印出來。這樣做不需要用戶干預(yù),用戶只需要在代碼中明確指定的幾點(diǎn)通過push
和pop
方法將正確的信息放到NDC中就行了。相反,per-client logger方法需要在代碼中作很多更改。
為了說明這一點(diǎn),我們舉個(gè)有關(guān)一個(gè)servlet把信息內(nèi)容發(fā)送到多個(gè)客戶的例子。這個(gè)Servlet程序在開始接到客戶端的請求,執(zhí)行其它代碼之前,首先創(chuàng)建一個(gè)NDC。該上下文信息可能是客戶端的主機(jī)名,以及其他請求中固有的信息,通常是包含在cookies中的信息。因此即便這個(gè)Servlet程序可能同時(shí)要服務(wù)于多個(gè)客戶,由相同的代碼啟動(dòng)的這些logs,比如屬于同一個(gè)logger,它們?nèi)匀荒軌虮粎^(qū)分開來,因?yàn)椴煌目蛻舳苏埱缶哂胁煌腘DC stack。這與在客戶請求期間把一個(gè)實(shí)例化的logger傳遞給所有要被執(zhí)行的代碼的復(fù)雜性形成了反差。
然而,一些復(fù)雜的應(yīng)用程序,比如虛擬網(wǎng)絡(luò)服務(wù)器,必須依據(jù)虛擬主機(jī)的上下文語言環(huán)境,以及發(fā)布請求的軟體組件來作不同的log。最近的log4j發(fā)行版支持多階層樹。這一功能的加強(qiáng)允許每個(gè)虛擬主機(jī)擁有它自己的logger階層版本。
一個(gè)經(jīng)常提出的爭議就是logging的運(yùn)算開銷。這種關(guān)注是有道理的,因?yàn)榧幢闶且粋€(gè)中等大小的應(yīng)用程序至少也會產(chǎn)生幾千個(gè)log輸出。許多工作都花費(fèi)在測量和改進(jìn)logging性能上。Log4j聲明它是快速和靈活的:速度第一,靈活性第二。
用戶需要清楚地了解下面這些與性能相關(guān)的問題:
當(dāng)logging被完全關(guān)閉或只是set of levels被關(guān)閉,日志請求的開銷是方法的調(diào) 用和整數(shù)的比較。在一個(gè)233 MHz Pentium II機(jī)器上,這種開銷通常在5 to 50 毫微秒范圍內(nèi)。 set of levels
不過,方法的調(diào)用包含有參數(shù)的建造上的“隱閉”開銷。
例如下面的logger cat
程序段中:
logger.debug("Entry number: " + i + " is " + String.valueOf(entry[i]));不管message被日志記錄與否,構(gòu)造message參數(shù)的開銷還是有的,比如說, 把整數(shù)i 和數(shù)組
entry[i]
轉(zhuǎn)化為String,連接中間字串。參數(shù)構(gòu)造的這種開銷可能 很高,它依賴于所介入的參數(shù)數(shù)量有多少。
為了避免這種參數(shù)構(gòu)造開銷,把以上的代碼段改寫為:
if(logger.isDebugEnabled() {
logger.debug("Entry number: " + i + " is " + String.valueOf(entry[i]));
}
如果排錯(cuò)功能不被使用,就不會有參數(shù)構(gòu)造上的開銷。但是,另一方面,如果 logger的排錯(cuò)功能被起用,就會有倆倍的開銷用于評估logger是否被起用:一 次是判斷
,一次是判斷debug是否被啟用。但這不是極重的負(fù) 擔(dān),因?yàn)樵u估logger的時(shí)間只有整個(gè)log語句執(zhí)行時(shí)間的1%debug
Enabled
在log4j中,把日志請求作為Logger類的實(shí)例。Logger是類而不是接口,這主 要是為了減少程序調(diào)用的開銷,但犧牲了接口所能帶來的靈活性。
有些用戶使用預(yù)處理或compile-time技術(shù)來編譯所有l(wèi)og語句。這樣logging方面 的性能是很好。但是,因?yàn)閞esulting application binary沒有包含任何log語 句,你不能對這個(gè)二進(jìn)制程序起用logging。在我看來,這是為了小的性能增加 而付出大的代價(jià)。
本質(zhì)上影響性能的因素是logger的層次關(guān)系。當(dāng)logging功能被打開時(shí),log4j仍 然需要把log請求的級別去與request logger的級別作比較。不過,有些loggers 并沒有指派的優(yōu)先級別,但它可以從它的上一層logger那里繼承優(yōu)先級別。因 此在繼承優(yōu)先級之前,logger可能需要搜索它的ancestors。
Log4j在這方面做了很大的努力,以便使這種階層的優(yōu)先級別搜尋(hierarchy walk )盡可能的快速。例如,子代loggers僅僅只和它們現(xiàn)有的ancestors鏈 接。在前面的BasicConfigurator
示例中,叫做
的logger 直接與 root logger鏈接,繞過了不存在的com或com
.foo.Barcom.foo
loggers。這極大地提高了優(yōu) 先級別搜尋的速度。
階層的優(yōu)先級搜尋(walking the hierarchy )的開銷在于它比logging完全關(guān)閉 時(shí)要慢三倍。
這里講的是log輸出的格式化和把log信息發(fā)送到目標(biāo)所在地的開銷。Log4j在這 方面也下了大力氣讓格式化能盡快執(zhí)行。對appenders也是一樣。通常情況 下,格式化語句的開銷可能是100到300微秒的處理時(shí)間。確切數(shù)字請參看 org.apache.log4.performance.Logging 。
盡管log4j具有許多功能特性,但速度是第一設(shè)計(jì)目標(biāo)。為了提高性能,一些 log4j的部件曾經(jīng)被重寫過許多次。即使這樣,log4j的貢獻(xiàn)者們不斷提出新的優(yōu)化辦法。你應(yīng)該很驚喜地發(fā)現(xiàn)當(dāng)以SimpleLayout來配置時(shí),性能測試顯示使用 log4j日志和使用System.out.println
日志同樣快。
Log4j是用Java編寫的一個(gè)非常流行的logging開發(fā)包。它的一個(gè)顯著特性之一是在loggers里運(yùn)用了繼承的概念。使用這種logger的層次關(guān)系,就可能準(zhǔn)確地控制每一個(gè)log語句的輸出。這樣減少了log信息的輸出量并降低了logging的開銷。
Log4j API的優(yōu)點(diǎn)之一是它的可管理性。一旦log語句被插入到代碼中,他們就能被配置文件控制而無需重新編譯源代碼。Log信息的輸出能夠有選擇地被起用或關(guān)閉,用戶能夠按照自己選擇的格式將這些log信息輸出到許多不同的輸出設(shè)備中。Log4j軟件包的設(shè)計(jì)是在代碼中保留log語句的同時(shí)不造成很大的性能損失。