Copyright© 2000-2004 The Apache Software Foundation. 版權所有。Log4j軟件是在遵守Apache Software License 1.1版的條例下發(fā)行的,Apache Software License的復制件被包括在log4j發(fā)布的LICENSE.txt文件里。這個(gè)簡(jiǎn)短手冊也借用了The complete log4j manual 里的一些內容,The complete log4j manual包含最新的更為詳盡的信息。 The complete log4j manual
這個(gè)文檔資料描述了log4j API,它的獨特的特性和設計原理。Log4j是由許多作者共同參與的開(kāi)放源代碼項目。它允許開(kāi)發(fā)人員以任意的精細程度控制哪些日志說(shuō)明被輸出。通過(guò)使用外部的配置文件,可以在運行時(shí)配置它。最好的是,log4j 開(kāi)發(fā)包很容易上手。注意,它也可能會(huì )使一些開(kāi)發(fā)人員著(zhù)迷。
幾乎每個(gè)大的應用程序都有它自己的日志和跟蹤程序的API。順應這一規則,E.U. SEMPER項目組決定編寫(xiě)它自己的程序跟蹤API(tracing API)。這開(kāi)始于1996年早期。經(jīng)過(guò)無(wú)數的工作,更改和性能加強,這個(gè)API終于成為一個(gè)十分受歡迎的Java日志軟件包,那就是log4j。這個(gè)軟件包的發(fā)行遵守open source動(dòng)議認證的Apache Software License。最新的log4j版本包括全部的源代碼,類(lèi)文件和文檔資料,可以在 http://logging.apache.org/log4j/找到它們。另外,log4j已經(jīng)被轉換成 C, C++, C#, Perl, Python, Ruby, 和 Eiffel 語(yǔ)言。
把log statements插入到你的代碼中是一種排錯的低技能辦法。這也許是唯一的方法,因為排錯工具并不總是可以被使用或者適用于你的程序。對于多線(xiàn)程的應用程序和多數發(fā)行的應用程序,通常就是這樣的情形。
經(jīng)驗告訴我們logging是開(kāi)發(fā)過(guò)程中重要的一環(huán)。它具有多種優(yōu)點(diǎn)。首先,它能精確地提供運行時(shí)的上下文( context )。一旦在程序中加入了Log 代碼,它就能自動(dòng)的生成并輸出logging信息而不需要人為的干預。另外,log信息的輸出可以被保存到一個(gè)固定的地方,以備以后研究。除了在開(kāi)發(fā)過(guò)程中發(fā)揮它的作用外,一個(gè)性能豐富的日志記錄軟件包能當作一個(gè)審計工具(audit tool)使用。
Brian W. Kernighan 和 Rob Pike 在他們的"The Practice of Programming" 書(shū)中這樣寫(xiě)到: "The Practice of Programming"
作為個(gè)人的選擇,除了得到一大堆程序跟蹤信息或一兩個(gè)變量值以外,我們傾
向於不使用排錯器。一個(gè)原因是在詳細而復雜的數據結構和控制流程中很容易
迷失;我們發(fā)現認真思考并在關(guān)鍵處加入自我檢查代碼和輸出指令,比起一步
步看程序要效率高。在日志說(shuō)明里查找比在明智地放置自我檢查代碼后的輸出
里查找要費時(shí)。而決定在哪里放置打印指令要比在日志說(shuō)明里一步步找到關(guān)鍵
的代碼要省時(shí)間。更重要的是,自我檢查的排錯指令和程序并存;而排錯
sessions是暫時(shí)的。
Logging確實(shí)也有它的缺陷。它降低了程序運行的速度。它太冗長(cháng),查看時(shí)很容易錯過(guò)。為了減少這些負面影響,log4j 被設計得可靠,高效和靈活。因為,記錄日志很少是一個(gè)應用程序的主要焦點(diǎn),log4j API 盡量做到容易被理解和使用。
Log4j 有三個(gè)主要組件: loggers , appenders 和 layouts 。這三類(lèi)組件一起應用,可以讓開(kāi)發(fā)人員能夠根據日志的類(lèi)型和級別進(jìn)行記錄,并且能在程序運行時(shí)控制log信息輸出的格式和往什么地方輸出信息。
任何logging API 與簡(jiǎn)單的System.out.println輸出調試信息方法比較,最主要的優(yōu)點(diǎn)在于它能夠關(guān)閉一些調試信息輸出而不影響其他人的調試。這種能力的實(shí)現是假設這些logging空間,也就是所有的可能發(fā)生的日志說(shuō)明空間,可以根據程序開(kāi)發(fā)人員選擇的標準進(jìn)行分類(lèi)。這一觀(guān)察以前使得我們選擇了 category 作為這個(gè)軟件包的中心概念。但是,在log4j 1.2版本以后,類(lèi)取代了Logger類(lèi)。對于那些熟悉早先版本的log4j的開(kāi)發(fā)人員來(lái)說(shuō),Logger類(lèi)只不過(guò)是Category類(lèi)的一個(gè)別名。Category
Loggers是被命名的實(shí)體。Logger的名字大小寫(xiě)有區別(case-sensitive),并且它們遵守階層式的命名規則:
|
例如,叫做"com.foo"的logger是叫做 "com.foo.Bar"的logger的父輩 。同樣地,"java"是"java.util" 的父輩,是"java.util.Vector"的前輩。大多數開(kāi)發(fā)人員都熟悉這種命名方法。 "com.foo" "com.foo.Bar" "java" "java.util" "java.util.Vector"
根(root)logger 位于logger 階層的最上層。它在兩個(gè)方面很特別:
通過(guò)這個(gè)類(lèi)的靜態(tài)方法Logger.getRootLogger得到它(指RootLogger)。所有其他的loggers是通過(guò)靜態(tài)方法Logger.getLogger來(lái)實(shí)例化并獲取的。這個(gè)方法Logger.getLogger把所想要的logger的名字作為參數。 Logger類(lèi)的一些其它基本方法在下面列出:
package org.apache.log4j; |
Loggers 可以 被指派優(yōu)先級別。Level.html#DEBUG">DEBUG, INFO, WARN, ERROR 和FATAL這組級別在org.apache.log4j.Level類(lèi)中有定義。你也 可以 通過(guò)Level類(lèi)的子類(lèi)去定義你自己的優(yōu)先級別,盡管我們不鼓勵你這樣做。在后面我們會(huì )講到一個(gè)更好的方法。
如果一個(gè)logger沒(méi)有被指定優(yōu)先級別,它將繼承最接近的祖先所被指定的優(yōu)先級別。下面是更多關(guān)于優(yōu)先級別的信息:
|
要保證所有的loggers最終都繼承一個(gè)優(yōu)先級別,root logger總是有一個(gè)被指派的優(yōu)先級。
下面是具有各種指派優(yōu)先級別值的四個(gè)表格,以及根據上面的規則所得出的繼承優(yōu)先級別。
| Logger name(名稱(chēng)) |
指派 級別 |
繼承 級別 |
|---|---|---|
| 根 | 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(名稱(chēng)) |
指派 級別 |
繼承 級別 |
|---|---|---|
| 根 | Proot | Proot |
| X | Px | Px |
| X.Y | Pxy | Pxy |
| X.Y.Z | Pxyz | Pxyz |
在上面的示例2中,所有的loggers都有一個(gè)指派的級別值。不需要級別繼承。
| Logger name(名稱(chēng)) |
指派 級別 |
繼承 級別 |
|---|---|---|
| 根 | 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(名稱(chēng)) |
指派 級別 |
繼承 級別 |
|---|---|---|
| 根 | 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的被指派的級別值。
日志請求是通過(guò)調用一個(gè)日志實(shí)例的打印方法(之一)而產(chǎn)生的。這些打印方法是 info, warn, error, fatal 和 log。
根據定義,打印方法決定一個(gè)日志請求的級別。例如,如果c是一個(gè)日志實(shí)例,那么語(yǔ)句c.info("..") 就是級別為INFO的一個(gè)日志請求。 c.info("..")
只有一個(gè)日志請求(A logging request)的級別高于或等于它的logger級別的時(shí)候才 能夠被執行 。否則,則被認為這個(gè)日志請求 不能被執行 。一個(gè)沒(méi)有被定義優(yōu)先級別的logger將從層次關(guān)系中的前輩那里繼承優(yōu)先級別。這個(gè)規則總結如下:
|
這個(gè)規則是log4j的核心。它假設級別是有先后順序的。對于標準的優(yōu)先級別來(lái)說(shuō),DEBUG < INFO < WARN < ERROR < FATAL。
這里是一個(gè)關(guān)于這個(gè)規則的例子:
// get a logger instance named "com.foo" |
以一樣的叁數名字調用getLogger方法,返回的reference總是指向完全相同的logger對象。
例如,在這里:
Logger x = Logger.getLogger("wombat");
|
因此,通過(guò)這種方式可以配置一個(gè)logger,而不需要傳遞references就能在其他地方得到相同的實(shí)例。在生物的父子關(guān)系中父母總是排放在孩子們前面, log4j loggers與此有相互矛盾的地方,那就是log4j loggers可以以任何順序被產(chǎn)生和配置。特別的是,一個(gè)"parent" logger 會(huì )找到并連接他的后代,即使他是在他們之后被定義。
Log4j環(huán)境通常是在程序被初始化的時(shí)候被配置的。最好的方式是通過(guò)閱讀一個(gè)配置文件去配置。我們會(huì )馬上討論到這方面的內容。
Log4j使得通過(guò)軟件組件的名稱(chēng)去定義loggers的名字很容易。這可以通過(guò)在每個(gè)類(lèi)中靜態(tài)地instantiating一個(gè)logger,讓logger的名字與這個(gè)合格的java類(lèi)文件名相同來(lái)完成。這是一種有用并且直觀(guān)的定義loggers的方式。因為日志的輸出帶有產(chǎn)生它們的logger的名字,這種命名策略使我們能夠很方便地識別這些log信息的來(lái)源。不過(guò),盡管這是通用的一種loggers命名策略,Log4j沒(méi)有限制怎樣對loggers進(jìn)行命名。開(kāi)發(fā)程序員可以根據自己的喜好隨意定義 loggers。 software component
當然,至今所知的最好的命名策略還是以它們所在的類(lèi)的名稱(chēng)來(lái)命名 loggers。
基于自身的logger選擇性地使用或不使用日志請求(logging requests )的能力僅僅整個(gè)Log4j能力的一部分。Log4j允許將log信息輸出到許多不同的輸出設備中。用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è)輸出設備中。 NT Event Loggers
多個(gè)appenders可以和一個(gè)logger連接在一起。
使用addAppender方法把一個(gè)appender加入到給定的logger上。一個(gè)給定的 logger的每一個(gè)被允許的日志請求都會(huì )被傳遞給這個(gè)logger的所有appenders,以及階層中高級別的appenders。換句話(huà)說(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ò)把additivity flag設置為false來(lái)覆蓋這個(gè)默認的行為從而使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添加性的規則總結如下:
|
下面的表格顯示一個(gè)示例:
| Logger name(名稱(chēng)) |
添加的 Appenders |
Additivity 旗標 |
輸出目標 | 注釋 |
|---|---|---|---|---|
| 根 | A1 | not applicable | A1 | Root logger是無(wú)名的,但是可以通過(guò)Logger.getRootLogger() 來(lái)訪(fǎng)問(wèn)。Root logger沒(méi)有附帶默認的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 | 因為additivity flag被設置為 false,所以沒(méi)有appender繼承積累。 |
| security.access | none | true | A-sec | 因為"security" logger里的additivity flag被設置為false,所以?xún)H僅只 有"security" logger的appenders。 |
通常,用戶(hù)不僅希望自己指定log信息的輸出目的地,而且,他們還希望指定 log信息的輸出格式。這可以通過(guò)和appender相關(guān)的 layout 實(shí)現。Layout負責根據用戶(hù)的需要去格式化log信息的輸出,而appender負責將一個(gè)格式化過(guò)的 log信息輸出到它的目的地。PatternLayout 是標準log4j發(fā)行包中的一部分,它讓用戶(hù)根據和C語(yǔ)言中的printf方法相似的轉換模式指定輸出格式。
例如,具有"%r [%t] %-5p %c - %m%n" 轉換格式的PatternLayout 將輸出以下的式樣:
176 [main] INFO org.foo.Bar - Located nearest gas station.
第一個(gè)區域是從程序開(kāi)始運行到輸出日志信息所用的毫秒數。第二個(gè)區域是產(chǎn)生日志請求的線(xiàn)程。第三個(gè)區域是這個(gè)log語(yǔ)句的優(yōu)先級別。第四個(gè)區域是和日志請求相關(guān)聯(lián)的logger名字。在'-' 之后的文字是這個(gè)log信息的內容。
同樣重要的是,log4j 將根據用戶(hù)指定的標準來(lái)表達log信息的內容。例如,如果你經(jīng)常需要日志記錄Oranges,Oranges是你當前項目中使用的一個(gè)對象類(lèi)型,那么你可以注冊一個(gè)OrangeRenderer,這樣每當需要日志記錄一個(gè) orange時(shí),OrangeRenderer就會(huì )被調用。
對象的表達遵照類(lèi)階層(class hierarchy)形式。例如,假設oranges是 fruits,你注冊了一個(gè),那么,包括oranges在內的所有的fruits 都將由FruitRenderer來(lái)表達,除非你自己為orange注冊了一個(gè)特定的 FruitRendererOrangeRenderer。
Object renderers必須實(shí)施ObjectRenderer界面。
在程序代碼中插入這些日志請求需要相當大的工作量。調查顯示,大約%4左右的代碼是logging。因此,即便是中等大小的應用程序也需要在它們的代碼中至少包含有幾千行的log語(yǔ)句。就從這個(gè)數目來(lái)看,管理這些log語(yǔ)句而不用人工地去修改它們是十分重要的。
Log4j環(huán)境是完全能夠通過(guò)編程來(lái)配置的。但是使用配置文件去配置則更靈活。目前,Log4j的配置文件是以XML格式和JAVA properties (key=value) 格式編寫(xiě)的。
假設我們有個(gè)叫MyApp的程序使用log4j,讓我們來(lái)看看這是怎樣做到的:
import com.foo.Bar; |
類(lèi)首先引入log4j的相關(guān)類(lèi),然后定義一個(gè)命名為MyApp的靜態(tài)logger變量,而這個(gè)名字恰好和MyApp的類(lèi)名一樣。MyApp
MyApp類(lèi)還使用了被定義在com.foo包中的Bar類(lèi):
package com.foo; |
通過(guò)調用BasicConfigurator.configure 方法產(chǎn)生一個(gè)相當簡(jiǎn)單的log4j的設置。這個(gè)方法將一個(gè) ConsoleAppender添加到root logger,從而讓log信息輸出到 console。通過(guò)把PatternLayout設置為 %-4r [%t] %-5p %c %x - %m%n來(lái)確定輸出格式。
注意,默認的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.
下面的圖形描繪了在調用BasicConfigurator.configure方法之后,MyApp的對象圖表。

