本文是對log4j官網Introduction部分的翻譯,原文連接地址:http://logging.apache.org/log...。html
幾乎每一個大型應用都包含本身的日誌API。1996年,爲了整個項目的一致性,E.U. SEMPER項目團隊決定開發本身的日誌API。通過無數次的改進,這款日誌API成爲了Java領域很是流行的日誌package,這就是log4j。
在代碼中加日誌進行調試是一種低級的方式。但由於調試工具並不老是可使用,因此打日誌有時候是惟一的調試方法。例如多線程應用和分佈式應用。
有經驗代表在軟件開發週期中,日誌組件佔有重要的地位。加入日誌有許多好處。能夠經過日誌精確瞭解應用運行的狀態。加入代碼中的日誌,無需人們手工干預就可自動生成輸出結果。日誌的輸出結果能夠保存在永久存儲介質上,方便從此對其進行查看。此外,在軟件開發週期中,充足豐富的日誌也能夠當作審計材料使用。
日誌也有缺點,它會使應用程序變慢。若是日誌過多,還會致使屏幕閃動。爲了緩和這些缺點,log4j被設計成可靠的、快速的和可擴展的。因爲日誌不多是一個應用中關注的重點,因此log4j的API儘量設計的簡單易懂。java
log4j主要有三大組件——loggers、appenders和layouts。這三大組建共同協做,使開發者能夠根據不一樣的日誌級別和日誌類型輸出信息,而且能夠指定信息輸出的格式和信息輸出的目的地。web
相比於使用簡單的System.out.println語句,日誌API最大的優點就是它能夠禁止某一類型的日誌輸出,同時又不影響其它類型的日誌輸出。要實現這種能力,須要開發人員根據某種條件,將日誌劃分爲不一樣的類型。老版本的log4j將Category類做爲核心就是因爲上面這個緣由。但log4j到1.2版本時,已經使用Logger類代替了原來的Category類。對於那些熟悉老版本log4j的人,能夠簡單把Logger類當作Category類的一個別名。
Loggers是被命名的實體,Logger的名稱是大小寫敏感的,而且遵循層次命名規則。
舉個例子,命名爲「com.foo」的logger是命名爲「com.foo.Bar」的logger的父親。命名爲「java」的logger是命名爲「java.util」的logger的父親,是命名爲「java.util.Vector」的祖先。這種命名規則應該被許多研發人員所熟悉。
root logger位於整個logger繼承體系的最頂端,相比於普通logger它有兩個特別之處:apache
能夠經過調用Logger類的靜態方法getRootLogger獲取root logger對象。其它普通logger的實例能夠經過Logger類的另外一個靜態方法getLogger獲取。getLogger方法接受一個參數做爲logger的名字。
Logger類中的其它一些基本方法以下所示:api
package org.apache.log4j; public class Logger { // Creation & retrieval methods: public static Logger getRootLogger(); public static Logger getLogger(String name); // printing methods: public void trace(Object message); 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能夠被分配日誌級別,能夠分配的級別以下:
TRACE
DEBUG
INFO
WARN
ERROR和
FATAL
這些級別被定義在org.apache.log4j.Level類中。雖然你也能夠通繼承Level類定義你本身的專有級別,可是咱們不鼓勵你這樣作。
若是一個logger沒有指定任何level,那麼這個logger會從它的父親那裏繼承level。
爲了保證全部的logger能夠最終被指定一個level,root logger老是被分配一個level。
下面四個表是上面規則的例子:
Example 1服務器
在第一個例子中,只有root logger被分配了一個level值Proot,Proot會被其它全部的logger——x、x.y、x.y.z繼承。
Example 2多線程
在第二個例子中,全部的logger都被分配了一個level值,就不須要繼承level了。
Example 3app
在第三個例子中,root、x和x.y.z三個logger分別被分配了Proot、Px和Pxyz三個level值,x.y這個logger從它的父親那裏繼承level值。
Example 4less
在第四個例子中,root和x兩個logger分別被分配了Proot和Px這兩個level值。x.y和x.y.z兩個logger則從離本身最近的祖先x繼承level值。
能夠調用logger實例的printing方法輸入日誌。printing方法包括debug、info、warn、error、fatal和log。
按照定義,printing方法決定了日誌請求的等級。例如,c是一個logger實例,c.info("..")語句請求輸出INFO級別的日誌。
只有日誌請求級別大於等於日誌級別的時候,日誌請求才會被准許輸出信息。不然,日誌請求會被禁止。這條規則是log4j的核心。日誌的level是有序的。對於標準的日誌級別: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方法老是會返回同一個logger對象的引用,例如在下面兩行代碼中:
Logger x = Logger.getLogger("wombat"); Logger y = Logger.getLogger("wombat");
x和y引用的是同一個logger對象。
這樣在配置好一個logger實例以後,能夠很方便的在代碼的其它地方獲取到這個logger實例,而無需傳遞logger實例的引用。
與生物學中的父子關係不一樣,log4j中的父親不必定老是早於它的孩子出生。log4j中的logger實例能夠以任意的順序被構造或配置。一個parent logger即便在它的後代以後被實例化,它們也依然能夠創建起父子關係。
log4j的配置一般會在應用初始化時被完成。最經常使用的方式是經過讀取配置文件完成配置。稍後會討論這個過程。
在log4j中對一個logger實例命名很是的簡單,在每個類中能夠有一個靜態的logger實例對象,能夠用類的徹底限定名做爲logger實例的名字。這對於定義一個logger很是有用,因爲在輸出日誌的時候能夠帶有logger實例的名字,因此這種用類的徹底限定名做爲logger實例的名字能夠很容易看出日誌發生的位置。固然這只是一種通用作法,log4j對此並無限制,開發人員能夠隨意指定logger實例的名字。但無論怎樣,用類的徹底限定名做爲logger實例的名字是一個很是好的方式。
禁止和容許日誌輸出的能力只是所有功能的一部分。log4j容許將日誌輸出到多個目的地。在log4j的術語中,日誌輸出目的地被稱爲appender。目前,存在的appender包括命令行、文件、GUI組件、遠程socket服務器、JMS、NT事件日誌和遠程UNIX Syslog後臺進程。而且能夠支持異步的方式記錄日誌。
一個logger實例能夠同時掛載多個appender。
addAppender方法向logger實例添加一個appender。對於每個被容許的日誌輸出請求,logger實例不只將該請求轉發到本身全部的appender上,並且還將日誌輸出請求轉發到它祖先上的全部appender上。例如,若是一個命令行appender被添加到root logger上,那麼全部被容許的日誌請求至少會輸出到命令行中。若是在這個基礎上再向C logger中添加一個文件appender,那麼對於C和它的後代,被容許的日誌會同時輸出到命令行和文件。經過將additivity flag設置爲false,能夠覆蓋這種默認行爲。
Appender Additivity:對C logger的日誌輸出請求會轉發到C本身和它祖先們的所有appender。這種行爲用術語「appender additivity」表示。若是 P是C的祖先,P將additivity flag設置爲false。那麼C的日誌會輸出到它本身的appender和C到P之間(包括P)每一個logger的appender,而不會輸出到P以上祖先的appender。對於每一個logger,它的additivity flag默認是設置爲true的。
下面的表格是這樣的一個例子:
一般,研發不只但願指定日誌輸出的目的地,並且但願可以指定日誌輸出的格式。能夠在appender上關聯一個layout用於指定日誌輸出格式。layout會按照用戶的意願輸出必定格式的日誌信息。
PatternLayout可讓用戶像使用C語言中的printf那樣使用格式化表達式定製日誌輸出的格式。
例如,使用PatternLayout的表達式"%r [%t] %-5p %c - %m%n」能夠包含下面日誌信息:
176 [main] INFO org.foo.Bar - Located nearest gas station.
第一個字段是程序啓動到如今通過的毫秒數,第二個字段是輸出日誌的線程,第三個字段是日誌的級別,第四個字段是logger的名字。’-’後面的文本是日誌輸出信息。%n是換行。
log4j會按照用戶指定的具體條件輸出日誌內容。例如,若是你頻繁的須要輸出Orange類對象的日誌,那麼一能夠註冊一個OrangeRenderer,每當輸出orange的日誌時,它都會被調用。
Object rendering follows the class hierarchy. For example, assuming oranges are fruits, if you register a FruitRenderer, all fruits including oranges will be rendered by the FruitRenderer, unless of course you registered an orange specific OrangeRenderer.
Object renderers have to implement the ObjectRenderer interface.
在現有應用中加入日誌須要大量的工做。調研代表,大約有接近4%的代碼跟日誌有關。所以,即便不是那麼大的應用也會有成千上萬行的日誌代碼。Given their number, it becomes imperative to manage these log statements without the need to modify them manually.
og4j環境是徹底能夠經過寫程序進行配置的。然而,使用配置文件對log4j進行配置會更加的靈活。目前,配置文件能夠採用XML和properties兩種格式的文件。
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中使用的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配置。它以硬編碼的方式向root logger中添加了一個ConsoleAppender,日誌輸出會使用PatternLayout的模板"%-4r [%t] %-5p %c %x - %m%n」進行格式化。注意默認狀況下,root logger被分配的日誌級別是Level.DEBUG。
上面程序輸入的日誌爲:
0 [main] INFO MyApp - Entering application. 36 [main] DEBUG com.foo.Bar - Did it again! 51 [main] INFO MyApp - Exiting application.
下面的圖形是MyApp在調用完BasicConfigurator.configure方法以後的對象圖:
前面的這種方式只能一直輸出同一種配置模式的日誌信息,能夠很容易的在MyApp啓動時修改日誌配置信息,使其輸出不一樣配置模式的日誌。
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解析文件並對日誌進行配置。
下面這個配置文件產生的配置結果與以前使用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
使用這個配置文件會向命令行輸出以下日誌信息:
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,是將日誌信息直接輸出到example.log文件中的。當example.log文件到達100KB時,會發生roll-over。這時老版本的example.log會自動移動到example.log.1。
注意到要改變日誌行爲無需從新編譯代碼。咱們能夠改變配置文件讓其日誌輸出到UNIX Syslog daemon、將全部的com.foo輸出的日誌都重定向到NT Event logger、或者將日誌事件轉發到遠程的log4j服務器。
log4j庫沒有對它的運行環境作過任何的假設。也就是說,log4j沒有任何默認的appender。然而在一些環境下,日誌類的靜態初始化器會自動嘗試配置log4j。Java從語言層面保證在類加載的時候,靜態初始化器會被調用一次且僅被調用一次。可是要額外留意不一樣的classloader可能會對同一個類加載多個副本。
The default initialization is very useful in environments where the exact entry point to the application depends on the runtime environment. For example, the same application can be used as a stand-alone application, as an applet, or as a servlet under the control of a web-server.
The exact default initialization algorithm is defined as follows:
若是url不是以「.xml」爲後綴,那麼將會使用PropertyConfigurator解析url並對log4j進行配置。若是url的後綴是「.xml」,那麼將會使用DOMConfigurator完成上述工做。你能夠隨意指定一個定製的配置器(configurator)。log4j.configuratorClass系統屬性的值就是你定製配置器的全限定類名。你本身定製的配置器必須實現Configurator這個接口。