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