注意,log4j 的子代loggers只和它們現有的前輩鏈接。在這里,名字叫 的logger直接和com.foo.Barroot logger鏈接,因此繞過(guò)了沒(méi)有被使用的com 或com.foo loggers。這樣極大地提高了log4j的性能并減少了內存(memory)的使用。
通過(guò)調用BasicConfigurator.configure方法來(lái)配置MyApp類(lèi)。其它的類(lèi)只需要引入org.apache.log4j.Logger類(lèi),獲取它們想要使用的loggers,就可以輸出 log。
先前的例子總是輸出同樣的log信息。幸運的是,很容易修改MyApp程序就可以在程序運行時(shí)對log輸出進(jìn)行控制。下面是略加修改后的版本:
import com.foo.Bar; |
這個(gè)例子中MyApp指示PropertyConfigurator方法去解讀配置文件并設置相應的logging 。
這里是一個(gè)配置文件的示例,這個(gè)配置文件產(chǎn)生和前面BasicConfigurator例子完全一樣的輸出結果:
# Set root logger level to DEBUG and its only appender to A1. |
假設我們不再需要com.foo軟件包里任何組件的日志輸出,下面的配置文件展示了達到這一目的的一種可能的方法:
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.
因為logger 沒(méi)有指定的優(yōu)先級別,它就從com.foo中繼承優(yōu)先級別,而com.foo的優(yōu)先級別在配置文件中被設置為WARN。 com.foo.BarBar.doIt 方法里的 log語(yǔ)句的級別為DEBUG,比WARN級別低。所以,doIt()方法的日志請求就被壓制住了。
這里是另一個(gè)使用多個(gè)appenders的配置文件。
log4j.rootLogger=debug, stdout, R |
調用以這個(gè)配置文件增強了的MyApp會(huì )把下列輸出信息輸出到控制臺(console)上。
INFO [main] (MyApp2.java:12) - Entering application.
DEBUG [main] (Bar.java:8) - Doing it again!
INFO [main] (MyApp2.java:15) - Exiting application.
另外,當root logger增加了第二個(gè)appender時(shí),log信息將同時(shí)也被輸出到 文件中。當example.log文件達到100KB 后,example.log文件將被rolled over。當roll-over 發(fā)生時(shí),example.log 的老版本將自動(dòng)被移到 example.logexample.log.1中去。
注意,要獲得這些不同的logging行為并不需要重新編譯代碼。我們還可以簡(jiǎn)單地通過(guò)修改log配置文件把log信息輸出到UNIX Syslog daemon中,把所有 com.foo的日志輸出轉指向NT Event logger 中,或者把log事件輸出到遠程 log4j服務(wù)器中,當然它要根據局部服務(wù)器規則進(jìn)行log,例如可以把log事件輸出到第二個(gè)log4j服務(wù)器中去。
Log4j庫沒(méi)有對它的環(huán)境作任何假設。特別是,沒(méi)有默認的log4j appenders。不過(guò)在一些精細定義過(guò)的情況下,這個(gè)Logger類(lèi)的靜態(tài)的initializer會(huì )試圖自動(dòng)配置log4j。 Java語(yǔ)言確保一個(gè)類(lèi)的靜態(tài)的initializer在這個(gè)類(lèi)被裝載到內存里時(shí)被調用一次,而且僅僅一次。這點(diǎn)很重要,要記住不同的classloaders會(huì )裝載同一個(gè)類(lèi)的不同復制版。這些同一個(gè)類(lèi)的不同復制版在JVM看來(lái)是完全不相關(guān)的。
默認的初始化在這樣的環(huán)境中很有用處,那就是同一個(gè)程序依據運行時(shí)的環(huán)境作不同用處。例如,同樣一個(gè)程序可以在web-server的控制下作為單獨的程序,作為一個(gè)applet,或者作為一個(gè)servlet被使用。
默認的初始化運算法則定義如下:
resource這個(gè)string變量設置為log4j.configuration系統屬性的值。 最好的方法指定默認初始化文件是通過(guò)log4j.configuration系統屬性來(lái)指定。 在log4j.configuration系統屬性沒(méi)有被定義的情況下,把resource這個(gè)string變 量設置成它的默認值"log4j.properties"。
resource變量轉換為一個(gè)URL。
resource變量不能轉換為一個(gè)URL,例如,因為 MalformedURLException的緣故,那么就通過(guò)調用 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)搜尋地址列單,請參看Loader.getResource(java.lang.String)。
默認的log4j初始化在web-server環(huán)境中特別有用。在Tomcat 3.x and 4.x下,你應該把log4j.properties放置在你的網(wǎng)絡(luò )程序的WEB-INF/classes目錄下面。 Log4j自己會(huì )去找到屬性文件并初始化。這樣做又簡(jiǎn)單又有效。
你可以選擇在Tomcat啟動(dòng)之前設置系統屬性log4j.configuration 。對于 Tomcat 3.x , 環(huán)境變量被用來(lái)設置命令行選項。對于 Tomcat 4.0,使用TOMCAT_OPTSCATALINA_OPTS環(huán)境變量而不是TOMCAT_OPTS 。
例子
Unix shell 命令
export TOMCAT_OPTS="-Dlog4j.configuration=foobar.txt"告訴log4j 使用文件
foobar.txt 作為默認的配置文件。這個(gè)文件應該被放置在你的網(wǎng)絡(luò )應用程序的WEB-INF/classes目錄下面。文件將通過(guò) PropertyConfigurator被讀取。每個(gè)網(wǎng)絡(luò )應用程序使用不同的默認配置文件,因為每個(gè)文件都是和每個(gè)網(wǎng)絡(luò )應用程序相關(guān)的。
例子
Unix shell 命令
export TOMCAT_OPTS="-Dlog4j.debug -Dlog4j.configuration=foobar.xml"告訴log4j輸出log4j-內部排錯信息,并使用文件
foobar.xml 作為默認的配置文件。這個(gè)文件應該被放置在你的網(wǎng)絡(luò )應用程序的WEB-INF/classes目錄下面。因為文件以.xml擴展符結尾,將使用DOMConfigurator來(lái)讀取。每個(gè)網(wǎng)絡(luò )應用程序使用不同的默認配置文件,因為每個(gè)文件都是和每個(gè)網(wǎng)絡(luò )應用程序相關(guān)的。
例子
Windows shell 命令
set TOMCAT_OPTS=-Dlog4j.configuration=foobar.lcf -Dlog4j.configuratorClass=com.foo.BarConfigurator告訴log4j使用文件
foobar.lcf 作為默認的配置文件。這個(gè)文件應該被放置在你的網(wǎng)絡(luò )應用程序的WEB-INF/classes 目錄下面。根據log4j.configuratorClass 系統屬性的定義 ,文件將通過(guò)將使用客戶(hù)自己的configurator—— com.foo.BarConfigurator被讀取。每個(gè)網(wǎng)絡(luò )應用程序使用不同的默認配置文件,因為每個(gè)文件都是和一個(gè)網(wǎng)絡(luò )應用程序相關(guān)的。
例子
Windows shell 命令
set TOMCAT_OPTS=-Dlog4j.configuration=file:/c:/foobar.lcf告訴log4j使用文件c:\foobar.lcf 作為默認的配置文件。這個(gè)配置文件完全由 URL
file:/c:/foobar.lcf指定。因此,這個(gè)相同的配置文件將被所有網(wǎng)絡(luò )應用程序使用。 c:\foobar.lcf
不同的網(wǎng)絡(luò )應用程序通過(guò)它們各自的classloaders裝載log4j的類(lèi)。因此,每個(gè) log4j環(huán)境的image會(huì )獨自地,沒(méi)有任何相互協(xié)調地行動(dòng)。例如,在多個(gè)網(wǎng)絡(luò )應用程序的配置中,FileAppenders若定義得完全相同,它們就會(huì )編寫(xiě)相同的文件。這樣的結果就不那么令人滿(mǎn)意。你必須保證不同的網(wǎng)絡(luò )應用程序的log4j配置不使用相同的系統資源。
初始化servlet
還可以使用一個(gè)特別的servlet來(lái)進(jìn)行log4j初始化。這里就是個(gè)示例:
package com.foo; |
在web.xml文件里為你的網(wǎng)絡(luò )應用程序定義下面的servlet。
<servlet> |
編寫(xiě)一個(gè)initialization servlet 是最靈活的方式來(lái)初始化log4j。不受任何限制,你可以在這個(gè)servlet的init()方法里放入任何代碼。
實(shí)際情況下的大多數系統都需要同時(shí)處理多個(gè)客戶(hù)端問(wèn)題。在這種系統的典型的多線(xiàn)程實(shí)施中,通常是不同的線(xiàn)程去分別處理不同的客戶(hù)需求。Logging特別適合于復雜的程序跟蹤和排錯。一個(gè)通常的處理辦法是通過(guò)給每個(gè)客戶(hù)產(chǎn)生一個(gè)新的分離開(kāi)的logger來(lái)達到把不同的客戶(hù)的日志輸出信息區分開(kāi)來(lái)。但這促進(jìn)了loggers的增殖,加大了logging的管理負擔。
一個(gè)更簡(jiǎn)潔的技術(shù)是獨特地標記來(lái)自于同一個(gè)客戶(hù)的每一個(gè)日志請求。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) 對這個(gè)方法進(jìn)行了描述。 Pattern Languages of Program Design 3
要獨特地標記每個(gè)日志請求,用戶(hù)把上下文信息送入NDC,NDC是 Nested Diagnostic Context 的縮寫(xiě)。NDC類(lèi)展示如下。
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類(lèi)是作為一個(gè)保存線(xiàn)程上下文的 stack 來(lái)獨個(gè)線(xiàn)程(per thread) 管理的。注意,org.apache.log4j.NDC類(lèi)中所有的方法都是靜態(tài)的。假設NDC打印功能被打開(kāi),每一次若有日志請求,相應的log4j組件就把這個(gè)當前線(xiàn)程的 整個(gè) NDC stack包括在日志輸出中打印出來(lái)。這樣做不需要用戶(hù)干預,用戶(hù)只需要在代碼中明確指定的幾點(diǎn)通過(guò)push和pop方法將正確的信息放到NDC中就行了。相反,per-client logger方法需要在代碼中作很多更改。
為了說(shuō)明這一點(diǎn),我們舉個(gè)有關(guān)一個(gè)servlet把信息內容發(fā)送到多個(gè)客戶(hù)的例子。這個(gè)Servlet程序在開(kāi)始接到客戶(hù)端的請求,執行其它代碼之前,首先創(chuàng )建一個(gè)NDC。該上下文信息可能是客戶(hù)端的主機名,以及其他請求中固有的信息,通常是包含在cookies中的信息。因此即便這個(gè)Servlet程序可能同時(shí)要服務(wù)于多個(gè)客戶(hù),由相同的代碼啟動(dòng)的這些logs,比如屬于同一個(gè)logger,它們仍然能夠被區分開(kāi)來(lái),因為不同的客戶(hù)端請求具有不同的NDC stack。這與在客戶(hù)請求期間把一個(gè)實(shí)例化的logger傳遞給所有要被執行的代碼的復雜性形成了反差。
然而,一些復雜的應用程序,比如虛擬網(wǎng)絡(luò )服務(wù)器,必須依據虛擬主機的上下文語(yǔ)言環(huán)境,以及發(fā)布請求的軟體組件來(lái)作不同的log。最近的log4j發(fā)行版支持多階層樹(shù)。這一功能的加強允許每個(gè)虛擬主機擁有它自己的logger階層版本。
一個(gè)經(jīng)常提出的爭議就是logging的運算開(kāi)銷(xiāo)。這種關(guān)注是有道理的,因為即便是一個(gè)中等大小的應用程序至少也會(huì )產(chǎn)生幾千個(gè)log輸出。許多工作都花費在測量和改進(jìn)logging性能上。Log4j聲明它是快速和靈活的:速度第一,靈活性第二。
用戶(hù)需要清楚地了解下面這些與性能相關(guān)的問(wèn)題:
當logging被完全關(guān)閉或只是set of levels被關(guān)閉,日志請求的開(kāi)銷(xiāo)是方法的調 用和整數的比較。在一個(gè)233 MHz Pentium II機器上,這種開(kāi)銷(xiāo)通常在5 to 50 毫微秒范圍內。 set of levels
不過(guò),方法的調用包含有參數的建造上的“隱閉”開(kāi)銷(xiāo)。
例如下面的logger cat程序段中:
logger.debug("Entry number: " + i + " is " + String.valueOf(entry[i]));
不管message被日志記錄與否,構造message參數的開(kāi)銷(xiāo)還是有的,比如說(shuō), 把整數i 和數組entry[i]轉化為String,連接中間字串。參數構造的這種開(kāi)銷(xiāo)可能 很高,它依賴(lài)于所介入的參數數量有多少。
為了避免這種參數構造開(kāi)銷(xiāo),把以上的代碼段改寫(xiě)為:
if(logger.isDebugEnabled() {
logger.debug("Entry number: " + i + " is " + String.valueOf(entry[i]));
}
如果排錯功能不被使用,就不會(huì )有參數構造上的開(kāi)銷(xiāo)。但是,另一方面,如果 logger的排錯功能被起用,就會(huì )有倆倍的開(kāi)銷(xiāo)用于評估logger是否被起用:一 次是判斷,一次是判斷debug是否被啟用。但這不是極重的負 擔,因為評估logger的時(shí)間只有整個(gè)log語(yǔ)句執行時(shí)間的1%debugEnabled
在log4j中,把日志請求作為L(cháng)ogger類(lèi)的實(shí)例。Logger是類(lèi)而不是接口,這主 要是為了減少程序調用的開(kāi)銷(xiāo),但犧牲了接口所能帶來(lái)的靈活性。
有些用戶(hù)使用預處理或compile-time技術(shù)來(lái)編譯所有log語(yǔ)句。這樣logging方面 的性能是很好。但是,因為resulting application binary沒(méi)有包含任何log語(yǔ) 句,你不能對這個(gè)二進(jìn)制程序起用logging。在我看來(lái),這是為了小的性能增加 而付出大的代價(jià)。
本質(zhì)上影響性能的因素是logger的層次關(guān)系。當logging功能被打開(kāi)時(shí),log4j仍 然需要把log請求的級別去與request logger的級別作比較。不過(guò),有些loggers 并沒(méi)有指派的優(yōu)先級別,但它可以從它的上一層logger那里繼承優(yōu)先級別。因 此在繼承優(yōu)先級之前,logger可能需要搜索它的ancestors。
Log4j在這方面做了很大的努力,以便使這種階層的優(yōu)先級別搜尋(hierarchy walk )盡可能的快速。例如,子代loggers僅僅只和它們現有的ancestors鏈 接。在前面的BasicConfigurator示例中,叫做的logger 直接與 root logger鏈接,繞過(guò)了不存在的com或com.foo.Barcom.foo loggers。這極大地提高了優(yōu) 先級別搜尋的速度。
階層的優(yōu)先級搜尋(walking the hierarchy )的開(kāi)銷(xiāo)在于它比logging完全關(guān)閉 時(shí)要慢三倍。
這里講的是log輸出的格式化和把log信息發(fā)送到目標所在地的開(kāi)銷(xiāo)。Log4j在這 方面也下了大力氣讓格式化能盡快執行。對appenders也是一樣。通常情況 下,格式化語(yǔ)句的開(kāi)銷(xiāo)可能是100到300微秒的處理時(shí)間。確切數字請參看 org.apache.log4.performance.Logging 。
盡管log4j具有許多功能特性,但速度是第一設計目標。為了提高性能,一些 log4j的部件曾經(jīng)被重寫(xiě)過(guò)許多次。即使這樣,log4j的貢獻者們不斷提出新的優(yōu)化辦法。你應該很驚喜地發(fā)現當以SimpleLayout來(lái)配置時(shí),性能測試顯示使用 log4j日志和使用System.out.println日志同樣快。
Log4j是用Java編寫(xiě)的一個(gè)非常流行的logging開(kāi)發(fā)包。它的一個(gè)顯著(zhù)特性之一是在loggers里運用了繼承的概念。使用這種logger的層次關(guān)系,就可能準確地控制每一個(gè)log語(yǔ)句的輸出。這樣減少了log信息的輸出量并降低了logging的開(kāi)銷(xiāo)。
Log4j API的優(yōu)點(diǎn)之一是它的可管理性。一旦log語(yǔ)句被插入到代碼中,他們就能被配置文件控制而無(wú)需重新編譯源代碼。Log信息的輸出能夠有選擇地被起用或關(guān)閉,用戶(hù)能夠按照自己選擇的格式將這些log信息輸出到許多不同的輸出設備中。Log4j軟件包的設計是在代碼中保留log語(yǔ)句的同時(shí)不造成很大的性能損失。
聯(lián)系客服