Copyright© 2000-2004 The Apache Software Foundation.
版權所有。Log4j軟件是在遵守Apache Software License 1.1版的條例下發行的,Apache Software
License的復制件被包括在log4j發布的LICENSE.txt文件里。這個簡短手冊也借用了The complete log4j
manual 里的一些內容,The complete log4j manual包含最新的更為詳盡的信息。 The
complete log4j manual
摘要
這個文檔資料描述了log4j API,它的獨特的特性和設計原理。Log4j是由許多
作者共同參與的開放源代碼項目。它允許開發人員以任意的精細程度控制哪些
日志說明被輸出。通過使用外部的配置文件,可以在運行時配置它。最好的
是,log4j 開發包很容易上手。注意,它也可能會使一些開發人員著迷。
簡 介
幾乎每個大的應用程序都有它自己的日志和跟蹤程序的API。順應這一規則,E.U. SEMPER項目組決定編寫它自己的程序跟蹤API(tracing API)。這開始于1996年早期。經過無數的工作,更改和性能加強,這個API終于成為一個十分受歡迎的Java日志軟件包,那就是log4j。這個軟件包的發行遵守open source動議認證的Apache Software License。最新的log4j版本包括全部的源代碼,類文件和文檔資料,可以在 http://logging.apache.org/log4j/找到它們。另外,log4j已經被轉換成 C, C++, C#, Perl, Python, Ruby, 和 Eiffel 語言。
把log statements插入到你的代碼中是一種排錯的低技能辦法。這也許是唯一的方法,因為排錯工具并不總是可以被使用或者適用于你的程序。對于多線程的應用程序和多數發行的應用程序,通常就是這樣的情形。
經驗告訴我們logging是開發過程中重要的一環。它具有多種優點。首先,它能精確地提供運行時的上下文(context)。
一旦在程序中加入了Log
代碼,它就能自動的生成并輸出logging信息而不需要人為的干預。另外,log信息的輸出可以被保存到一個固定的地方,以備以后研究。除了在開發過程
中發揮它的作用外,一個性能豐富的日志記錄軟件包能當作一個審計工具(audit tool)使用。
Brian W. Kernighan 和 Rob Pike 在他們的"The Practice of Programming" 書中這樣寫到: "The Practice of Programming"
作為個人的選擇,除了得到一大堆程序跟蹤信息或一兩個變量值以外,我們傾
向於不使用排錯器。一個原因是在詳細而復雜的數據結構和控制流程中很容易
迷失;我們發現認真思考并在關鍵處加入自我檢查代碼和輸出指令,比起一步
步看程序要效率高。在日志說明里查找比在明智地放置自我檢查代碼后的輸出
里查找要費時。而決定在哪里放置打印指令要比在日志說明里一步步找到關鍵
的代碼要省時間。更重要的是,自我檢查的排錯指令和程序并存;而排錯
sessions是暫時的。
Logging確實也有它的缺陷。它降低了程序運行的速度。它太冗長,查看時很容易錯過。為了減少這些負面影響,log4j 被設計得可靠,高效和靈活。因為,記錄日志很少是一個應用程序的主要焦點,log4j API 盡量做到容易被理解和使用。
Loggers, Appenders and Layouts
Log4j 有三個主要組件:loggers, appenders和layouts。這三類組件一起應用,可以讓開發人員能夠根據日志的類型和級別進行記錄,并且能在程序運行時控制log信息輸出的格式和往什么地方輸出信息。
Logger hierarchy
任何logging API 與簡單的System.out.println
輸出調試信息方法比較,最主要的優點在于它能夠關閉一些調試信息輸出而不影響其他人的調試。這種能力的實現是假設這些logging空間,也就是所有的可能發生的日志說明空間,可以根據程序開發人員選擇的標準進行分類。這一觀察以前使得我們選擇了category作為這個軟件包的中心概念。但是,在log4j 1.2版本以后,Logger
類取代了Category
類。對于那些熟悉早先版本的log4j的開發人員來說,Logger類只不過是Category類的一個別名。
Loggers是被命名的實體。Logger的名字大小寫有區別(case-sensitive),并且它們遵守階層式的命名規則:
-
Named Hierarchy
-
如果一個logger 的名字后面跟著一個點號(dot),它就是點號(dot)后面的那個logger的前輩( ancestor),是這個晚輩(descendant) 的前綴。如果在它自己和這個晚輩之間沒有其它的前輩,它和這個晚輩之間就是父子關系。
|
例如,叫做"com.foo"的logger是叫做 "com.foo.Bar"的logger的父輩 。同樣地,"java"是"java.util" 的父輩,是"java.util.Vector"的前輩。大多數開發人員都熟悉這種命名方法。 "com.foo"
"com.foo.Bar"
"java"
"java.util"
"java.util.Vector"
根(root)logger 位于logger 階層的最上層。它在兩個方面很特別:
- 它總是存在的,
- 不能通過使用它的名字直接得到它。
通過這個類的靜態方法Logger.getRootLogger得到它(指RootLogger)。所有其他的loggers是通過靜態方法Logger.getLogger來實例化并獲取的。這個方法Logger.getLogger把所想要的logger的名字作為參數。 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可以被指派優先級別。Level.html#DEBUG">DEBUG, INFO, WARN, ERROR 和FATAL這組
級別在org.apache.log4j.Level
類中有定義。你也可以通過Level類的子類去定
義你自己的優先級別,盡管我們不鼓勵你這樣做。在后面我們會講到一個更好
的方法。
如果一個logger沒有被指定優先級別,它將繼承最接近的祖先所被指定的優先級別。下面是更多關于優先級別的信息:
-
Level Inheritance
-
對于一個給定的logger C,它繼承的級別等于logger階層里,從C開始往root logger上去的第一個non-null級別。
|
要保證所有的loggers最終都繼承一個優先級別,root logger總是有一個被指派的優先級。
下面是具有各種指派優先級別值的四個表格,以及根據上面的規則所得出的繼承優先級別。
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的被指派的級別值。
日志請求是通過調用一個日志實例的打印方法(之一)而產生的。這些打印方法是log
4j/Logger.html#debug(java.lang.Object)">debug, info, warn, error, fatal 和 log。
根據定義,打印方法決定一個日志請求的級別。例如,如果c是一個日志實例,那么語句c.info("..") 就是級別為INFO的一個日志請求。 c.info("..")
只有一個日志請求(A logging request)的級別高于或等于它的logger級別的時候才能夠被執行。否則,則被認為這個日志請求不能被執行。一個沒有被定義優先級別的logger將從層次關系中的前輩那里繼承優先級別。這個規則總結如下:
-
Basic Selection Rule
-
在一個級別為q(被指定的或繼承的)的logger里,一個級別為p的日志請求,只有在p >= q 時才能夠被執行。
|
這個規則是log4j的核心。它假設級別是有先后順序的。對于標準的優先級別來說,DEBUG < INFO < WARN < ERROR < FATAL
。
這里是一個關于這個規則的例子:
// 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");
|
以一樣的叁數名字調用getLogger
方法,返回的reference總是指向完全相同的logger對象。
例如,在這里:
Logger x = Logger.getLogger("wombat"); Logger y = Logger.getLogger("wombat");
|
x和y指向完全相同的logger對象。
因此,通過這種方式可以配置一個logger,而不需要傳遞references就能在其
他地方得到相同的實例。在生物的父子關系中父母總是排放在孩子們前面,
log4j loggers與此有相互矛盾的地方,那就是log4j loggers可以以任何順序被
產生和配置。特別的是,一個"parent" logger 會找到并連接他的后代,即使他
是在他們之后被定義。
Log4j環境通常是在程序被初始化的時候被配置的。最好的方式是通過閱讀一
個配置文件去配置。我們會馬上討論到這方面的內容。
Log4j使得通過軟件組件的名稱去定義loggers的名字很容易。這可以通過在每
個類中靜態地instantiating一個logger,讓logger的名字與這個合格的java類文
件名相同來完成。這是一種有用并且直觀的定義loggers的方式。因為日志的
輸出帶有產生它們的logger的名字,這種命名策略使我們能夠很方便地識別這
些log信息的來源。不過,盡管這是通用的一種loggers命名策略,Log4j沒有限
制怎樣對loggers進行命名。開發程序員可以根據自己的喜好隨意定義
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的繼承關系不再是添加性的。 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添加性的規則總結如下:
-
Appender Additivity
-
Logger C的log輸出信息將被輸出到C的所有appenders和它的前輩的
appenders。這就是"appender additivity"的意思。
但是,如果logger C的前輩,比如說P,P的additivity flag被設置為 false ,那
么,C的輸出信息將被輸出到C的所有appenders中去,以及它的前輩的——截
止在P那里,包括P在內的,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相關的layout實現。Layout負責
根據用戶的需要去格式化log信息的輸出,而appender負責將一個格式化過的
log信息輸出到它的目的地。
PatternLayout 是標準log4j發行包中的一部分,它
讓用戶根據和C語言中的printf
方法相似的轉換模式指定輸出格式。
例如,具有"%r [%t] %-5p %c - %m%n" 轉換格式的PatternLayout 將輸出以下
的式樣:
176 [main] INFO org.foo.Bar - Located nearest gas station.
第一個區域是從程序開始運行到輸出日志信息所用的毫秒數。第二個區域是產
生日志請求的線程。第三個區域是這個log語句的優先級別。第四個區域是和
日志請求相關聯的logger名字。在'-' 之后的文字是這個log信息的內容。
同樣重要的是,log4j 將根據用戶指定的標準來表達log信息的內容。例如,如
果你經常需要日志記錄Oranges
,Oranges是你當前項目中使用的一個對象類
型,那么你可以注冊一個OrangeRenderer
,這樣每當需要日志記錄一個
orange時,OrangeRenderer就會被調用。
對象的表達遵照類階層(class hierarchy)形式。例如,假設oranges是
fruits,你注冊了一個FruitRenderer
,那么,包括oranges在內的所有的fruits
都將由FruitRenderer來表達,除非你自己為orange注冊了一個特定的
OrangeRenderer
。
Object renderers必須實施ObjectRenderer界面。
配 置
在程序代碼中插入這些日志請求需要相當大的工作量。調查顯示,大約%4左
右的代碼是logging。因此,即便是中等大小的應用程序也需要在它們的代碼中
至少包含有幾千行的log語句。就從這個數目來看,管理這些log語句而不用人
工地去修改它們是十分重要的。
Log4j環境是完全能夠通過編程來配置的。但是使用配置文件去配置則更靈
活。目前,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的相關類,然后定義一個命名為MyApp的靜態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!"); } }
|
通過調用BasicConfigurator.configure 方法產生一個相當簡單的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.
下面的圖形描繪了在調用BasicConfigurator.configure
方法之后,MyApp
的對
象圖表。
注意,log4j 的子代loggers只和它們現有的前輩鏈接。在這里,名字叫
com
.foo.Bar
的logger直接和root
logger鏈接,因此繞過了沒有被使用的com
或com.foo
loggers。這樣極大地提高了log4j的性能并減少了內存(memory)
的使用。
通過調用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 。
這里是一個配置文件的示例,這個配置文件產生和前面BasicConfigurator
例子
完全一樣的輸出結果:
# 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
沒有指定的優先級別,它就從com.foo中繼承優先級
別,而com.foo的優先級別在配置文件中被設置為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
|
調用以這個配置文件增強了的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 發生時,example.log 的老版本將自動被移到
example.log.1
中去。
注意,要獲得這些不同的logging行為并不需要重新編譯代碼。我們還可以簡單
地通過修改log配置文件把log信息輸出到UNIX Syslog daemon中,把所有
com.foo
的日志輸出轉指向NT Event logger 中,或者把log事件輸出到遠程
log4j服務器中,當然它要根據局部服務器規則進行log,例如可以把log事件輸
出到第二個log4j服務器中去。
默認的初始化過程
Log4j庫沒有對它的環境作任何假設。特別是,沒有默認的log4j appenders。
不過在一些精細定義過的情況下,這個Logger
類的靜態的initializer會試圖自動
配置log4j。 Java語言確保一個類的靜態的initializer在這個類被裝載到內存里
時被調用一次,而且僅僅一次。這點很重要,要記住不同的classloaders會裝
載同一個類的不同復制版。這些同一個類的不同復制版在JVM看來是完全不相
關的。
默認的初始化在這樣的環境中很有用處,那就是同一個程序依據運行時的環境
作不同用處。例如,同樣一個程序可以在web-server的控制下作為單獨的程
序,作為一個applet,或者作為一個servlet被使用。
默認的初始化運算法則定義如下:
- 把log4j.defaultInitOverride的系統屬性設置為 "false"以外的任何值將會造成
log4j跳過默認的初始化過程。
- 把
resource
這個string變量設置為log4j.configuration系統屬性的值。
最好的方法指定默認初始化文件是通過log4j.configuration系統屬性來指定。
在log4j.configuration系統屬性沒有被定義的情況下,把resource這個string變
量設置成它的默認值"log4j.properties"。
- 把
resource
變量轉換為一個URL。
- 如果這個
resource
變量不能轉換為一個URL,例如,因為
MalformedURLException
的緣故,那么就通過調用
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)
有關搜尋地址列單,請參看Loader.getResource(java.lang.String)。
- 如果不能找到URL,那就放棄默認的初始化。否則,從URL配置log4j 。
Configurator.html">PropertyConfigurator將被用于解讀URL來配置log4j,除非這個URL以".xml"擴
展符結束,若這個URL以".xml"擴展符結束,DOMConfigurator則被使用。你可
以選擇性地指定一個客戶自己的configurator。log4j.configuratorClass系統屬
性的值就是你客戶自己的configurator的類名。你指定的客戶configurator必須
實施Configurator接口。
配置示例
Tomcat下默認的初始化
默認的log4j初始化在web-server環境中特別有用。在Tomcat 3.x and 4.x下,
你應該把log4j.properties
放置在你的網絡程序的WEB-INF/classes
目錄下面。
Log4j自己會去找到屬性文件并初始化。這樣做又簡單又有效。
你可以選擇在Tomcat啟動之前設置系統屬性log4j.configuration 。對于
Tomcat 3.x ,TOMCAT_OPTS
環境變量被用來設置命令行選項。對于
Tomcat 4.0,使用CATALINA_OPTS
環境變量而不是TOMCAT_OPTS 。
例子
Unix shell 命令
export TOMCAT_OPTS="-Dlog4j.configuration=foobar.txt"
告訴log4j 使用文件
foobar.txt
作為默認的配置文件。這個文件應該被放置在你
的網絡應用程序的
WEB-INF/classes
目錄下面。文件將通過
PropertyConfigurator被讀取。每個網絡應用程序使用不同的默認配置文件,
因為每個文件都是和每個網絡應用程序相關的。
例子
Unix shell 命令
export TOMCAT_OPTS="-Dlog4j.debug -Dlog4j.configuration=foobar.xml"
告訴log4j輸出log4j-內部排錯信息,并使用文件
foobar.xml
作為默認的配置文
件。這個文件應該被放置在你的網絡應用程序的
WEB-INF/classes
目錄下面。
因為文件以.xml擴展符結尾,將使用
DOMConfigurator來讀取。每個網絡應
用程序使用不同的默認配置文件,因為每個文件都是和每個網絡應用程序相關
的。
例子
Windows shell 命令
set TOMCAT_OPTS=-Dlog4j.configuration=foobar.lcf -Dlog4j.configuratorClass=com.foo.BarConfigurator
告訴log4j使用文件
foobar.lcf
作為默認的配置文件。這個文件應該被放置在你
的網絡應用程序的
WEB-INF/classes
目錄下面。根據
log4j.configuratorClass
系統屬性的定義 ,文件將通過將使用客戶自己的configurator——
com.foo.BarConfigurator
被讀取。每個
網絡應用程序使用不同的默認配置文件,因為每個文件都是和一個網絡應用程
序相關的。
例子
Windows shell 命令
set TOMCAT_OPTS=-Dlog4j.configuration=file:/c:/foobar.lcf
告訴log4j使用文件c:\foobar.lcf 作為默認的配置文件。這個配置文件完全由
URL
file:/c:/foobar.lcf
指定。因此,這個相同的配置文件將被所有網絡應用程
序使用。
c:\foobar.lcf
不同的網絡應用程序通過它們各自的classloaders裝載log4j的類。因此,每個
log4j環境的image會獨自地,沒有任何相互協調地行動。例如,在多個網絡應
用程序的配置中,FileAppenders
若定義得完全相同,它們就會編寫相同的文
件。這樣的結果就不那么令人滿意。你必須保證不同的網絡應用程序的log4j配
置不使用相同的系統資源。
初始化servlet
還可以使用一個特別的servlet來進行log4j初始化。這里就是個示例:
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文件里為你的網絡應用程序定義下面的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
實際情況下的大多數系統都需要同時處理多個客戶端問題。在這種系統的典型
的多線程實施中,通常是不同的線程去分別處理不同的客戶需求。Logging特
別適合于復雜的程序跟蹤和排錯。一個通常的處理辦法是通過給每個客戶產生
一個新的分離開的logger來達到把不同的客戶的日志輸出信息區分開來。但這
促進了loggers的增殖,加大了logging的管理負擔。
一個更簡潔的技術是獨特地標記來自于同一個客戶的每一個日志請求。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
類中所有的方法都是靜態的。假設NDC打印
功能被打開,每一次若有日志請求,相應的log4j組件就把這個當前線程的整個
NDC stack包括在日志輸出中打印出來。這樣做不需要用戶干預,用戶只需要
在代碼中明確指定的幾點通過push
和pop
方法將正確的信息放到NDC中就行
了。相反,per-client logger方法需要在代碼中作很多更改。
為了說明這一點,我們舉個有關一個servlet把信息內容發送到多個客戶的例
子。這個Servlet程序在開始接到客戶端的請求,執行其它代碼之前,首先創建
一個NDC。該上下文信息可能是客戶端的主機名,以及其他請求中固有的信
息,通常是包含在cookies中的信息。因此即便這個Servlet程序可能同時要服
務于多個客戶,由相同的代碼啟動的這些logs,比如屬于同一個logger,它們
仍然能夠被區分開來,因為不同的客戶端請求具有不同的NDC stack。這與在
客戶請求期間把一個實例化的logger傳遞給所有要被執行的代碼的復雜性形成
了反差。
然而,一些復雜的應用程序,比如虛擬網絡服務器,必須依據虛擬主機的上下
文語言環境,以及發布請求的軟體組件來作不同的log。最近的log4j發行版支
持多階層樹。這一功能的加強允許每個虛擬主機擁有它自己的logger階層版
本。
性能
一個經常提出的爭議就是logging的運算開銷。這種關注是有道理的,因為即便
是一個中等大小的應用程序至少也會產生幾千個log輸出。許多工作都花費在
測量和改進logging性能上。Log4j聲明它是快速和靈活的:速度第一,靈活性
第二。
用戶需要清楚地了解下面這些與性能相關的問題:
- Logging performance when logging is turned off.
當logging被完全關閉或只是set of levels被關閉,日志請求的開銷是方法的調
用和整數的比較。在一個233 MHz Pentium II機器上,這種開銷通常在5 to 50
毫微秒范圍內。 set
of levels
不過,方法的調用包含有參數的建造上的“隱閉”開銷。
例如下面的logger cat
程序段中:
logger.debug("Entry number: " + i + " is " + String.valueOf(entry[i]));
不管message被日志記錄與否,構造message參數的開銷還是有的,比如說,
把整數i 和數組entry[i]
轉化為String,連接中間字串。參數構造的這種開銷可能
很高,它依賴于所介入的參數數量有多少。
為了避免這種參數構造開銷,把以上的代碼段改寫為:
if(logger.isDebugEnabled() {
logger.debug("Entry number: " + i + " is " + String.valueOf(entry[i]));
}
如果排錯功能不被使用,就不會有參數構造上的開銷。但是,另一方面,如果
logger的排錯功能被起用,就會有倆倍的開銷用于評估logger是否被起用:一
次是判斷debug
Enabled
,一次是判斷debug是否被啟用。但這不是極重的負
擔,因為評估logger的時間只有整個log語句執行時間的1%
在log4j中,把日志請求作為Logger類的實例。Logger是類而不是接口,這主
要是為了減少程序調用的開銷,但犧牲了接口所能帶來的靈活性。
有些用戶使用預處理或compile-time技術來編譯所有log語句。這樣logging方面
的性能是很好。但是,因為resulting application binary沒有包含任何log語
句,你不能對這個二進制程序起用logging。在我看來,這是為了小的性能增加
而付出大的代價。
- The performance of deciding whether to log or not to log when
logging is turned on.
本質上影響性能的因素是logger的層次關系。當logging功能被打開時,log4j仍
然需要把log請求的級別去與request logger的級別作比較。不過,有些loggers
并沒有指派的優先級別,但它可以從它的上一層logger那里繼承優先級別。因
此在繼承優先級之前,logger可能需要搜索它的ancestors。
Log4j在這方面做了很大的努力,以便使這種階層的優先級別搜尋(hierarchy
walk )盡可能的快速。例如,子代loggers僅僅只和它們現有的ancestors鏈
接。在前面的BasicConfigurator
示例中,叫做com
.foo.Bar
的logger 直接與
root logger鏈接,繞過了不存在的com或com.foo
loggers。這極大地提高了優
先級別搜尋的速度。
階層的優先級搜尋(walking the hierarchy )的開銷在于它比logging完全關閉
時要慢三倍。
- Actually outputting log messages
這里講的是log輸出的格式化和把log信息發送到目標所在地的開銷。Log4j在這
方面也下了大力氣讓格式化能盡快執行。對appenders也是一樣。通常情況
下,格式化語句的開銷可能是100到300微秒的處理時間。確切數字請參看
org.apache.log4.performance.Logging 。
盡管log4j具有許多功能特性,但速度是第一設計目標。為了提高性能,一些
log4j的部件曾經被重寫過許多次。即使這樣,log4j的貢獻者們不斷提出新的優
化辦法。你應該很驚喜地發現當以SimpleLayout來配置時,性能測試顯示使用
log4j日志和使用System.out.println
日志同樣快。
結論
Log4j是用Java編寫的一個非常流行的logging開發包。它的一個顯著特性之一
是在loggers里運用了繼承的概念。使用這種logger的層次關系,就可能準確地
控制每一個log語句的輸出。這樣減少了log信息的輸出量并降低了logging的開
銷。
Log4j API的優點之一是它的可管理性。一旦log語句被插入到代碼中,他們就
能被配置文件控制而無需重新編譯源代碼。Log信息的輸出能夠有選擇地被起
用或關閉,用戶能夠按照自己選擇的格式將這些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.
posted on 2007-01-19 00:34
苦笑枯 閱讀(1537)
評論(0) 編輯 收藏 所屬分類:
Java