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