log4j 簡明手冊

log4j 簡明手冊

Ceki Gülcü

March 2002

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, appenderslayouts。這三類組件一起應用,可以讓開發人員能夠根據日誌的類型和級別進行記錄,並且能在程序運行時控制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 階層的最上層。它在兩個方面很特別:

  1. 它總是存在的,
  2. 不能通過使用它的名字直接得到它。

通過這個類的靜態方法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, ERRORFATAL這組級別在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.YX.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, PxPxyz。Logger X.Y 從它的父輩X那裏繼承它的級別值。

例子
Logger
name(名稱)
指派
級別
繼承
級別
Proot Proot
X Px Px
X.Y none Px
X.Y.Z none Px

在示例4中,loggers root和X 分別被指派級別值ProotPx。Logger X.YX.Y.Z繼承它們最接近的父輩X的被指派的級別值。

日誌請求是通過調用一個日誌實例的打印方法(之一)而產生的。這些打印方法是 log4j/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 時才能夠被執行。

consolefiles,GUI components,remote socket servers, JMS,NT Event Loggers,和 remote UNIX Syslog daemons。它還可以同時將log信息輸出到多個輸出設備中。   NT Event Loggers

這個規則是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();

   Logger barlogger = Logger.getLogger("com.foo.Bar");

   // This request is enabled, because  >= .
   logger.("Low fuel level.");

   // This request is disabled, because  < .
   logger.("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  >= .
   barlogger.("Located nearest gas station.");

   // This request is disabled, because  < .
   barlogger.("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。

Appenders and Layouts

基於自身的logger選擇性地使用或不使用日誌請求(logging requests )的能力僅僅整個Log4j能力的一部分。Log4j允許將log信息輸出到許多不同的輸出設備中。用log4j的語言來說,一個log信息輸出目的地就叫做一個appender。目前,log4j 的appenders可以將log信息輸出到

多個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 appenderconsole 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

PatternLayout 是標準log4j發行包中的一部分,它讓用戶根據和C語言中的printf方法相似的轉換模式指定輸出格式。

下面的表格顯示一個示例:

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信息輸出到它的目的地。

例如,具有"%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被使用。

默認的初始化運算法則定義如下:

  1. log4j.defaultInitOverride的系統屬性設置爲 "false"以外的任何值將會造成 log4j跳過默認的初始化過程。

     

  2. resource這個string變量設置爲log4j.configuration系統屬性的值。最好的方法指定默認初始化文件是通過log4j.configuration系統屬性來指定。在log4j.configuration系統屬性沒有被定義的情況下,把resource這個string變量設置成它的默認值"log4j.properties"。

     

  3. resource變量轉換爲一個URL。

     

  4. 如果這個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)

     

  5. 如果不能找到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包括在日誌輸出中打印出來。這樣做不需要用戶干預,用戶只需要在代碼中明確指定的幾點通過pushpop方法將正確的信息放到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. The performance of deciding whether to log or not to log when logging is turned on. Actually outputting log messages

當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是否被起用:一次是判斷debugEnabled,一次是判斷debug是否被啓用。但這不是極重的負擔,因爲評估logger的時間只有整個log語句執行時間的1%

在log4j中,把日誌請求作爲Logger類的實例。Logger是類而不是接口,這主要是爲了減少程序調用的開銷,但犧牲了接口所能帶來的靈活性。

有些用戶使用預處理或compile-time技術來編譯所有log語句。這樣logging方面的性能是很好。但是,因爲resulting application binary沒有包含任何log語句,你不能對這個二進制程序起用logging。在我看來,這是爲了小的性能增加而付出大的代價。

 

本質上影響性能的因素是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完全關閉時要慢三倍。

這裏講的是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.  
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章