摘要
這個文檔資料描述了log4j API,它的獨特的特性和設(shè)計原理。Log4j是由許多作者共同參與的開放源代碼項目。它允許開發(fā)人員以任意的精細(xì)程度控制哪些日志說明被輸出。通過使用外部的配置文件,可以在運行時配置它。最好的是,log4j 開發(fā)包很容易上手。注意,它也可能會使一些開發(fā)人員著迷。
簡 介
幾乎每個大的應(yīng)用程序都有它自己的日志和跟蹤程序的API。順應(yīng)這一規(guī)則,E.U. SEMPER項目組決定編寫它自己的程序跟蹤API(tracing API)。這開始于1996年早期。經(jīng)過無數(shù)的工作,更改和性能加強,這個API終于成為一個十分受歡迎的Java日志軟件包,那就是log4j。這個軟件包的發(fā)行遵守open source動議認(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插入到你的代碼中是一種排錯的低技能辦法。這也許是唯一的方法,因為排錯工具并不總是可以被使用或者適用于你的程序。對于多線程的應(yīng)用程序和多數(shù)發(fā)行的應(yīng)用程序,通常就是這樣的情形。
經(jīng)驗告訴我們logging是開發(fā)過程中重要的一環(huán)。它具有多種優(yōu)點。首先,它能精確地提供運行時的上下文(context)。一旦在程序中加入了Log 代碼,它就能自動的生成并輸出logging信息而不需要人為的干預(yù)。另外,log信息的輸出可以被保存到一個固定的地方,以備以后研究。除了在開發(fā)過程中發(fā)揮它的作用外,一個性能豐富的日志記錄軟件包能當(dāng)作一個審計工具(audit tool)使用。
Brian W. Kernighan 和 Rob Pike 在他們的"The Practice of Programming" 書中這樣寫到: "The Practice of Programming"
作為個人的選擇,除了得到一大堆程序跟蹤信息或一兩個變量值以外,我們傾 向於不使用排錯器。一個原因是在詳細(xì)而復(fù)雜的數(shù)據(jù)結(jié)構(gòu)和控制流程中很容易 迷失;我們發(fā)現(xiàn)認(rèn)真思考并在關(guān)鍵處加入自我檢查代碼和輸出指令,比起一步 步看程序要效率高。在日志說明里查找比在明智地放置自我檢查代碼后的輸出 里查找要費時。而決定在哪里放置打印指令要比在日志說明里一步步找到關(guān)鍵 的代碼要省時間。更重要的是,自我檢查的排錯指令和程序并存;而排錯 sessions是暫時的。
Logging確實也有它的缺陷。它降低了程序運行的速度。它太冗長,查看時很容易錯過。為了減少這些負(fù)面影響,log4j 被設(shè)計得可靠,高效和靈活。因為,記錄日志很少是一個應(yīng)用程序的主要焦點,log4j API 盡量做到容易被理解和使用。
Loggers, Appenders and Layouts
Log4j 有三個主要組件:loggers, appenders和layouts。這三類組件一起應(yīng)用,可以讓開發(fā)人員能夠根據(jù)日志的類型和級別進行記錄,并且能在程序運行時控制log信息輸出的格式和往什么地方輸出信息。
Logger hierarchy
任何logging API 與簡單的System.out.println
輸出調(diào)試信息方法比較,最主要的優(yōu)點在于它能夠關(guān)閉一些調(diào)試信息輸出而不影響其他人的調(diào)試。這種能力的實現(xiàn)是假設(shè)這些logging空間,也就是所有的可能發(fā)生的日志說明空間,可以根據(jù)程序開發(fā)人員選擇的標(biāo)準(zhǔn)進行分類。這一觀察以前使得我們選擇了category作為這個軟件包的中心概念。但是,在log4j 1.2版本以后,
類取代了Logger
類。對于那些熟悉早先版本的log4j的開發(fā)人員來說,Logger類只不過是Category類的一個別名。
Category
Loggers是被命名的實體。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 階層的最上層。它在兩個方面很特別:
- 它總是存在的,
- 不能通過使用它的名字直接得到它。
通過這個類的靜態(tài)方法Logger.getRootLogger得到它(指RootLogger)。所有其他的loggers是通過靜態(tài)方法Logger.getLogger來實例化并獲取的。這個方法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)先級別。Level.html#DEBUG">DEBUG, INFO, WARN, ERROR 和FATAL這組級別在org.apache.log4j.Level
類中有定義。你也可以通過Level類的子類去定義你自己的優(yōu)先級別,盡管我們不鼓勵你這樣做。在后面我們會講到一個更好的方法。
如果一個logger沒有被指定優(yōu)先級別,它將繼承最接近的祖先所被指定的優(yōu)先級別。下面是更多關(guān)于優(yōu)先級別的信息:
|
要保證所有的loggers最終都繼承一個優(yōu)先級別,root logger總是有一個被指派的優(yōu)先級。
下面是具有各種指派優(yōu)先級別值的四個表格,以及根據(jù)上面的規(guī)則所得出的繼承優(yōu)先級別。
Logger name(名稱) |
指派 級別 |
繼承 級別 |
---|---|---|
根 | Proot | Proot |
X | none | Proot |
X.Y | none | Proot |
X.Y.Z | none | Proot |
在上面的示例1中,只有root logger被指派了級別。這個級別的值,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都有一個指派的級別值。不需要級別繼承。
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)用一個日志實例的打印方法(之一)而產(chǎn)生的。這些打印方法是 log
4j/Logger.html#debug(java.lang.Object)">debug, info, warn, error, fatal 和 log。
根據(jù)定義,打印方法決定一個日志請求的級別。例如,如果c是一個日志實例,那么語句c.info("..") 就是級別為INFO的一個日志請求。 c.info("..")
只有一個日志請求(A logging request)的級別高于或等于它的logger級別的時候才能夠被執(zhí)行。否則,則被認(rèn)為這個日志請求不能被執(zhí)行。一個沒有被定義優(yōu)先級別的logger將從層次關(guān)系中的前輩那里繼承優(yōu)先級別。這個規(guī)則總結(jié)如下:
|
這個規(guī)則是log4j的核心。它假設(shè)級別是有先后順序的。對于標(biāo)準(zhǔn)的優(yōu)先級別來說,DEBUG < INFO < WARN < ERROR < FATAL
。
這里是一個關(guān)于這個規(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對象。
例如,在這里:
Logger x = Logger.getLogger("wombat"); Logger y = Logger.getLogger("wombat"); |
因此,通過這種方式可以配置一個logger,而不需要傳遞references就能在其他地方得到相同的實例。在生物的父子關(guān)系中父母總是排放在孩子們前面, log4j loggers與此有相互矛盾的地方,那就是log4j loggers可以以任何順序被產(chǎn)生和配置。特別的是,一個"parent" logger 會找到并連接他的后代,即使他是在他們之后被定義。
Log4j環(huán)境通常是在程序被初始化的時候被配置的。最好的方式是通過閱讀一個配置文件去配置。我們會馬上討論到這方面的內(nèi)容。
Log4j使得通過軟件組件的名稱去定義loggers的名字很容易。這可以通過在每個類中靜態(tài)地instantiating一個logger,讓logger的名字與這個合格的java類文件名相同來完成。這是一種有用并且直觀的定義loggers的方式。因為日志的輸出帶有產(chǎn)生它們的logger的名字,這種命名策略使我們能夠很方便地識別這些log信息的來源。不過,盡管這是通用的一種loggers命名策略,Log4j沒有限制怎樣對loggers進行命名。開發(fā)程序員可以根據(jù)自己的喜好隨意定義 loggers。 software component
當(dāng)然,至今所知的最好的命名策略還是以它們所在的類的名稱來命名 loggers。
Appenders and Layouts
基于自身的logger選擇性地使用或不使用日志請求(logging requests )的能力僅僅整個Log4j能力的一部分。Log4j允許將log信息輸出到許多不同的輸出設(shè)備中。用log4j的語言來說,一個log信息輸出目的地就叫做一個appender。目前,log4j 的appenders可以將log信息輸出到console,files,GUI components,remote socket servers, JMS,NT Event Loggers,和 remote UNIX Syslog daemons。它還可以同時將log信息輸出到多個輸出設(shè)備中。 NT Event Loggers
多個appenders可以和一個logger連接在一起。
使用addAppender方法把一個appender加入到給定的logger上。一個給定的 logger的每一個被允許的日志請求都會被傳遞給這個logger的所有appenders,以及階層中高級別的appenders。換句話說appenders是從logger階層中不斷添加地被繼承的。例如,一個 console appender加給了root logger,那么,這個root logger所有被允許輸出的日志信息將被輸出到console。如果你又給一個名字為C的logger添加了一個 file appender,那么C 以及C的子輩的所有被允許的日志信息將被同時輸出到 file appender和console appender。可以通過把additivity flag設(shè)置為false
來覆蓋這個默認(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é)如下:
|
下面的表格顯示一個示例:
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 | 因為additivity flag被設(shè)置為 false ,所以沒有appender繼承積累。
|
security.access | none | true | A-sec | 因為"security" logger里的additivity flag被設(shè)置為false ,所以僅僅只有"security" logger的appenders。 |
通常,用戶不僅希望自己指定log信息的輸出目的地,而且,他們還希望指定 log信息的輸出格式。這可以通過和appender相關(guān)的layout實現(xiàn)。Layout負(fù)責(zé)根據(jù)用戶的需要去格式化log信息的輸出,而appender負(fù)責(zé)將一個格式化過的 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.
第一個區(qū)域是從程序開始運行到輸出日志信息所用的毫秒數(shù)。第二個區(qū)域是產(chǎn)生日志請求的線程。第三個區(qū)域是這個log語句的優(yōu)先級別。第四個區(qū)域是和日志請求相關(guān)聯(lián)的logger名字。在'-' 之后的文字是這個log信息的內(nèi)容。
同樣重要的是,log4j 將根據(jù)用戶指定的標(biāo)準(zhǔn)來表達(dá)log信息的內(nèi)容。例如,如果你經(jīng)常需要日志記錄Oranges
,Oranges是你當(dāng)前項目中使用的一個對象類型,那么你可以注冊一個OrangeRenderer
,這樣每當(dāng)需要日志記錄一個 orange時,OrangeRenderer就會被調(diào)用。
對象的表達(dá)遵照類階層(class hierarchy)形式。例如,假設(shè)oranges是 fruits,你注冊了一個
,那么,包括oranges在內(nèi)的所有的fruits 都將由FruitRenderer來表達(dá),除非你自己為orange注冊了一個特定的 FruitRenderer
OrangeRenderer
。
Object renderers必須實施ObjectRenderer界面。
配 置
在程序代碼中插入這些日志請求需要相當(dāng)大的工作量。調(diào)查顯示,大約%4左右的代碼是logging。因此,即便是中等大小的應(yīng)用程序也需要在它們的代碼中至少包含有幾千行的log語句。就從這個數(shù)目來看,管理這些log語句而不用人工地去修改它們是十分重要的。
Log4j環(huán)境是完全能夠通過編程來配置的。但是使用配置文件去配置則更靈活。目前,Log4j的配置文件是以XML格式和JAVA properties (key=value) 格式編寫的。
假設(shè)我們有個叫MyApp
的程序使用log4j,讓我們來看看這是怎樣做到的:
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."); } } |
類首先引入log4j的相關(guān)類,然后定義一個命名為MyApp的靜態(tài)logger變量,而這個名字恰好和MyApp的類名一樣。
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!"); } } |
通過調(diào)用BasicConfigurator.configure 方法產(chǎn)生一個相當(dāng)簡單的log4j的設(shè)置。這個方法將一個 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信息。幸運的是,很容易修改MyApp
程序就可以在程序運行時對log輸出進行控制。下面是略加修改后的版本:
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."); } } |
這個例子中MyApp
指示PropertyConfigurator
方法去解讀配置文件并設(shè)置相應(yīng)的logging 。
這里是一個配置文件的示例,這個配置文件產(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 |
由這個文件所配置的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
沒有指定的優(yōu)先級別,它就從com.foo中繼承優(yōu)先級別,而com.foo的優(yōu)先級別在配置文件中被設(shè)置為WARN。 com.foo
.BarBar.doIt
方法里的 log語句的級別為DEBUG,比WARN級別低。所以,doIt()
方法的日志請求就被壓制住了。
這里是另一個使用多個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)用以這個配置文件增強了的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增加了第二個appender時,log信息將同時也被輸出到
文件中。當(dāng)example.log文件達(dá)到100KB 后,example.log文件將被rolled over。當(dāng)roll-over 發(fā)生時,example.log 的老版本將自動被移到 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ī)則進行l(wèi)og,例如可以把log事件輸出到第二個log4j服務(wù)器中去。
默認(rèn)的初始化過程
Log4j庫沒有對它的環(huán)境作任何假設(shè)。特別是,沒有默認(rèn)的log4j appenders。不過在一些精細(xì)定義過的情況下,這個Logger
類的靜態(tài)的initializer會試圖自動配置log4j。 Java語言確保一個類的靜態(tài)的initializer在這個類被裝載到內(nèi)存里時被調(diào)用一次,而且僅僅一次。這點很重要,要記住不同的classloaders會裝載同一個類的不同復(fù)制版。這些同一個類的不同復(fù)制版在JVM看來是完全不相關(guān)的。
默認(rèn)的初始化在這樣的環(huán)境中很有用處,那就是同一個程序依據(jù)運行時的環(huán)境作不同用處。例如,同樣一個程序可以在web-server的控制下作為單獨的程序,作為一個applet,或者作為一個servlet被使用。
默認(rèn)的初始化運算法則定義如下:
- 把log4j.defaultInitOverride的系統(tǒng)屬性設(shè)置為 "false"以外的任何值將會造成 log4j跳過默認(rèn)的初始化過程。
- 把
這個string變量設(shè)置為log4j.configuration系統(tǒng)屬性的值。最好的方法指定默認(rèn)初始化文件是通過log4j.configuration系統(tǒng)屬性來指定。在log4j.configuration系統(tǒng)屬性沒有被定義的情況下,把resource這個string變量設(shè)置成它的默認(rèn)值"log4j.properties"。resource
- 把
resource
變量轉(zhuǎn)換為一個URL。 - 如果這個
resource
變量不能轉(zhuǎn)換為一個URL,例如,因為MalformedURLException
的緣故,那么就通過調(diào)用 org.apache.log4j.helpers.Loader.getResource(resource, Logger.class)在 classpath上搜尋resource,它會返回一個URL。注意, string "log4j.properties"是一個不合式的URL。org.apache.log4j.helpers.Loader.getResource(resource, Logger.class)
有關(guān)搜尋地址列單,請參看Loader.getResource(java.lang.String)。
- 如果不能找到URL,那就放棄默認(rèn)的初始化。否則,從URL配置log4j 。
Configurator.html">PropertyConfigurator將被用于解讀URL來配置log4j,除非這個URL以".xml"擴展符結(jié)束,若這個URL以".xml"擴展符結(jié)束,DOMConfigurator則被使用。你可以選擇性地指定一個客戶自己的configurator。log4j.configuratorClass系統(tǒng)屬性的值就是你客戶自己的configurator的類名。你指定的客戶configurator必須 實施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自己會去找到屬性文件并初始化。這樣做又簡單又有效。
你可以選擇在Tomcat啟動之前設(shè)置系統(tǒng)屬性log4j.configuration 。對于 Tomcat 3.x ,
環(huán)境變量被用來設(shè)置命令行選項。對于 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)的配置文件。這個文件應(yīng)該被放置在你的網(wǎng)絡(luò)應(yīng)用程序的WEB-INF/classes
目錄下面。文件將通過 PropertyConfigurator被讀取。每個網(wǎng)絡(luò)應(yīng)用程序使用不同的默認(rèn)配置文件,因為每個文件都是和每個網(wǎng)絡(luò)應(yīng)用程序相關(guān)的。
例子
Unix shell 命令
export TOMCAT_OPTS="-Dlog4j.debug -Dlog4j.configuration=foobar.xml"告訴log4j輸出log4j-內(nèi)部排錯信息,并使用文件
foobar.xml
作為默認(rèn)的配置文件。這個文件應(yīng)該被放置在你的網(wǎng)絡(luò)應(yīng)用程序的WEB-INF/classes
目錄下面。因為文件以.xml擴展符結(jié)尾,將使用DOMConfigurator來讀取。每個網(wǎng)絡(luò)應(yīng)用程序使用不同的默認(rèn)配置文件,因為每個文件都是和每個網(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)的配置文件。這個文件應(yīng)該被放置在你的網(wǎng)絡(luò)應(yīng)用程序的WEB-INF/classes
目錄下面。根據(jù)log4j.configuratorClass 系統(tǒng)屬性的定義 ,文件將通過將使用客戶自己的configurator—— com.foo.BarConfigurator
被讀取。每個網(wǎng)絡(luò)應(yīng)用程序使用不同的默認(rèn)配置文件,因為每個文件都是和一個網(wǎng)絡(luò)應(yīng)用程序相關(guān)的。
例子
Windows shell 命令
set TOMCAT_OPTS=-Dlog4j.configuration=file:/c:/foobar.lcf告訴log4j使用文件c:\foobar.lcf 作為默認(rèn)的配置文件。這個配置文件完全由 URL
file:/c:/foobar.lcf
指定。因此,這個相同的配置文件將被所有網(wǎng)絡(luò)應(yīng)用程序使用。 c:\foobar.lcf
不同的網(wǎng)絡(luò)應(yīng)用程序通過它們各自的classloaders裝載log4j的類。因此,每個 log4j環(huán)境的image會獨自地,沒有任何相互協(xié)調(diào)地行動。例如,在多個網(wǎng)絡(luò)應(yīng)用程序的配置中,FileAppenders
若定義得完全相同,它們就會編寫相同的文件。這樣的結(jié)果就不那么令人滿意。你必須保證不同的網(wǎng)絡(luò)應(yīng)用程序的log4j配置不使用相同的系統(tǒng)資源。
初始化servlet
還可以使用一個特別的servlet來進行l(wèi)og4j初始化。這里就是個示例:
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> |
編寫一個initialization servlet 是最靈活的方式來初始化log4j。不受任何限制,你可以在這個servlet的init()
方法里放入任何代碼。
Nested Diagnostic Contexts
實際情況下的大多數(shù)系統(tǒng)都需要同時處理多個客戶端問題。在這種系統(tǒng)的典型的多線程實施中,通常是不同的線程去分別處理不同的客戶需求。Logging特別適合于復(fù)雜的程序跟蹤和排錯。一個通常的處理辦法是通過給每個客戶產(chǎn)生一個新的分離開的logger來達(dá)到把不同的客戶的日志輸出信息區(qū)分開來。但這促進了loggers的增殖,加大了logging的管理負(fù)擔(dān)。
一個更簡潔的技術(shù)是獨特地標(biāo)記來自于同一個客戶的每一個日志請求。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) 對這個方法進行了描述。 Pattern Languages of Program Design 3
要獨特地標(biāo)記每個日志請求,用戶把上下文信息送入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類是作為一個保存線程上下文的stack來獨個線程(per thread) 管理的。注意,org.apache.log4j.NDC
類中所有的方法都是靜態(tài)的。假設(shè)NDC打印功能被打開,每一次若有日志請求,相應(yīng)的log4j組件就把這個當(dāng)前線程的整個 NDC stack包括在日志輸出中打印出來。這樣做不需要用戶干預(yù),用戶只需要在代碼中明確指定的幾點通過push
和pop
方法將正確的信息放到NDC中就行了。相反,per-client logger方法需要在代碼中作很多更改。
為了說明這一點,我們舉個有關(guān)一個servlet把信息內(nèi)容發(fā)送到多個客戶的例子。這個Servlet程序在開始接到客戶端的請求,執(zhí)行其它代碼之前,首先創(chuàng)建一個NDC。該上下文信息可能是客戶端的主機名,以及其他請求中固有的信息,通常是包含在cookies中的信息。因此即便這個Servlet程序可能同時要服務(wù)于多個客戶,由相同的代碼啟動的這些logs,比如屬于同一個logger,它們?nèi)匀荒軌虮粎^(qū)分開來,因為不同的客戶端請求具有不同的NDC stack。這與在客戶請求期間把一個實例化的logger傳遞給所有要被執(zhí)行的代碼的復(fù)雜性形成了反差。
然而,一些復(fù)雜的應(yīng)用程序,比如虛擬網(wǎng)絡(luò)服務(wù)器,必須依據(jù)虛擬主機的上下文語言環(huán)境,以及發(fā)布請求的軟體組件來作不同的log。最近的log4j發(fā)行版支持多階層樹。這一功能的加強允許每個虛擬主機擁有它自己的logger階層版本。
性能
一個經(jīng)常提出的爭議就是logging的運算開銷。這種關(guān)注是有道理的,因為即便是一個中等大小的應(yīng)用程序至少也會產(chǎn)生幾千個log輸出。許多工作都花費在測量和改進logging性能上。Log4j聲明它是快速和靈活的:速度第一,靈活性第二。
用戶需要清楚地了解下面這些與性能相關(guān)的問題:
- Logging performance when logging is turned off.
- The performance of deciding whether to log or not to log when logging is turned on.
本質(zhì)上影響性能的因素是logger的層次關(guān)系。當(dāng)logging功能被打開時,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)閉時要慢三倍。
- Actually outputting log messages
這里講的是log輸出的格式化和把log信息發(fā)送到目標(biāo)所在地的開銷。Log4j在這方面也下了大力氣讓格式化能盡快執(zhí)行。對appenders也是一樣。通常情況下,格式化語句的開銷可能是100到300微秒的處理時間。確切數(shù)字請參看 org.apache.log4.performance.Logging 。
當(dāng)logging被完全關(guān)閉或只是set of levels被關(guān)閉,日志請求的開銷是方法的調(diào)用和整數(shù)的比較。在一個233 MHz Pentium II機器上,這種開銷通常在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])); }
如果排錯功能不被使用,就不會有參數(shù)構(gòu)造上的開銷。但是,另一方面,如果 logger的排錯功能被起用,就會有倆倍的開銷用于評估logger是否被起用:一次是判斷
,一次是判斷debug是否被啟用。但這不是極重的負(fù)擔(dān),因為評估logger的時間只有整個log語句執(zhí)行時間的1%
debug
Enabled
在log4j中,把日志請求作為Logger類的實例。Logger是類而不是接口,這主要是為了減少程序調(diào)用的開銷,但犧牲了接口所能帶來的靈活性。
有些用戶使用預(yù)處理或compile-time技術(shù)來編譯所有l(wèi)og語句。這樣logging方面的性能是很好。但是,因為resulting application binary沒有包含任何log語句,你不能對這個二進制程序起用logging。在我看來,這是為了小的性能增加而付出大的代價。
盡管log4j具有許多功能特性,但速度是第一設(shè)計目標(biāo)。為了提高性能,一些 log4j的部件曾經(jīng)被重寫過許多次。即使這樣,log4j的貢獻者們不斷提出新的優(yōu)化辦法。你應(yīng)該很驚喜地發(fā)現(xiàn)當(dāng)以SimpleLayout來配置時,性能測試顯示使用 log4j日志和使用System.out.println
日志同樣快。
結(jié)論
Log4j是用Java編寫的一個非常流行的logging開發(fā)包。它的一個顯著特性之一是在loggers里運用了繼承的概念。使用這種logger的層次關(guān)系,就可能準(zhǔn)確地控制每一個log語句的輸出。這樣減少了log信息的輸出量并降低了logging的開銷。
Log4j API的優(yōu)點之一是它的可管理性。一旦log語句被插入到代碼中,他們就能被配置文件控制而無需重新編譯源代碼。Log信息的輸出能夠有選擇地被起用或關(guān)閉,用戶能夠按照自己選擇的格式將這些log信息輸出到許多不同的輸出設(shè)備中。Log4j軟件包的設(shè)計是在代碼中保留log語句的同時不造成很大的性能損失。