Log4j簡(jiǎn)明手冊(cè)
Log4j簡(jiǎn)明手冊(cè)
1. 概述
本文主要描述Log4j的API的唯一特性和它的設(shè)計(jì)原理。Log4j是一個(gè)基于許多作者的開放源碼的項(xiàng)目。它允許開發(fā)員以任意的間隔來控制日志的輸出。它通過設(shè)在外部的配置文件而達(dá)到運(yùn)行時(shí)靈活的設(shè)置。最重要的是,Log4j有一個(gè)平穩(wěn)的學(xué)習(xí)曲線。注意:根據(jù)來自用戶的反饋判斷,它很容易使人上癮。
2. 導(dǎo)言
幾乎所有的大型應(yīng)用程序都包括它的自己的日志和跟蹤API。順應(yīng)這個(gè)規(guī)則,E.U. SEMPER 項(xiàng)目決定寫它自己的跟蹤PAI。這是1996年初。在無數(shù)次加強(qiáng),幾次變形和許多工作后,那個(gè)API變成了如今的Log4j,一個(gè)流行的java日志包。這個(gè)包以Apache Software License協(xié)議發(fā)布,一個(gè)成熟的開放源嗎協(xié)議。最新的Log4j版本,包括全部的源碼,class文件和文檔,你可以在http://jakarta.apache.org/Log4j/上找到。順便,Log4j已經(jīng)給C, C++, C#, Python, Ruby, and Eiffel 語言都提供了接口。
為了調(diào)試而插入日志輸出到代碼里是一個(gè)低技術(shù)成分的方法,但它可能也是唯一的方法,因?yàn)檎{(diào)試器并不是一直可用或者可以適應(yīng)的,尤其對(duì)于多線程的分布使式的大型程序而言。
經(jīng)驗(yàn)指出調(diào)試是軟件開發(fā)周期中一個(gè)重要的組成部分。
Log4j擁有幾個(gè)優(yōu)點(diǎn):
首先,它提供關(guān)于運(yùn)行程序的準(zhǔn)確的環(huán)境。一旦代碼被插入,不需要人工干預(yù)就可以產(chǎn)生調(diào)試信息。
其次,日志輸出可以被有計(jì)劃的保存在永久媒體中以便日后研究。
另外,除了在開發(fā)周期中,一個(gè)充分詳盡的日志包可以被用來作為以后的統(tǒng)計(jì)工具。
Log4j當(dāng)然還有它的缺點(diǎn),它可能減慢程序。如果太詳細(xì),它可能導(dǎo)致屏幕盲目滾動(dòng)。排除這些情況,Log4j是可靠的,快速的,可以擴(kuò)展的。因?yàn)槿罩竞苌偈且粋€(gè)應(yīng)用程序的主要目的, 設(shè)計(jì)者們正努力使得Log4j API學(xué)習(xí)和使用簡(jiǎn)單化。
3. 日志類別、輸出源和布局
Log4j有三個(gè)主要的組件:日志類別(Loggers)、輸出源( Appenders)和布局(Layouts)。這三種類型的組件一起工作使得開發(fā)員可以根據(jù)信息的類型和級(jí)別記錄它們,并且在運(yùn)行時(shí)控制這些信息的輸出格式和位置。
3.1 日志類別的層次結(jié)構(gòu)(Loggers)
Log4j首要的相對(duì)于簡(jiǎn)單的使用System.out.println()方法的優(yōu)點(diǎn)是基于它的在禁止一些特定的信息輸出的同時(shí)不妨礙其它信息的輸出的能力。這個(gè)能力源自于日志命名空間,也就是說,所有日志聲明的空間,它根據(jù)一些開發(fā)員選擇的公式而分類。以前的觀察引導(dǎo)我們選擇類別作為包的中心概念。然而,自從Log4j的1.2版本,Logger類被Catalog類所取代,對(duì)于那些熟悉Log4j以前版本的人來說,Logger類可以被想象成僅僅是Category 類的別名。
Loggers 被指定為實(shí)體,Logger的名字是大小寫敏感的,它們遵循以下的命名
規(guī)則:
2 命名繼承
如果類別的名稱(后面加一個(gè)點(diǎn))是其子類別名稱的前綴,則它就是另一個(gè)類別的祖輩。
如果一個(gè)類別(Logger)和它的子類別之間沒有其它的繼承關(guān)系,我們就稱之為parent與child的關(guān)系。
例如,類別"com.foo"是類別"com.foo.Bar"的parent。相似的,"java"是"java.util"的parent,是"java.util.Vector"的父輩。.這個(gè)命名規(guī)則應(yīng)該被大多數(shù)的開發(fā)員所熟悉。
根(root) 類別位于logger繼承結(jié)構(gòu)的最上層。它有兩種例外:
1.它一直存在
2.它不能根據(jù)名稱而獲得。
調(diào)用類的靜態(tài)方法Logger.getRootLogger可以得到它。其它所有的Logger可以通過靜態(tài)方法Logger.getLogger而得到它們自己的實(shí)例。這個(gè)方法取希望的Logger名作為參數(shù)。Logger的一些基本的方法示例如下:
package org.apache.Log4j;
public Logger class {
// Creation & 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);
// generic printing method:
public void log(Level l, Object message);
}
Loggers可以被分配的級(jí)別。所有級(jí)別的集合包括:
DEBUG
INFO
WARN
ERROR
FATAL
它們被定義于org.apache.Log4j.Level 類。雖然我們不鼓勵(lì),但是你們可以通過繼承Level類來定義你們自己的級(jí)別。我們隨后將介紹一個(gè)比較好的方法。
如果一個(gè)Logger沒有被分配一個(gè)級(jí)別,那么它將從一個(gè)被分配了級(jí)別的最接近它的ancestor哪里繼承。
正規(guī)的說:
2 級(jí)別繼承
對(duì)于一個(gè)給定的Logger C,它的繼承的級(jí)別等于從C開始上溯到的第一個(gè)擁有非空級(jí)別的Logger的級(jí)別。
為了保證所有的Logger最終能夠繼承到一個(gè)級(jí)別,根Logger通常有一個(gè)已經(jīng)定義了的級(jí)別。
以下四個(gè)表中的數(shù)據(jù)演示了根據(jù)以上規(guī)則得到的結(jié)果。
類別名
分配的級(jí)別
繼承的級(jí)別
root
Proot
Proot
X
none
Proot
X.Y
none
Proot
X.Y.Z
none
Proot
Example 1
在例子1中,只有根Logger定義了一個(gè)級(jí)別,它的級(jí)別的值--"Proot"被所有其它的Loggers X, X.Y, 和X.Y.Z所繼承。
類別名
分配的級(jí)別
繼承的級(jí)別
root
Proot
Proot
X
Px
Px
X.Y
Pxy
Pxy
X.Y.Z
Pxyz
Pxyz
Example 2
在例子2中,所有的Logger都有一個(gè)被分配的級(jí)別值,所以它們不需要級(jí)別繼承。
類別名
分配的級(jí)別
繼承的級(jí)別
root
Proot
Proot
X
Px
Px
X.Y
none
Px
X.Y.Z
Pxyz
Pxyz
Example 3
在例子3中,根Logger,以及X和X.Y.Z被分別分配了級(jí)別Proot,Px和Pxyz。Logger X.Y從它的parent X繼承了級(jí)別值Px。
類別名
分配的級(jí)別
繼承的級(jí)別
root
Proot
Proot
X
Px
Px
X.Y
none
Px
X.Y.Z
none
Px
Example 4
在例子4中,根Logger和X被分別分配了級(jí)別"Proot"和"Px",Logger X.Y 和 X.Y.Z從被分配了級(jí)別的最接近它們的ancestor X那里得到繼承。
我們需要通過調(diào)用Logger的輸出的實(shí)例方法之一來實(shí)現(xiàn)日志請(qǐng)求。這些輸出的方法是debug, info, warn, error, fatal 和 log.
通過定義輸出方法來區(qū)分日志的請(qǐng)求的級(jí)別。例如,如果c是一個(gè)Logger的實(shí)例,那么聲明 c.info 就是一個(gè)INFO級(jí)別的日志請(qǐng)求。
如果一個(gè)日志的請(qǐng)求的級(jí)別高于或等于日志的級(jí)別那么它就能被啟用。反之,將被禁用。一個(gè)沒有被安排級(jí)別的Logger將從它的父輩中得到繼承。這個(gè)規(guī)則總結(jié)如下。
2 基本的選擇規(guī)則
假如在一個(gè)級(jí)別為q的Logger中發(fā)生一個(gè)級(jí)別為p的日志請(qǐng)求,如果p>=q,那么請(qǐng)求將被啟用。
這是Log4j的核心原則。它假設(shè)級(jí)別是有序的。對(duì)于標(biāo)準(zhǔn)級(jí)別,我們定義DEBUG < INFO < WARN < ERROR < FATAL.
以下是關(guān)于這條規(guī)則的一個(gè)例子。
// 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 progamitcally. This is usually done
// in configuration files.
cat.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");
調(diào)用getLogger方法將返回一個(gè)同名的Logger對(duì)象的實(shí)例。
例如,
Categoty x = Logger.getLogger("wombat");
Categoty y = Logger.getLogger("wombat");
x和y參照的是同一個(gè)Logger對(duì)象。
這樣我們就可以先定義一個(gè)Logger,然后在代碼的其它地方不需傳參就可以重新得到我們已經(jīng)定義了的Logger的實(shí)例.
同基本的生物學(xué)理論--父先于子相反,Log4j 的loggers可以以任何順序創(chuàng)造和配置。特別是,一個(gè)后實(shí)例化的"parent"logger能夠找到并且連接它的子logger。
配置Log4j的環(huán)境通常在一個(gè)應(yīng)用程序被初始化的時(shí)候進(jìn)行,最好的方法是通過讀一個(gè)配置文件。這個(gè)方法我們將簡(jiǎn)短介紹。
Log4j使得通過軟件組件命名logger很容易。我們可以通過Logger的靜態(tài)的初始化方法在每一個(gè)類里定義一個(gè)logger,令logger的名字等于類名的全局名,而實(shí)現(xiàn)logger的命名。這是一個(gè)實(shí)效的簡(jiǎn)單的定義一個(gè)logger的方法。因?yàn)槿罩据敵鰩в挟a(chǎn)生日志的類的名字,這個(gè)命名策略使得我們更容易定位到一個(gè)日志信息的來源。雖然普通,但卻是命名logger的常用策略之一。Log4j沒有限制定義logger的可能。開發(fā)員可以自由的按照它們的意愿定義logger的名稱。
然而,以類的所在位置來命名Logger好象是目前已知的最好方法。
3.2 輸出源(Appenders)和布局(Layouts)
有選擇的能用或者禁用日志請(qǐng)求僅僅是Log4j的一部分功能。Log4j允許日志請(qǐng)求被輸出到多個(gè)輸出源。用Log4j的話說,一個(gè)輸出源被稱做一個(gè)Appender. 。Appender包括console(控制臺(tái)), files(文件), GUI components(圖形的組件), remote socket servers(socket 服務(wù)), JMS(java信息服務(wù)), NT Event Loggers(NT的事件日志), and remote UNIX Syslog daemons(遠(yuǎn)程UNIX的后臺(tái)日志服務(wù))。它也可以做到異步記錄。
一個(gè)logger可以設(shè)置超過一個(gè)的appender。
用addAppender 方法添加一個(gè)appender到一個(gè)給定的logger。對(duì)于一個(gè)給定的logger它每個(gè)生效的日志請(qǐng)求都被轉(zhuǎn)發(fā)到該logger所有的appender上和該logger的父輩logger的appender上。換句話說,appender自動(dòng)從它的父輩獲得繼承。舉例來說,如果一個(gè)根logger擁有一個(gè)console appender,那么所有生效的日志請(qǐng)求至少會(huì)被輸出到console上。如果一個(gè)名為C的logger有一個(gè)file類型的appender,那么它就會(huì)對(duì)它自己以及所有它的子logger生效。我們也可以通過設(shè)置appender的additivity flag 為false,來重載appender的默認(rèn)行為,以便繼承的屬性不在生效。
調(diào)節(jié)輸出源(appender)添加性的規(guī)則如下。
輸出源的可添加性(Appender Additivity )
一個(gè)名為C的logger的日志定義的輸出將延續(xù)到它自身以及它的ancestor logger的appenders。這就是術(shù)語"appender additivity"的含義。
然而,logger C的一個(gè)ancestor logger P,它的附加標(biāo)志被設(shè)為false,那么C的輸出將被定位到所有C的appender,以及從它開始上溯到P的所有ancestor logger的appender。
Loggers的附加標(biāo)記(additivity flag)默認(rèn)為true。
下表是一個(gè)例子。
Logger
Name
Added
Appenders
Additivity
Flag
Output Targets
Comment
root
A1
not applicable
A1
The root logger is anonymous but can be accessed with the Logger.getRootLogger() method. There is no default appender attached to root.
x
A-x1, A-x2
true
A1, A-x1, A-x2
Appenders of "x" and root.
x.y
none
true
A1, A-x1, A-x2
Appenders of "x" and root.
x.y.z
A-xyz1
true
A1, A-x1, A-x2, A-xyz1
Appenders in "x.y.z", "x" and root.
security
A-sec
false
A-sec
No appender accumulation since the additivity flag is set to false.
security.access
none
true
A-sec
Only appenders of "security" because the additivity flag in "security" is set to false.
經(jīng)常,用戶希望自定義不但輸出源,而且定義輸出格式。這個(gè)是通過在一個(gè)appender上附加一個(gè)layout來完成的。layout是負(fù)責(zé)根據(jù)用戶的希望來格式化日志請(qǐng)求。而appender是負(fù)責(zé)發(fā)送格式化的輸出到它的目的地。PatternLayout,作為L(zhǎng)og4j標(biāo)準(zhǔn)版中的一部分,讓用戶指以類似C語言的printf方法的格式來指定日志的輸出格式。
例如,轉(zhuǎn)化模式為"%r [%t] %-5p %c - %m%n" 的PatternLayout 將輸出類似如下的信息:
176 [main] INFO org.foo.Bar - Located nearest gas station.
第一個(gè)欄位是自從程序開始后消逝的毫秒數(shù)。
第二個(gè)欄位是做出日志的線程。
第三個(gè)欄位是log的級(jí)別。
第四個(gè)欄位是日志請(qǐng)求相關(guān)的logger的名字。而"-"后的文字是信息的表述。
Log4j將根據(jù)用戶定義的公式來修飾日志信息的內(nèi)容。例如,如果你經(jīng)常需要記錄Oranges,一個(gè)在你當(dāng)前的項(xiàng)目被用到的對(duì)象類型,那么你可以注冊(cè)一個(gè)OrangeRenderer ,它將在一個(gè)orange需要被記錄時(shí)被調(diào)用。
對(duì)象渲染類似的類的結(jié)構(gòu)繼承。例如,假設(shè)oranges是fruits,如果你注冊(cè)了一個(gè)FruitRenderer,所有的水果包括oranges將被FruitRenderer所渲染。除非你注冊(cè)了一個(gè)orange。
對(duì)象渲染必須實(shí)現(xiàn)ObjectRenderer接口。
4. 配置
插入日志請(qǐng)求到應(yīng)用程序的代碼中需要大量的預(yù)先計(jì)劃和最終努力。觀察顯示大約4%的代碼是用來輸出的。
因此,大小適度的程序都被嵌入有成千個(gè)日志輸出語句。為了以無需手工的方式管理這些日志的輸出狀態(tài),給日志輸出以編號(hào)和規(guī)范變得勢(shì)在必行。
Log4j在程序中有充分的可配置性。然而,用配置文件配置Log4j具有更大的彈性。目前,它的配置文件支持xml和java properties(key=value)文件兩種格式。
讓我們以一個(gè)例子來演示它是如何做的。假定有一個(gè)用了Log4j的程序MyApp。
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è)靜態(tài)logger變量,并給予值為"MyApp"類的全路徑名稱。
MYApp用了定義在包c(diǎn)om.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()方法創(chuàng)建了一個(gè)相當(dāng)簡(jiǎn)單的Log4j的設(shè)置。它加入一
個(gè)ConsoleAppender到根logger。輸出將被采用了"%-4r [%t] %-5p %c %x - %m%n"模式
的PatternLayout所格式化。
注意,根logger默認(rèn)被分配了Level.DEBUG的級(jí)別。
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的子logger只連接到已經(jīng)存在的它們的父代。特別的是,名為
com.foo.bar的logger是直接連接到根logger,而不是圍繞著沒用的com或com.foo
logger。這顯著的提高了程序性能并且減少的內(nèi)存占用。
MyApp類配置Log4j是通過調(diào)用BasicConfigurator.configure 方法。其它的類僅僅
需要引入org.apache.Log4j.Logger 類,找到它們希望用的logger,并且用它就行。
以前的例子通常輸出同樣的日志信息。幸運(yùn)的是,修改MyApp是容易的,以便日志輸
出可以在運(yùn)行時(shí)刻被控制。這里是一個(gè)小小修改的版本。
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通知程序調(diào)用PropertyConfigurator()方法解析一個(gè)配置文件,并且根
據(jù)這個(gè)配置文件來設(shè)置日志。
這里是一個(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è)我們不在對(duì)com.foo包的任何類的輸出感興趣的話,隨后的配置文件向我們展示
了實(shí)現(xiàn)這個(gè)目的的方法之一。
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.
當(dāng)logger com.foo.bar沒有被分配一個(gè)級(jí)別,它將從com.foo繼承,在配置文件中
它被設(shè)置了WARN的級(jí)別。在Bar.doIt方法中定義的log為DEBUG級(jí)別,低于WARN,
因此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
以這個(gè)配置文件調(diào)用加強(qiáng)了的MyApp類將輸出如下信息.
INFO [main] (MyApp2.java:12) - Entering application.
DEBUG [main] (Bar.java:8) - Doing it again!
INFO [main] (MyApp2.java:15) - Exiting application.
另外,因?yàn)楦鵯ogger有被分配第二個(gè)appender,所以輸出也將被定向到example.log文件。
這個(gè)文件大小達(dá)到100kb時(shí)將自動(dòng)備份。備份時(shí)老版本的example.log文件自動(dòng)被移到
文件example.log.1中。
注意我們不需要重新編譯代碼就可以獲得這些不同的日志行為。我們一樣可以容易
的使日志輸出到UNIX Syslog daemon, 重定向所有的com.foo到NT Event logger,
或者轉(zhuǎn)發(fā)日志到一個(gè)遠(yuǎn)程的Log4j服務(wù)器,它根據(jù)本地server的策略來進(jìn)行日志輸出。例
如轉(zhuǎn)發(fā)日志事件到第二個(gè)Log4j服務(wù)器.
5. 默認(rèn)的初始化過程
Log4j類庫不對(duì)它的環(huán)境做任何假設(shè)。特別是沒有默認(rèn)的Log4j appender。在一些特別
的有著良好定義的環(huán)境下,logger的靜態(tài)inializer將嘗試自動(dòng)的配置Log4j。
java語言的特性保證類的靜態(tài)initializer當(dāng)且僅當(dāng)裝載類到內(nèi)存之時(shí)只會(huì)被調(diào)用一次。
要記住的重要一點(diǎn)是,不同的類裝載器可能裝載同一個(gè)類的完全不同的拷貝。
這些同樣類的拷貝被虛擬機(jī)認(rèn)為是完全不相干的。
默認(rèn)的initialization是非常有用的,特別是在一些應(yīng)用程序所依靠的運(yùn)行環(huán)境被準(zhǔn)確的
定位的情況下。例如,同一樣的應(yīng)用程序可以被用做一個(gè)標(biāo)準(zhǔn)的應(yīng)用程序,或一個(gè)
applet,或一個(gè)在web-server控制下的servlet。
準(zhǔn)確的默認(rèn)的initialization原理被定義如下:
1.設(shè)置系統(tǒng)屬性Log4j.defaultInitOverride為"false"以外的其它值,那么Log4j將
跳過默認(rèn)的initialization過程。
2.設(shè)置資源變量字符串給系統(tǒng)屬性Log4j.configuration。定義默認(rèn)initialization
文件的最好的方法是通過系統(tǒng)屬性Log4j.configuration。萬一系統(tǒng)屬性
Log4j.configuration沒有被定義,那么設(shè)置字符串變量resource 給它的默認(rèn)值
Log4j.properties。
3.嘗試轉(zhuǎn)換resource 變量為一個(gè)URL。
4.如果變量resource的值不能被轉(zhuǎn)換為一個(gè)URL,例如由于MalformedURLException違
例,那么通過調(diào)用
org.apache.Log4j.helpers.Loader.getResource(resource, Logger.class) 方法從
classpath中搜索resource,它將返回一個(gè)URL,并通知"Log4j.properties"的值是一個(gè)錯(cuò)
誤的URL。
看See Loader.getResource(java.lang.String) 查看搜索位置的列表。
5.如果沒有URL被發(fā)現(xiàn),那么放棄默認(rèn)的initialization。否則用URL配置Log4j。
PropertyConfigurator將用來解析URL,配置Log4j,除非URL以".xml"為結(jié)尾。
在這種情況下的話DOMConfigurator將被調(diào)用。你可以有機(jī)會(huì)定義一個(gè)自定義的
configurator。
系統(tǒng)屬性Log4j.configuratorClass 的值取自你的自定義的類名的全路徑。
你自定義的configurator必須實(shí)現(xiàn)configurator接口。
6. 配置范例
6.1 Tomcat下的初始化
默認(rèn)的Log4j initialization典型的應(yīng)用是在web-server 環(huán)境下。在tomcat3.x和tomcat4.x
下,你應(yīng)該將配置文件Log4j.properties放在你的web應(yīng)用程序的WEB-INF/classes 目錄
下。
Log4j將發(fā)現(xiàn)屬性文件,并且以此初始化。這是使它工作的最容易的方法。
你也可以選擇在運(yùn)行tomcat前設(shè)置系統(tǒng)屬性Log4j.configuration 。對(duì)于tomcat 3.x,
TOMCAT_OPTS 系統(tǒng)變量是用來設(shè)置命令行的選項(xiàng)。對(duì)于tomcat4.0,用系統(tǒng)環(huán)境變
量CATALINA_OPTS 代替了TOMCAT_OPTS。
Example 1
UNIX 命令行
export TOMCAT_OPTS="-DLog4j.configuration=foobar.txt"
告訴Log4j用文件foobar.txt作為默認(rèn)的配置文件。這個(gè)文件應(yīng)該放在WEB-INF/classes
目錄下。這個(gè)文件將被PropertyConfigurator所讀。每個(gè)web-application將用不同的默認(rèn)
配置文件,因?yàn)槊總€(gè)文件是和它的web-application 相關(guān)的。
Example 2
UNIX 命令行
export TOMCAT_OPTS="-DLog4j.debug -DLog4j.configuration=foobar.xml"
告訴Log4j輸出Log4j-internal的調(diào)試信息,并且用foobar.xml作為默認(rèn)的配置文件。
這個(gè)文件應(yīng)該放在你的web-application的WEB-INF/classes 目錄下。因?yàn)橛?xml的
擴(kuò)展名,它將被DOMConfigurator所讀。每個(gè)web-application將用不同的默認(rèn)
配置文件。因?yàn)槊總€(gè)文件都和它所在的web-application 相關(guān)的。
Example 3
UNIX 命令行
set TOMCAT_OPTS=-DLog4j.configuration=foobar.lcf -DLog4j.configuratorClass=com.foo.BarConfigurator
告訴Log4j用文件foobar.lcf作為默認(rèn)的配置文件。這個(gè)文件應(yīng)該放在你的
web-application的WEB-INF/classes 目錄下。因?yàn)槎x了Log4j.configuratorClass 系統(tǒng)屬
性,文件將用自定義的com.foo.barconfigurator類來解析。每個(gè)web-application將用不
同的默認(rèn)配置文件。因?yàn)槊總€(gè)文件都和它所在的web-application 相關(guān)的。
Example 4
UNIX 命令行
set TOMCAT_OPTS=-DLog4j.configuration=file:/c:/foobar.lcf
告訴Log4j用文件foobar.lcf作為默認(rèn)的配置文件。這個(gè)配置文件用URL file:/c:/foobar.lcf
定義了全路徑名。這樣同樣的配置文件將被所有的web-application所用。
不同的web-application將通過它們自己的類裝載器來裝載Log4j。這樣,每個(gè)Log4j的環(huán)
境將獨(dú)立的運(yùn)作,而沒有任何的相互同步。例如:在多個(gè)web-application中定義了
完全相同的輸出源的FileAppenders將嘗試寫同樣的文件。結(jié)果好象是缺乏安全性的。
你必須確保每個(gè)不同的web-application的Log4j配置沒有用到同樣的系統(tǒng)資源。
6.2 Servlet 的初始化
用一個(gè)特別的servlet來做Log4j的初始化也是可以的。如下是一個(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中定義隨后的servlet為你的web-application。
<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>
寫一個(gè)初始化的servlet是最有彈性的初始化Log4j的方法。代碼中沒有任何限制,你可
以在servlet的init方法中定義它。
7. Nested Diagnostic Contexts
在現(xiàn)實(shí)世界中的系統(tǒng)經(jīng)常不得不同時(shí)處理多個(gè)客戶端請(qǐng)求。在這樣的一個(gè)典型的多線程的系統(tǒng)中,不同的線程將處理不同的客戶端。Logging特別能夠適應(yīng)這種復(fù)雜的分布式的應(yīng)用程序的調(diào)試和跟蹤。一個(gè)常見的區(qū)分每個(gè)客戶端所輸出的Logging的方法是為每個(gè)客戶端實(shí)例化一個(gè)新的獨(dú)立的Logger。這導(dǎo)致Logger的大量產(chǎn)生,管理的成本也超過了logging本身。
唯一標(biāo)識(shí)每個(gè)log請(qǐng)求是一個(gè)輕量級(jí)的技術(shù)。Neil Harrison 在名為“Patterns for Logging Diagnostic Messages”的書中描述了這個(gè)方法in Pattern Languages of Program Design 3, edited by R. Martin, D. Riehle, and F. Buschmann (Addison-Wesley, 1997).
為了唯一標(biāo)識(shí)每個(gè)請(qǐng)求,用戶把上下文信息推入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如同一個(gè)堆棧樣管理每個(gè)線程。注意所有the org.apache.log4j.NDC 類的方法都是靜態(tài)的。假設(shè)NDC輸出被開啟,每次一個(gè)log 請(qǐng)求被生成時(shí),適當(dāng)?shù)膌og4j組件為將要輸出log的線程包含完整的NDC堆棧。這是在沒有用戶的干預(yù)的情況下做到的,用戶只負(fù)責(zé)在NDC中定位正確的信息,通過在代碼中正確位置插入很少的push和pop方法就行了。相反的,在代碼中per-client實(shí)現(xiàn)方法有著很大變化。
為了演示這個(gè)點(diǎn),讓我們以一個(gè)發(fā)送內(nèi)容到匿名客戶端的servlet為例。這個(gè)servlet可以在開始執(zhí)行每個(gè)其他代碼前的初始化時(shí)建立NDC。上下文信息可以是客戶主機(jī)的名字和其他的請(qǐng)求中固有的信息。
典型的信息包括cookies。因此,即使servlet同時(shí)為多個(gè)客戶同時(shí)提供服務(wù),log 被同樣的代碼初始化,例如屬于同一個(gè)logger,依然可以被區(qū)別,因?yàn)槊總€(gè)客戶請(qǐng)求將有不同的NDC堆棧。與之相比,Contrast this with the complexity of passing a freshly instantiated logger to all code exercised during the client′s request。
不過,一些詭異的程序,例如虛擬主機(jī)的web server記錄日志,不是一般的依靠虛擬主機(jī)的上下文,還要依靠軟件的組件發(fā)出請(qǐng)求。近來log4j的發(fā)布版本支持多層的樹形結(jié)構(gòu)。這個(gè)增強(qiáng)允許每個(gè)虛擬主機(jī)可以處理在樹型結(jié)構(gòu)中屬于它自己的logger。
8. 優(yōu)化
一個(gè)經(jīng)常引用的依靠于logging的參數(shù)是可以計(jì)算的花費(fèi)。這是一個(gè)合理的概念,一個(gè)適度的應(yīng)用程序可能產(chǎn)生成千上萬個(gè)日志請(qǐng)求。許多努力花在測(cè)量和調(diào)試logging的優(yōu)化上。Log4j要求快速和彈性:速度最重要,彈性是其次。
用戶應(yīng)該注意隨后的優(yōu)化建議。
1.日志為禁用時(shí),日志的優(yōu)化。
當(dāng)日志被徹底的關(guān)閉,一個(gè)日志請(qǐng)求的花費(fèi)等于一個(gè)方法的調(diào)用加上整數(shù)的比較時(shí)間。
在233mhz的Pentium II 機(jī)器上這個(gè)花費(fèi)通常在5-50納秒之間。
然而,方法調(diào)用包括參數(shù)構(gòu)建的隱藏花費(fèi)。
例如,對(duì)于logger cat,
logger.debug("Entry number: " + i + " is " + String.valueOf(entry[i]));
引起了構(gòu)建信息參數(shù)的花費(fèi),例如,轉(zhuǎn)化整數(shù)i和entry[i]到一個(gè)string,并且連接中間字符串,不管信息是否被輸出。這個(gè)參數(shù)的構(gòu)建花費(fèi)可能是很高,它主要決定于被調(diào)用的參數(shù)的大小。
避免參數(shù)構(gòu)建的花費(fèi)應(yīng)如下,
if(logger.isDebugEnabled() {
logger.debug("Entry number: " + i + " is " + String.valueOf(entry[i]));
}
如果logger的debug被關(guān)閉這將不會(huì)招致參數(shù)構(gòu)建的花費(fèi)。另一方面,如果logger是debug的話,它將產(chǎn)生兩次判斷 logger是否能用的花費(fèi)。一次是在debugenabled,一次是debug。這是無關(guān)緊要的,因?yàn)榕袛嗳罩镜哪苡?只占日志實(shí)際花費(fèi)時(shí)間的約1%。
在Log4j里,日志請(qǐng)求在Logger 類的實(shí)例里。Logger 是一個(gè)類,而不是一個(gè)接口。這大量的減少了在方法調(diào)用上的彈性化的花費(fèi)。
當(dāng)然用戶采用預(yù)處理或編譯時(shí)間技術(shù)去編譯出所有的日志聲明。這將導(dǎo)致完美的執(zhí)行成效。然而因?yàn)槎M(jìn)制應(yīng)用程序不包括任何的日志聲明的結(jié)果,日志不可能對(duì)那個(gè)二進(jìn)制程序開啟。以我的觀點(diǎn),以這種較大的代價(jià)來換取較小的性能優(yōu)化是不值得的。
2。當(dāng)日志狀態(tài)為啟用時(shí),日志的優(yōu)化。
這是本質(zhì)上的優(yōu)化logger的層次。當(dāng)日志狀態(tài)為開,Log4j依然需要比較請(qǐng)求的級(jí)別與logger的級(jí)別。然而, logger可能沒有被安排一個(gè)級(jí)別;它們將從它們的father繼承。這樣,在繼承之前,logger可能需要搜索它的ancestor。
這里有一個(gè)認(rèn)真的努力使層次的搜索盡可能的快。例如,子logger僅僅連接到它的存在的father logger。
在先前展示的BasicConfigurator 例子中,名為com.foo.bar 的logger是連接到跟根logger,因此繞過 了不存在的logger com和com.foo。這將顯著的改善執(zhí)行的速度,特別是解析logger的層結(jié)構(gòu)時(shí)。
典型的層次結(jié)構(gòu)的解析的花費(fèi)是logger徹底關(guān)閉時(shí)的三倍。
3.日志信息的輸出時(shí),日志的優(yōu)化。
這是主要花費(fèi)在日志輸出的格式化和發(fā)送它到它的輸出源上。這里我們?cè)僖淮蔚母冻雠σ允垢袷交瘓?zhí)行的盡可能快。同appender一樣。實(shí)際上典型的花費(fèi)大約是100-300毫秒。
詳情看org.apache.log4.performance.Logging。
雖然Log4j有許多特點(diǎn),但是它的第一個(gè)設(shè)計(jì)目標(biāo)還是速度。一些Log4j的組件已經(jīng)被重寫過很多次以改善性能。不過,投稿者經(jīng)常提出了新的優(yōu)化。你應(yīng)該滿意的知道,以SimpleLayout的配置執(zhí)行測(cè)試已經(jīng)展示了Log4j的輸出同System.out.println一樣快。
9. 總結(jié)
Log4j是一個(gè)用java寫成的流行的日志包。一個(gè)它與眾不同的特點(diǎn)是在logger中的繼承的概念。用logger的繼承可以以任意的間隔控制日志的狀態(tài)輸出。這個(gè)減少了體積和最小化日志的代價(jià)。
易管理性是Log4j API的優(yōu)點(diǎn)之一。只要日志定義被加入到代碼中,它們就可以用配置文件來控制。它們可以有選擇的被禁用,并且發(fā)送到不同的多個(gè)輸出源上,以用戶選擇好的格式。
Log4j的包被設(shè)計(jì)成,不需花費(fèi)沉重的執(zhí)行代價(jià)就可以保留它們?cè)诋a(chǎn)品中。
1. 概述
本文主要描述Log4j的API的唯一特性和它的設(shè)計(jì)原理。Log4j是一個(gè)基于許多作者的開放源碼的項(xiàng)目。它允許開發(fā)員以任意的間隔來控制日志的輸出。它通過設(shè)在外部的配置文件而達(dá)到運(yùn)行時(shí)靈活的設(shè)置。最重要的是,Log4j有一個(gè)平穩(wěn)的學(xué)習(xí)曲線。注意:根據(jù)來自用戶的反饋判斷,它很容易使人上癮。
2. 導(dǎo)言
幾乎所有的大型應(yīng)用程序都包括它的自己的日志和跟蹤API。順應(yīng)這個(gè)規(guī)則,E.U. SEMPER 項(xiàng)目決定寫它自己的跟蹤PAI。這是1996年初。在無數(shù)次加強(qiáng),幾次變形和許多工作后,那個(gè)API變成了如今的Log4j,一個(gè)流行的java日志包。這個(gè)包以Apache Software License協(xié)議發(fā)布,一個(gè)成熟的開放源嗎協(xié)議。最新的Log4j版本,包括全部的源碼,class文件和文檔,你可以在http://jakarta.apache.org/Log4j/上找到。順便,Log4j已經(jīng)給C, C++, C#, Python, Ruby, and Eiffel 語言都提供了接口。
為了調(diào)試而插入日志輸出到代碼里是一個(gè)低技術(shù)成分的方法,但它可能也是唯一的方法,因?yàn)檎{(diào)試器并不是一直可用或者可以適應(yīng)的,尤其對(duì)于多線程的分布使式的大型程序而言。
經(jīng)驗(yàn)指出調(diào)試是軟件開發(fā)周期中一個(gè)重要的組成部分。
Log4j擁有幾個(gè)優(yōu)點(diǎn):
首先,它提供關(guān)于運(yùn)行程序的準(zhǔn)確的環(huán)境。一旦代碼被插入,不需要人工干預(yù)就可以產(chǎn)生調(diào)試信息。
其次,日志輸出可以被有計(jì)劃的保存在永久媒體中以便日后研究。
另外,除了在開發(fā)周期中,一個(gè)充分詳盡的日志包可以被用來作為以后的統(tǒng)計(jì)工具。
Log4j當(dāng)然還有它的缺點(diǎn),它可能減慢程序。如果太詳細(xì),它可能導(dǎo)致屏幕盲目滾動(dòng)。排除這些情況,Log4j是可靠的,快速的,可以擴(kuò)展的。因?yàn)槿罩竞苌偈且粋€(gè)應(yīng)用程序的主要目的, 設(shè)計(jì)者們正努力使得Log4j API學(xué)習(xí)和使用簡(jiǎn)單化。
3. 日志類別、輸出源和布局
Log4j有三個(gè)主要的組件:日志類別(Loggers)、輸出源( Appenders)和布局(Layouts)。這三種類型的組件一起工作使得開發(fā)員可以根據(jù)信息的類型和級(jí)別記錄它們,并且在運(yùn)行時(shí)控制這些信息的輸出格式和位置。
3.1 日志類別的層次結(jié)構(gòu)(Loggers)
Log4j首要的相對(duì)于簡(jiǎn)單的使用System.out.println()方法的優(yōu)點(diǎn)是基于它的在禁止一些特定的信息輸出的同時(shí)不妨礙其它信息的輸出的能力。這個(gè)能力源自于日志命名空間,也就是說,所有日志聲明的空間,它根據(jù)一些開發(fā)員選擇的公式而分類。以前的觀察引導(dǎo)我們選擇類別作為包的中心概念。然而,自從Log4j的1.2版本,Logger類被Catalog類所取代,對(duì)于那些熟悉Log4j以前版本的人來說,Logger類可以被想象成僅僅是Category 類的別名。
Loggers 被指定為實(shí)體,Logger的名字是大小寫敏感的,它們遵循以下的命名
規(guī)則:
2 命名繼承
如果類別的名稱(后面加一個(gè)點(diǎn))是其子類別名稱的前綴,則它就是另一個(gè)類別的祖輩。
如果一個(gè)類別(Logger)和它的子類別之間沒有其它的繼承關(guān)系,我們就稱之為parent與child的關(guān)系。
例如,類別"com.foo"是類別"com.foo.Bar"的parent。相似的,"java"是"java.util"的parent,是"java.util.Vector"的父輩。.這個(gè)命名規(guī)則應(yīng)該被大多數(shù)的開發(fā)員所熟悉。
根(root) 類別位于logger繼承結(jié)構(gòu)的最上層。它有兩種例外:
1.它一直存在
2.它不能根據(jù)名稱而獲得。
調(diào)用類的靜態(tài)方法Logger.getRootLogger可以得到它。其它所有的Logger可以通過靜態(tài)方法Logger.getLogger而得到它們自己的實(shí)例。這個(gè)方法取希望的Logger名作為參數(shù)。Logger的一些基本的方法示例如下:
package org.apache.Log4j;
public Logger class {
// Creation & 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);
// generic printing method:
public void log(Level l, Object message);
}
Loggers可以被分配的級(jí)別。所有級(jí)別的集合包括:
DEBUG
INFO
WARN
ERROR
FATAL
它們被定義于org.apache.Log4j.Level 類。雖然我們不鼓勵(lì),但是你們可以通過繼承Level類來定義你們自己的級(jí)別。我們隨后將介紹一個(gè)比較好的方法。
如果一個(gè)Logger沒有被分配一個(gè)級(jí)別,那么它將從一個(gè)被分配了級(jí)別的最接近它的ancestor哪里繼承。
正規(guī)的說:
2 級(jí)別繼承
對(duì)于一個(gè)給定的Logger C,它的繼承的級(jí)別等于從C開始上溯到的第一個(gè)擁有非空級(jí)別的Logger的級(jí)別。
為了保證所有的Logger最終能夠繼承到一個(gè)級(jí)別,根Logger通常有一個(gè)已經(jīng)定義了的級(jí)別。
以下四個(gè)表中的數(shù)據(jù)演示了根據(jù)以上規(guī)則得到的結(jié)果。
類別名
分配的級(jí)別
繼承的級(jí)別
root
Proot
Proot
X
none
Proot
X.Y
none
Proot
X.Y.Z
none
Proot
Example 1
在例子1中,只有根Logger定義了一個(gè)級(jí)別,它的級(jí)別的值--"Proot"被所有其它的Loggers X, X.Y, 和X.Y.Z所繼承。
類別名
分配的級(jí)別
繼承的級(jí)別
root
Proot
Proot
X
Px
Px
X.Y
Pxy
Pxy
X.Y.Z
Pxyz
Pxyz
Example 2
在例子2中,所有的Logger都有一個(gè)被分配的級(jí)別值,所以它們不需要級(jí)別繼承。
類別名
分配的級(jí)別
繼承的級(jí)別
root
Proot
Proot
X
Px
Px
X.Y
none
Px
X.Y.Z
Pxyz
Pxyz
Example 3
在例子3中,根Logger,以及X和X.Y.Z被分別分配了級(jí)別Proot,Px和Pxyz。Logger X.Y從它的parent X繼承了級(jí)別值Px。
類別名
分配的級(jí)別
繼承的級(jí)別
root
Proot
Proot
X
Px
Px
X.Y
none
Px
X.Y.Z
none
Px
Example 4
在例子4中,根Logger和X被分別分配了級(jí)別"Proot"和"Px",Logger X.Y 和 X.Y.Z從被分配了級(jí)別的最接近它們的ancestor X那里得到繼承。
我們需要通過調(diào)用Logger的輸出的實(shí)例方法之一來實(shí)現(xiàn)日志請(qǐng)求。這些輸出的方法是debug, info, warn, error, fatal 和 log.
通過定義輸出方法來區(qū)分日志的請(qǐng)求的級(jí)別。例如,如果c是一個(gè)Logger的實(shí)例,那么聲明 c.info 就是一個(gè)INFO級(jí)別的日志請(qǐng)求。
如果一個(gè)日志的請(qǐng)求的級(jí)別高于或等于日志的級(jí)別那么它就能被啟用。反之,將被禁用。一個(gè)沒有被安排級(jí)別的Logger將從它的父輩中得到繼承。這個(gè)規(guī)則總結(jié)如下。
2 基本的選擇規(guī)則
假如在一個(gè)級(jí)別為q的Logger中發(fā)生一個(gè)級(jí)別為p的日志請(qǐng)求,如果p>=q,那么請(qǐng)求將被啟用。
這是Log4j的核心原則。它假設(shè)級(jí)別是有序的。對(duì)于標(biāo)準(zhǔn)級(jí)別,我們定義DEBUG < INFO < WARN < ERROR < FATAL.
以下是關(guān)于這條規(guī)則的一個(gè)例子。
// 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 progamitcally. This is usually done
// in configuration files.
cat.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");
調(diào)用getLogger方法將返回一個(gè)同名的Logger對(duì)象的實(shí)例。
例如,
Categoty x = Logger.getLogger("wombat");
Categoty y = Logger.getLogger("wombat");
x和y參照的是同一個(gè)Logger對(duì)象。
這樣我們就可以先定義一個(gè)Logger,然后在代碼的其它地方不需傳參就可以重新得到我們已經(jīng)定義了的Logger的實(shí)例.
同基本的生物學(xué)理論--父先于子相反,Log4j 的loggers可以以任何順序創(chuàng)造和配置。特別是,一個(gè)后實(shí)例化的"parent"logger能夠找到并且連接它的子logger。
配置Log4j的環(huán)境通常在一個(gè)應(yīng)用程序被初始化的時(shí)候進(jìn)行,最好的方法是通過讀一個(gè)配置文件。這個(gè)方法我們將簡(jiǎn)短介紹。
Log4j使得通過軟件組件命名logger很容易。我們可以通過Logger的靜態(tài)的初始化方法在每一個(gè)類里定義一個(gè)logger,令logger的名字等于類名的全局名,而實(shí)現(xiàn)logger的命名。這是一個(gè)實(shí)效的簡(jiǎn)單的定義一個(gè)logger的方法。因?yàn)槿罩据敵鰩в挟a(chǎn)生日志的類的名字,這個(gè)命名策略使得我們更容易定位到一個(gè)日志信息的來源。雖然普通,但卻是命名logger的常用策略之一。Log4j沒有限制定義logger的可能。開發(fā)員可以自由的按照它們的意愿定義logger的名稱。
然而,以類的所在位置來命名Logger好象是目前已知的最好方法。
3.2 輸出源(Appenders)和布局(Layouts)
有選擇的能用或者禁用日志請(qǐng)求僅僅是Log4j的一部分功能。Log4j允許日志請(qǐng)求被輸出到多個(gè)輸出源。用Log4j的話說,一個(gè)輸出源被稱做一個(gè)Appender. 。Appender包括console(控制臺(tái)), files(文件), GUI components(圖形的組件), remote socket servers(socket 服務(wù)), JMS(java信息服務(wù)), NT Event Loggers(NT的事件日志), and remote UNIX Syslog daemons(遠(yuǎn)程UNIX的后臺(tái)日志服務(wù))。它也可以做到異步記錄。
一個(gè)logger可以設(shè)置超過一個(gè)的appender。
用addAppender 方法添加一個(gè)appender到一個(gè)給定的logger。對(duì)于一個(gè)給定的logger它每個(gè)生效的日志請(qǐng)求都被轉(zhuǎn)發(fā)到該logger所有的appender上和該logger的父輩logger的appender上。換句話說,appender自動(dòng)從它的父輩獲得繼承。舉例來說,如果一個(gè)根logger擁有一個(gè)console appender,那么所有生效的日志請(qǐng)求至少會(huì)被輸出到console上。如果一個(gè)名為C的logger有一個(gè)file類型的appender,那么它就會(huì)對(duì)它自己以及所有它的子logger生效。我們也可以通過設(shè)置appender的additivity flag 為false,來重載appender的默認(rèn)行為,以便繼承的屬性不在生效。
調(diào)節(jié)輸出源(appender)添加性的規(guī)則如下。
輸出源的可添加性(Appender Additivity )
一個(gè)名為C的logger的日志定義的輸出將延續(xù)到它自身以及它的ancestor logger的appenders。這就是術(shù)語"appender additivity"的含義。
然而,logger C的一個(gè)ancestor logger P,它的附加標(biāo)志被設(shè)為false,那么C的輸出將被定位到所有C的appender,以及從它開始上溯到P的所有ancestor logger的appender。
Loggers的附加標(biāo)記(additivity flag)默認(rèn)為true。
下表是一個(gè)例子。
Logger
Name
Added
Appenders
Additivity
Flag
Output Targets
Comment
root
A1
not applicable
A1
The root logger is anonymous but can be accessed with the Logger.getRootLogger() method. There is no default appender attached to root.
x
A-x1, A-x2
true
A1, A-x1, A-x2
Appenders of "x" and root.
x.y
none
true
A1, A-x1, A-x2
Appenders of "x" and root.
x.y.z
A-xyz1
true
A1, A-x1, A-x2, A-xyz1
Appenders in "x.y.z", "x" and root.
security
A-sec
false
A-sec
No appender accumulation since the additivity flag is set to false.
security.access
none
true
A-sec
Only appenders of "security" because the additivity flag in "security" is set to false.
經(jīng)常,用戶希望自定義不但輸出源,而且定義輸出格式。這個(gè)是通過在一個(gè)appender上附加一個(gè)layout來完成的。layout是負(fù)責(zé)根據(jù)用戶的希望來格式化日志請(qǐng)求。而appender是負(fù)責(zé)發(fā)送格式化的輸出到它的目的地。PatternLayout,作為L(zhǎng)og4j標(biāo)準(zhǔn)版中的一部分,讓用戶指以類似C語言的printf方法的格式來指定日志的輸出格式。
例如,轉(zhuǎn)化模式為"%r [%t] %-5p %c - %m%n" 的PatternLayout 將輸出類似如下的信息:
176 [main] INFO org.foo.Bar - Located nearest gas station.
第一個(gè)欄位是自從程序開始后消逝的毫秒數(shù)。
第二個(gè)欄位是做出日志的線程。
第三個(gè)欄位是log的級(jí)別。
第四個(gè)欄位是日志請(qǐng)求相關(guān)的logger的名字。而"-"后的文字是信息的表述。
Log4j將根據(jù)用戶定義的公式來修飾日志信息的內(nèi)容。例如,如果你經(jīng)常需要記錄Oranges,一個(gè)在你當(dāng)前的項(xiàng)目被用到的對(duì)象類型,那么你可以注冊(cè)一個(gè)OrangeRenderer ,它將在一個(gè)orange需要被記錄時(shí)被調(diào)用。
對(duì)象渲染類似的類的結(jié)構(gòu)繼承。例如,假設(shè)oranges是fruits,如果你注冊(cè)了一個(gè)FruitRenderer,所有的水果包括oranges將被FruitRenderer所渲染。除非你注冊(cè)了一個(gè)orange。
對(duì)象渲染必須實(shí)現(xiàn)ObjectRenderer接口。
4. 配置
插入日志請(qǐng)求到應(yīng)用程序的代碼中需要大量的預(yù)先計(jì)劃和最終努力。觀察顯示大約4%的代碼是用來輸出的。
因此,大小適度的程序都被嵌入有成千個(gè)日志輸出語句。為了以無需手工的方式管理這些日志的輸出狀態(tài),給日志輸出以編號(hào)和規(guī)范變得勢(shì)在必行。
Log4j在程序中有充分的可配置性。然而,用配置文件配置Log4j具有更大的彈性。目前,它的配置文件支持xml和java properties(key=value)文件兩種格式。
讓我們以一個(gè)例子來演示它是如何做的。假定有一個(gè)用了Log4j的程序MyApp。
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è)靜態(tài)logger變量,并給予值為"MyApp"類的全路徑名稱。
MYApp用了定義在包c(diǎn)om.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()方法創(chuàng)建了一個(gè)相當(dāng)簡(jiǎn)單的Log4j的設(shè)置。它加入一
個(gè)ConsoleAppender到根logger。輸出將被采用了"%-4r [%t] %-5p %c %x - %m%n"模式
的PatternLayout所格式化。
注意,根logger默認(rèn)被分配了Level.DEBUG的級(jí)別。
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的子logger只連接到已經(jīng)存在的它們的父代。特別的是,名為
com.foo.bar的logger是直接連接到根logger,而不是圍繞著沒用的com或com.foo
logger。這顯著的提高了程序性能并且減少的內(nèi)存占用。
MyApp類配置Log4j是通過調(diào)用BasicConfigurator.configure 方法。其它的類僅僅
需要引入org.apache.Log4j.Logger 類,找到它們希望用的logger,并且用它就行。
以前的例子通常輸出同樣的日志信息。幸運(yùn)的是,修改MyApp是容易的,以便日志輸
出可以在運(yùn)行時(shí)刻被控制。這里是一個(gè)小小修改的版本。
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通知程序調(diào)用PropertyConfigurator()方法解析一個(gè)配置文件,并且根
據(jù)這個(gè)配置文件來設(shè)置日志。
這里是一個(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è)我們不在對(duì)com.foo包的任何類的輸出感興趣的話,隨后的配置文件向我們展示
了實(shí)現(xiàn)這個(gè)目的的方法之一。
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.
當(dāng)logger com.foo.bar沒有被分配一個(gè)級(jí)別,它將從com.foo繼承,在配置文件中
它被設(shè)置了WARN的級(jí)別。在Bar.doIt方法中定義的log為DEBUG級(jí)別,低于WARN,
因此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
以這個(gè)配置文件調(diào)用加強(qiáng)了的MyApp類將輸出如下信息.
INFO [main] (MyApp2.java:12) - Entering application.
DEBUG [main] (Bar.java:8) - Doing it again!
INFO [main] (MyApp2.java:15) - Exiting application.
另外,因?yàn)楦鵯ogger有被分配第二個(gè)appender,所以輸出也將被定向到example.log文件。
這個(gè)文件大小達(dá)到100kb時(shí)將自動(dòng)備份。備份時(shí)老版本的example.log文件自動(dòng)被移到
文件example.log.1中。
注意我們不需要重新編譯代碼就可以獲得這些不同的日志行為。我們一樣可以容易
的使日志輸出到UNIX Syslog daemon, 重定向所有的com.foo到NT Event logger,
或者轉(zhuǎn)發(fā)日志到一個(gè)遠(yuǎn)程的Log4j服務(wù)器,它根據(jù)本地server的策略來進(jìn)行日志輸出。例
如轉(zhuǎn)發(fā)日志事件到第二個(gè)Log4j服務(wù)器.
5. 默認(rèn)的初始化過程
Log4j類庫不對(duì)它的環(huán)境做任何假設(shè)。特別是沒有默認(rèn)的Log4j appender。在一些特別
的有著良好定義的環(huán)境下,logger的靜態(tài)inializer將嘗試自動(dòng)的配置Log4j。
java語言的特性保證類的靜態(tài)initializer當(dāng)且僅當(dāng)裝載類到內(nèi)存之時(shí)只會(huì)被調(diào)用一次。
要記住的重要一點(diǎn)是,不同的類裝載器可能裝載同一個(gè)類的完全不同的拷貝。
這些同樣類的拷貝被虛擬機(jī)認(rèn)為是完全不相干的。
默認(rèn)的initialization是非常有用的,特別是在一些應(yīng)用程序所依靠的運(yùn)行環(huán)境被準(zhǔn)確的
定位的情況下。例如,同一樣的應(yīng)用程序可以被用做一個(gè)標(biāo)準(zhǔn)的應(yīng)用程序,或一個(gè)
applet,或一個(gè)在web-server控制下的servlet。
準(zhǔn)確的默認(rèn)的initialization原理被定義如下:
1.設(shè)置系統(tǒng)屬性Log4j.defaultInitOverride為"false"以外的其它值,那么Log4j將
跳過默認(rèn)的initialization過程。
2.設(shè)置資源變量字符串給系統(tǒng)屬性Log4j.configuration。定義默認(rèn)initialization
文件的最好的方法是通過系統(tǒng)屬性Log4j.configuration。萬一系統(tǒng)屬性
Log4j.configuration沒有被定義,那么設(shè)置字符串變量resource 給它的默認(rèn)值
Log4j.properties。
3.嘗試轉(zhuǎn)換resource 變量為一個(gè)URL。
4.如果變量resource的值不能被轉(zhuǎn)換為一個(gè)URL,例如由于MalformedURLException違
例,那么通過調(diào)用
org.apache.Log4j.helpers.Loader.getResource(resource, Logger.class) 方法從
classpath中搜索resource,它將返回一個(gè)URL,并通知"Log4j.properties"的值是一個(gè)錯(cuò)
誤的URL。
看See Loader.getResource(java.lang.String) 查看搜索位置的列表。
5.如果沒有URL被發(fā)現(xiàn),那么放棄默認(rèn)的initialization。否則用URL配置Log4j。
PropertyConfigurator將用來解析URL,配置Log4j,除非URL以".xml"為結(jié)尾。
在這種情況下的話DOMConfigurator將被調(diào)用。你可以有機(jī)會(huì)定義一個(gè)自定義的
configurator。
系統(tǒng)屬性Log4j.configuratorClass 的值取自你的自定義的類名的全路徑。
你自定義的configurator必須實(shí)現(xiàn)configurator接口。
6. 配置范例
6.1 Tomcat下的初始化
默認(rèn)的Log4j initialization典型的應(yīng)用是在web-server 環(huán)境下。在tomcat3.x和tomcat4.x
下,你應(yīng)該將配置文件Log4j.properties放在你的web應(yīng)用程序的WEB-INF/classes 目錄
下。
Log4j將發(fā)現(xiàn)屬性文件,并且以此初始化。這是使它工作的最容易的方法。
你也可以選擇在運(yùn)行tomcat前設(shè)置系統(tǒng)屬性Log4j.configuration 。對(duì)于tomcat 3.x,
TOMCAT_OPTS 系統(tǒng)變量是用來設(shè)置命令行的選項(xiàng)。對(duì)于tomcat4.0,用系統(tǒng)環(huán)境變
量CATALINA_OPTS 代替了TOMCAT_OPTS。
Example 1
UNIX 命令行
export TOMCAT_OPTS="-DLog4j.configuration=foobar.txt"
告訴Log4j用文件foobar.txt作為默認(rèn)的配置文件。這個(gè)文件應(yīng)該放在WEB-INF/classes
目錄下。這個(gè)文件將被PropertyConfigurator所讀。每個(gè)web-application將用不同的默認(rèn)
配置文件,因?yàn)槊總€(gè)文件是和它的web-application 相關(guān)的。
Example 2
UNIX 命令行
export TOMCAT_OPTS="-DLog4j.debug -DLog4j.configuration=foobar.xml"
告訴Log4j輸出Log4j-internal的調(diào)試信息,并且用foobar.xml作為默認(rèn)的配置文件。
這個(gè)文件應(yīng)該放在你的web-application的WEB-INF/classes 目錄下。因?yàn)橛?xml的
擴(kuò)展名,它將被DOMConfigurator所讀。每個(gè)web-application將用不同的默認(rèn)
配置文件。因?yàn)槊總€(gè)文件都和它所在的web-application 相關(guān)的。
Example 3
UNIX 命令行
set TOMCAT_OPTS=-DLog4j.configuration=foobar.lcf -DLog4j.configuratorClass=com.foo.BarConfigurator
告訴Log4j用文件foobar.lcf作為默認(rèn)的配置文件。這個(gè)文件應(yīng)該放在你的
web-application的WEB-INF/classes 目錄下。因?yàn)槎x了Log4j.configuratorClass 系統(tǒng)屬
性,文件將用自定義的com.foo.barconfigurator類來解析。每個(gè)web-application將用不
同的默認(rèn)配置文件。因?yàn)槊總€(gè)文件都和它所在的web-application 相關(guān)的。
Example 4
UNIX 命令行
set TOMCAT_OPTS=-DLog4j.configuration=file:/c:/foobar.lcf
告訴Log4j用文件foobar.lcf作為默認(rèn)的配置文件。這個(gè)配置文件用URL file:/c:/foobar.lcf
定義了全路徑名。這樣同樣的配置文件將被所有的web-application所用。
不同的web-application將通過它們自己的類裝載器來裝載Log4j。這樣,每個(gè)Log4j的環(huán)
境將獨(dú)立的運(yùn)作,而沒有任何的相互同步。例如:在多個(gè)web-application中定義了
完全相同的輸出源的FileAppenders將嘗試寫同樣的文件。結(jié)果好象是缺乏安全性的。
你必須確保每個(gè)不同的web-application的Log4j配置沒有用到同樣的系統(tǒng)資源。
6.2 Servlet 的初始化
用一個(gè)特別的servlet來做Log4j的初始化也是可以的。如下是一個(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中定義隨后的servlet為你的web-application。
<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>
寫一個(gè)初始化的servlet是最有彈性的初始化Log4j的方法。代碼中沒有任何限制,你可
以在servlet的init方法中定義它。
7. Nested Diagnostic Contexts
在現(xiàn)實(shí)世界中的系統(tǒng)經(jīng)常不得不同時(shí)處理多個(gè)客戶端請(qǐng)求。在這樣的一個(gè)典型的多線程的系統(tǒng)中,不同的線程將處理不同的客戶端。Logging特別能夠適應(yīng)這種復(fù)雜的分布式的應(yīng)用程序的調(diào)試和跟蹤。一個(gè)常見的區(qū)分每個(gè)客戶端所輸出的Logging的方法是為每個(gè)客戶端實(shí)例化一個(gè)新的獨(dú)立的Logger。這導(dǎo)致Logger的大量產(chǎn)生,管理的成本也超過了logging本身。
唯一標(biāo)識(shí)每個(gè)log請(qǐng)求是一個(gè)輕量級(jí)的技術(shù)。Neil Harrison 在名為“Patterns for Logging Diagnostic Messages”的書中描述了這個(gè)方法in Pattern Languages of Program Design 3, edited by R. Martin, D. Riehle, and F. Buschmann (Addison-Wesley, 1997).
為了唯一標(biāo)識(shí)每個(gè)請(qǐng)求,用戶把上下文信息推入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如同一個(gè)堆棧樣管理每個(gè)線程。注意所有the org.apache.log4j.NDC 類的方法都是靜態(tài)的。假設(shè)NDC輸出被開啟,每次一個(gè)log 請(qǐng)求被生成時(shí),適當(dāng)?shù)膌og4j組件為將要輸出log的線程包含完整的NDC堆棧。這是在沒有用戶的干預(yù)的情況下做到的,用戶只負(fù)責(zé)在NDC中定位正確的信息,通過在代碼中正確位置插入很少的push和pop方法就行了。相反的,在代碼中per-client實(shí)現(xiàn)方法有著很大變化。
為了演示這個(gè)點(diǎn),讓我們以一個(gè)發(fā)送內(nèi)容到匿名客戶端的servlet為例。這個(gè)servlet可以在開始執(zhí)行每個(gè)其他代碼前的初始化時(shí)建立NDC。上下文信息可以是客戶主機(jī)的名字和其他的請(qǐng)求中固有的信息。
典型的信息包括cookies。因此,即使servlet同時(shí)為多個(gè)客戶同時(shí)提供服務(wù),log 被同樣的代碼初始化,例如屬于同一個(gè)logger,依然可以被區(qū)別,因?yàn)槊總€(gè)客戶請(qǐng)求將有不同的NDC堆棧。與之相比,Contrast this with the complexity of passing a freshly instantiated logger to all code exercised during the client′s request。
不過,一些詭異的程序,例如虛擬主機(jī)的web server記錄日志,不是一般的依靠虛擬主機(jī)的上下文,還要依靠軟件的組件發(fā)出請(qǐng)求。近來log4j的發(fā)布版本支持多層的樹形結(jié)構(gòu)。這個(gè)增強(qiáng)允許每個(gè)虛擬主機(jī)可以處理在樹型結(jié)構(gòu)中屬于它自己的logger。
8. 優(yōu)化
一個(gè)經(jīng)常引用的依靠于logging的參數(shù)是可以計(jì)算的花費(fèi)。這是一個(gè)合理的概念,一個(gè)適度的應(yīng)用程序可能產(chǎn)生成千上萬個(gè)日志請(qǐng)求。許多努力花在測(cè)量和調(diào)試logging的優(yōu)化上。Log4j要求快速和彈性:速度最重要,彈性是其次。
用戶應(yīng)該注意隨后的優(yōu)化建議。
1.日志為禁用時(shí),日志的優(yōu)化。
當(dāng)日志被徹底的關(guān)閉,一個(gè)日志請(qǐng)求的花費(fèi)等于一個(gè)方法的調(diào)用加上整數(shù)的比較時(shí)間。
在233mhz的Pentium II 機(jī)器上這個(gè)花費(fèi)通常在5-50納秒之間。
然而,方法調(diào)用包括參數(shù)構(gòu)建的隱藏花費(fèi)。
例如,對(duì)于logger cat,
logger.debug("Entry number: " + i + " is " + String.valueOf(entry[i]));
引起了構(gòu)建信息參數(shù)的花費(fèi),例如,轉(zhuǎn)化整數(shù)i和entry[i]到一個(gè)string,并且連接中間字符串,不管信息是否被輸出。這個(gè)參數(shù)的構(gòu)建花費(fèi)可能是很高,它主要決定于被調(diào)用的參數(shù)的大小。
避免參數(shù)構(gòu)建的花費(fèi)應(yīng)如下,
if(logger.isDebugEnabled() {
logger.debug("Entry number: " + i + " is " + String.valueOf(entry[i]));
}
如果logger的debug被關(guān)閉這將不會(huì)招致參數(shù)構(gòu)建的花費(fèi)。另一方面,如果logger是debug的話,它將產(chǎn)生兩次判斷 logger是否能用的花費(fèi)。一次是在debugenabled,一次是debug。這是無關(guān)緊要的,因?yàn)榕袛嗳罩镜哪苡?只占日志實(shí)際花費(fèi)時(shí)間的約1%。
在Log4j里,日志請(qǐng)求在Logger 類的實(shí)例里。Logger 是一個(gè)類,而不是一個(gè)接口。這大量的減少了在方法調(diào)用上的彈性化的花費(fèi)。
當(dāng)然用戶采用預(yù)處理或編譯時(shí)間技術(shù)去編譯出所有的日志聲明。這將導(dǎo)致完美的執(zhí)行成效。然而因?yàn)槎M(jìn)制應(yīng)用程序不包括任何的日志聲明的結(jié)果,日志不可能對(duì)那個(gè)二進(jìn)制程序開啟。以我的觀點(diǎn),以這種較大的代價(jià)來換取較小的性能優(yōu)化是不值得的。
2。當(dāng)日志狀態(tài)為啟用時(shí),日志的優(yōu)化。
這是本質(zhì)上的優(yōu)化logger的層次。當(dāng)日志狀態(tài)為開,Log4j依然需要比較請(qǐng)求的級(jí)別與logger的級(jí)別。然而, logger可能沒有被安排一個(gè)級(jí)別;它們將從它們的father繼承。這樣,在繼承之前,logger可能需要搜索它的ancestor。
這里有一個(gè)認(rèn)真的努力使層次的搜索盡可能的快。例如,子logger僅僅連接到它的存在的father logger。
在先前展示的BasicConfigurator 例子中,名為com.foo.bar 的logger是連接到跟根logger,因此繞過 了不存在的logger com和com.foo。這將顯著的改善執(zhí)行的速度,特別是解析logger的層結(jié)構(gòu)時(shí)。
典型的層次結(jié)構(gòu)的解析的花費(fèi)是logger徹底關(guān)閉時(shí)的三倍。
3.日志信息的輸出時(shí),日志的優(yōu)化。
這是主要花費(fèi)在日志輸出的格式化和發(fā)送它到它的輸出源上。這里我們?cè)僖淮蔚母冻雠σ允垢袷交瘓?zhí)行的盡可能快。同appender一樣。實(shí)際上典型的花費(fèi)大約是100-300毫秒。
詳情看org.apache.log4.performance.Logging。
雖然Log4j有許多特點(diǎn),但是它的第一個(gè)設(shè)計(jì)目標(biāo)還是速度。一些Log4j的組件已經(jīng)被重寫過很多次以改善性能。不過,投稿者經(jīng)常提出了新的優(yōu)化。你應(yīng)該滿意的知道,以SimpleLayout的配置執(zhí)行測(cè)試已經(jīng)展示了Log4j的輸出同System.out.println一樣快。
9. 總結(jié)
Log4j是一個(gè)用java寫成的流行的日志包。一個(gè)它與眾不同的特點(diǎn)是在logger中的繼承的概念。用logger的繼承可以以任意的間隔控制日志的狀態(tài)輸出。這個(gè)減少了體積和最小化日志的代價(jià)。
易管理性是Log4j API的優(yōu)點(diǎn)之一。只要日志定義被加入到代碼中,它們就可以用配置文件來控制。它們可以有選擇的被禁用,并且發(fā)送到不同的多個(gè)輸出源上,以用戶選擇好的格式。
Log4j的包被設(shè)計(jì)成,不需花費(fèi)沉重的執(zhí)行代價(jià)就可以保留它們?cè)诋a(chǎn)品中。
posted on 2006-04-06 23:18 zeroone0 閱讀(688) 評(píng)論(0) 編輯 收藏 所屬分類: 編程