隨筆-72  評論-20  文章-0  trackbacks-1

          摘要

          這個文檔資料描述了log4j API,它的獨特的特性和設(shè)計原理。Log4j是由許多作者共同參與的開放源代碼項目。它允許開發(fā)人員以任意的精細(xì)程度控制哪些日志說明被輸出。通過使用外部的配置文件,可以在運行時配置它。最好的是,log4j 開發(fā)包很容易上手。注意,它也可能會使一些開發(fā)人員著迷。

          簡 介

          幾乎每個大的應(yīng)用程序都有它自己的日志和跟蹤程序的API。順應(yīng)這一規(guī)則,E.U. SEMPER項目組決定編寫它自己的程序跟蹤API(tracing API)。這開始于1996年早期。經(jīng)過無數(shù)的工作,更改和性能加強,這個API終于成為一個十分受歡迎的Java日志軟件包,那就是log4j。這個軟件包的發(fā)行遵守open source動議認(rèn)證的Apache Software License。最新的log4j版本包括全部的源代碼,類文件和文檔資料,可以在 http://logging.apache.org/log4j/找到它們。另外,log4j已經(jīng)被轉(zhuǎn)換成 C, C++, C#, Perl, Python, Ruby, 和 Eiffel 語言。

          把log statements插入到你的代碼中是一種排錯的低技能辦法。這也許是唯一的方法,因為排錯工具并不總是可以被使用或者適用于你的程序。對于多線程的應(yīng)用程序和多數(shù)發(fā)行的應(yīng)用程序,通常就是這樣的情形。

          經(jīng)驗告訴我們logging是開發(fā)過程中重要的一環(huán)。它具有多種優(yōu)點。首先,它能精確地提供運行時的上下文(context)。一旦在程序中加入了Log 代碼,它就能自動的生成并輸出logging信息而不需要人為的干預(yù)。另外,log信息的輸出可以被保存到一個固定的地方,以備以后研究。除了在開發(fā)過程中發(fā)揮它的作用外,一個性能豐富的日志記錄軟件包能當(dāng)作一個審計工具(audit tool)使用。

          Brian W. Kernighan 和 Rob Pike 在他們的"The Practice of Programming" 書中這樣寫到:  "The Practice of Programming"

          作為個人的選擇,除了得到一大堆程序跟蹤信息或一兩個變量值以外,我們傾
          向於不使用排錯器。一個原因是在詳細(xì)而復(fù)雜的數(shù)據(jù)結(jié)構(gòu)和控制流程中很容易
          迷失;我們發(fā)現(xiàn)認(rèn)真思考并在關(guān)鍵處加入自我檢查代碼和輸出指令,比起一步
          步看程序要效率高。在日志說明里查找比在明智地放置自我檢查代碼后的輸出
          里查找要費時。而決定在哪里放置打印指令要比在日志說明里一步步找到關(guān)鍵
          的代碼要省時間。更重要的是,自我檢查的排錯指令和程序并存;而排錯
          sessions是暫時的。

          Logging確實也有它的缺陷。它降低了程序運行的速度。它太冗長,查看時很容易錯過。為了減少這些負(fù)面影響,log4j 被設(shè)計得可靠,高效和靈活。因為,記錄日志很少是一個應(yīng)用程序的主要焦點,log4j API 盡量做到容易被理解和使用。

          Loggers, Appenders and Layouts

          Log4j 有三個主要組件:loggers, appenderslayouts。這三類組件一起應(yīng)用,可以讓開發(fā)人員能夠根據(jù)日志的類型和級別進行記錄,并且能在程序運行時控制log信息輸出的格式和往什么地方輸出信息。

          Logger hierarchy

          任何logging API 與簡單的System.out.println輸出調(diào)試信息方法比較,最主要的優(yōu)點在于它能夠關(guān)閉一些調(diào)試信息輸出而不影響其他人的調(diào)試。這種能力的實現(xiàn)是假設(shè)這些logging空間,也就是所有的可能發(fā)生的日志說明空間,可以根據(jù)程序開發(fā)人員選擇的標(biāo)準(zhǔn)進行分類。這一觀察以前使得我們選擇了category作為這個軟件包的中心概念。但是,在log4j 1.2版本以后,Logger類取代了Category類。對于那些熟悉早先版本的log4j的開發(fā)人員來說,Logger類只不過是Category類的一個別名。

          Loggers是被命名的實體。Logger的名字大小寫有區(qū)別(case-sensitive),并且它們遵守階層式的命名規(guī)則:

          Named Hierarchy

          如果一個logger 的名字后面跟著一個點號(dot),它就是點號(dot)后面的那個logger的前輩( ancestor),是這個晚輩(descendant) 的前綴。如果在它自己和這個晚輩之間沒有其它的前輩,它和這個晚輩之間就是關(guān)系。

          例如,叫做"com.foo"的logger是叫做 "com.foo.Bar"的logger的父輩 。同樣地,"java"是"java.util" 的父輩,是"java.util.Vector"的前輩。大多數(shù)開發(fā)人員都熟悉這種命名方法。  "com.foo"  "com.foo.Bar"  "java"  "java.util"  "java.util.Vector"

          根(root)logger 位于logger 階層的最上層。它在兩個方面很特別:

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

          通過這個類的靜態(tài)方法Logger.getRootLogger得到它(指RootLogger)。所有其他的loggers是通過靜態(tài)方法Logger.getLogger來實例化并獲取的。這個方法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)先級別。Level.html#DEBUG">DEBUG, INFO, WARN, ERRORFATAL這組級別在org.apache.log4j.Level類中有定義。你也可以通過Level類的子類去定義你自己的優(yōu)先級別,盡管我們不鼓勵你這樣做。在后面我們會講到一個更好的方法。

          如果一個logger沒有被指定優(yōu)先級別,它將繼承最接近的祖先所被指定的優(yōu)先級別。下面是更多關(guān)于優(yōu)先級別的信息:

          Level Inheritance

          對于一個給定的logger C,它繼承的級別等于logger階層里,從C開始往root logger上去的第一個non-null級別。

          要保證所有的loggers最終都繼承一個優(yōu)先級別,root logger總是有一個被指派的優(yōu)先級。

          下面是具有各種指派優(yōu)先級別值的四個表格,以及根據(jù)上面的規(guī)則所得出的繼承優(yōu)先級別。

          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的被指派的級別值。

          日志請求是通過調(diào)用一個日志實例的打印方法(之一)而產(chǎn)生的。這些打印方法是 log4j/Logger.html#debug(java.lang.Object)">debug, info, warn, error, fatal 和 log。

          根據(jù)定義,打印方法決定一個日志請求的級別。例如,如果c是一個日志實例,那么語句c.info("..") 就是級別為INFO的一個日志請求。  c.info("..")

          只有一個日志請求(A logging request)的級別高于或等于它的logger級別的時候才能夠被執(zhí)行。否則,則被認(rèn)為這個日志請求不能被執(zhí)行。一個沒有被定義優(yōu)先級別的logger將從層次關(guān)系中的前輩那里繼承優(yōu)先級別。這個規(guī)則總結(jié)如下:

          Basic Selection Rule

          在一個級別為q(被指定的或繼承的)的logger里,一個級別為p的日志請求,只有在p >= q 時才能夠被執(zhí)行。

          這個規(guī)則是log4j的核心。它假設(shè)級別是有先后順序的。對于標(biāo)準(zhǔn)的優(yōu)先級別來說,DEBUG < INFO < WARN < ERROR < FATAL

          這里是一個關(guān)于這個規(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對象。

          例如,在這里:
             Logger x = Logger.getLogger("wombat");
                                      Logger y = Logger.getLogger("wombat");
          x和y指向完全相同的logger對象。

          因此,通過這種方式可以配置一個logger,而不需要傳遞references就能在其他地方得到相同的實例。在生物的父子關(guān)系中父母總是排放在孩子們前面, log4j loggers與此有相互矛盾的地方,那就是log4j loggers可以以任何順序被產(chǎn)生和配置。特別的是,一個"parent" logger 會找到并連接他的后代,即使他是在他們之后被定義。

          Log4j環(huán)境通常是在程序被初始化的時候被配置的。最好的方式是通過閱讀一個配置文件去配置。我們會馬上討論到這方面的內(nèi)容。

          Log4j使得通過軟件組件的名稱去定義loggers的名字很容易。這可以通過在每個類中靜態(tài)地instantiating一個logger,讓logger的名字與這個合格的java類文件名相同來完成。這是一種有用并且直觀的定義loggers的方式。因為日志的輸出帶有產(chǎn)生它們的logger的名字,這種命名策略使我們能夠很方便地識別這些log信息的來源。不過,盡管這是通用的一種loggers命名策略,Log4j沒有限制怎樣對loggers進行命名。開發(fā)程序員可以根據(jù)自己的喜好隨意定義 loggers。  software component

          當(dāng)然,至今所知的最好的命名策略還是以它們所在的類的名稱來命名 loggers。

          Appenders and Layouts

          基于自身的logger選擇性地使用或不使用日志請求(logging requests )的能力僅僅整個Log4j能力的一部分。Log4j允許將log信息輸出到許多不同的輸出設(shè)備中。用log4j的語言來說,一個log信息輸出目的地就叫做一個appender。目前,log4j 的appenders可以將log信息輸出到consolefiles,GUI components,remote socket servers, JMS,NT Event Loggers,和 remote UNIX Syslog daemons。它還可以同時將log信息輸出到多個輸出設(shè)備中。   NT Event Loggers

          多個appenders可以和一個logger連接在一起。

          使用addAppender方法把一個appender加入到給定的logger上。一個給定的 logger的每一個被允許的日志請求都會被傳遞給這個logger的所有appenders,以及階層中高級別的appenders。換句話說appenders是從logger階層中不斷添加地被繼承的。例如,一個 console appender加給了root logger,那么,這個root logger所有被允許輸出的日志信息將被輸出到console。如果你又給一個名字為C的logger添加了一個 file appender,那么C 以及C的子輩的所有被允許的日志信息將被同時輸出到 file appenderconsole appender。可以通過把additivity flag設(shè)置為false來覆蓋這個默認(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的前輩,比如說P,P的additivity flag被設(shè)置為 false,那么,C的輸出信息將被輸出到C的所有appenders中去,以及它的前輩的——截止在P那里,包括P在內(nèi)的,appenders中去,但是不會輸出到P的前輩的 appenders中去。

          默認(rèn)情況下,Loggers的additivity flag設(shè)置為true

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

          Logger
          name(名稱)
          添加的
          Appenders
          Additivity
          旗標(biāo)
          輸出目標(biāo) 注釋
          A1 not applicable A1 Root logger是無名的,但是可以通過Logger.getRootLogger() 來訪問。Root logger沒有附帶默認(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 因為additivity flag被設(shè)置為 false,所以沒有appender繼承積累。
          security.access none true A-sec 因為"security" logger里的additivity flag被設(shè)置為false,所以僅僅只有"security" logger的appenders。

          通常,用戶不僅希望自己指定log信息的輸出目的地,而且,他們還希望指定 log信息的輸出格式。這可以通過和appender相關(guān)的layout實現(xiàn)。Layout負(fù)責(zé)根據(jù)用戶的需要去格式化log信息的輸出,而appender負(fù)責(zé)將一個格式化過的 log信息輸出到它的目的地。PatternLayout 是標(biāo)準(zhǔn)log4j發(fā)行包中的一部分,它讓用戶根據(jù)和C語言中的printf方法相似的轉(zhuǎn)換模式指定輸出格式。

          例如,具有"%r [%t] %-5p %c - %m%n" 轉(zhuǎn)換格式的PatternLayout 將輸出以下的式樣:

          176 [main] INFO org.foo.Bar - Located nearest gas station.

          第一個區(qū)域是從程序開始運行到輸出日志信息所用的毫秒數(shù)。第二個區(qū)域是產(chǎn)生日志請求的線程。第三個區(qū)域是這個log語句的優(yōu)先級別。第四個區(qū)域是和日志請求相關(guān)聯(lián)的logger名字。在'-' 之后的文字是這個log信息的內(nèi)容。

          同樣重要的是,log4j 將根據(jù)用戶指定的標(biāo)準(zhǔn)來表達(dá)log信息的內(nèi)容。例如,如果你經(jīng)常需要日志記錄Oranges,Oranges是你當(dāng)前項目中使用的一個對象類型,那么你可以注冊一個OrangeRenderer,這樣每當(dāng)需要日志記錄一個 orange時,OrangeRenderer就會被調(diào)用。

          對象的表達(dá)遵照類階層(class hierarchy)形式。例如,假設(shè)oranges是 fruits,你注冊了一個FruitRenderer,那么,包括oranges在內(nèi)的所有的fruits 都將由FruitRenderer來表達(dá),除非你自己為orange注冊了一個特定的 OrangeRenderer

          Object renderers必須實施ObjectRenderer界面。

          配 置

          在程序代碼中插入這些日志請求需要相當(dāng)大的工作量。調(diào)查顯示,大約%4左右的代碼是logging。因此,即便是中等大小的應(yīng)用程序也需要在它們的代碼中至少包含有幾千行的log語句。就從這個數(shù)目來看,管理這些log語句而不用人工地去修改它們是十分重要的。

          Log4j環(huán)境是完全能夠通過編程來配置的。但是使用配置文件去配置則更靈活。目前,Log4j的配置文件是以XML格式和JAVA properties (key=value) 格式編寫的。

          假設(shè)我們有個叫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的相關(guān)類,然后定義一個命名為MyApp的靜態(tài)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!");
                                                              }
                                                              }
                                                              

          通過調(diào)用BasicConfigurator.configure 方法產(chǎn)生一個相當(dāng)簡單的log4j的設(shè)置。這個方法將一個 ConsoleAppender添加到root logger,從而讓log信息輸出到 console。通過把PatternLayout設(shè)置為 %-4r [%t] %-5p %c %x - %m%n來確定輸出格式。

          注意,默認(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的對象圖表。

          注意,log4j 的子代loggers只和它們現(xiàn)有的前輩鏈接。在這里,名字叫 com.foo.Bar的logger直接和root logger鏈接,因此繞過了沒有被使用的com 或com.foo loggers。這樣極大地提高了log4j的性能并減少了內(nèi)存(memory)的使用。

          通過調(diào)用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方法去解讀配置文件并設(shè)置相應(yīng)的logging 。

          這里是一個配置文件的示例,這個配置文件產(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
                                                              

          由這個文件所配置的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 沒有指定的優(yōu)先級別,它就從com.foo中繼承優(yōu)先級別,而com.foo的優(yōu)先級別在配置文件中被設(shè)置為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
                                                              

          調(diào)用以這個配置文件增強了的MyApp會把下列輸出信息輸出到控制臺(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增加了第二個appender時,log信息將同時也被輸出到 example.log文件中。當(dāng)example.log文件達(dá)到100KB 后,example.log文件將被rolled over。當(dāng)roll-over 發(fā)生時,example.log 的老版本將自動被移到 example.log.1中去。

          注意,要獲得這些不同的logging行為并不需要重新編譯代碼。我們還可以簡單地通過修改log配置文件把log信息輸出到UNIX Syslog daemon中,把所有 com.foo的日志輸出轉(zhuǎn)指向NT Event logger 中,或者把log事件輸出到遠(yuǎn)程 log4j服務(wù)器中,當(dāng)然它要根據(jù)局部服務(wù)器規(guī)則進行l(wèi)og,例如可以把log事件輸出到第二個log4j服務(wù)器中去。

          默認(rèn)的初始化過程

          Log4j庫沒有對它的環(huán)境作任何假設(shè)。特別是,沒有默認(rèn)的log4j appenders。不過在一些精細(xì)定義過的情況下,這個Logger類的靜態(tài)的initializer會試圖自動配置log4j。 Java語言確保一個類的靜態(tài)的initializer在這個類被裝載到內(nèi)存里時被調(diào)用一次,而且僅僅一次。這點很重要,要記住不同的classloaders會裝載同一個類的不同復(fù)制版。這些同一個類的不同復(fù)制版在JVM看來是完全不相關(guān)的。

          默認(rèn)的初始化在這樣的環(huán)境中很有用處,那就是同一個程序依據(jù)運行時的環(huán)境作不同用處。例如,同樣一個程序可以在web-server的控制下作為單獨的程序,作為一個applet,或者作為一個servlet被使用。

          默認(rèn)的初始化運算法則定義如下:

          1. log4j.defaultInitOverride的系統(tǒng)屬性設(shè)置為 "false"以外的任何值將會造成 log4j跳過默認(rèn)的初始化過程。

             

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

             

          3. resource變量轉(zhuǎn)換為一個URL。

             

          4. 如果這個resource變量不能轉(zhuǎn)換為一個URL,例如,因為 MalformedURLException的緣故,那么就通過調(diào)用 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)

            有關(guān)搜尋地址列單,請參看Loader.getResource(java.lang.String)

             

          5. 如果不能找到URL,那就放棄默認(rèn)的初始化。否則,從URL配置log4j 。

            Configurator.html">PropertyConfigurator將被用于解讀URL來配置log4j,除非這個URL以".xml"擴展符結(jié)束,若這個URL以".xml"擴展符結(jié)束,DOMConfigurator則被使用。你可以選擇性地指定一個客戶自己的configurator。log4j.configuratorClass系統(tǒng)屬性的值就是你客戶自己的configurator的類名。你指定的客戶configurator必須 實施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自己會去找到屬性文件并初始化。這樣做又簡單又有效。

          你可以選擇在Tomcat啟動之前設(shè)置系統(tǒng)屬性log4j.configuration 。對于 Tomcat 3.x ,TOMCAT_OPTS 環(huán)境變量被用來設(shè)置命令行選項。對于 Tomcat 4.0,使用CATALINA_OPTS環(huán)境變量而不是TOMCAT_OPTS 。

          例子

          Unix shell 命令

          export TOMCAT_OPTS="-Dlog4j.configuration=foobar.txt"
          告訴log4j 使用文件foobar.txt 作為默認(rèn)的配置文件。這個文件應(yīng)該被放置在你的網(wǎng)絡(luò)應(yīng)用程序的WEB-INF/classes目錄下面。文件將通過 PropertyConfigurator被讀取。每個網(wǎng)絡(luò)應(yīng)用程序使用不同的默認(rèn)配置文件,因為每個文件都是和每個網(wǎng)絡(luò)應(yīng)用程序相關(guān)的。

          例子

          Unix shell 命令

             export TOMCAT_OPTS="-Dlog4j.debug -Dlog4j.configuration=foobar.xml"
                                                  
          告訴log4j輸出log4j-內(nèi)部排錯信息,并使用文件foobar.xml 作為默認(rèn)的配置文件。這個文件應(yīng)該被放置在你的網(wǎng)絡(luò)應(yīng)用程序的WEB-INF/classes目錄下面。因為文件以.xml擴展符結(jié)尾,將使用DOMConfigurator來讀取。每個網(wǎng)絡(luò)應(yīng)用程序使用不同的默認(rèn)配置文件,因為每個文件都是和每個網(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)的配置文件。這個文件應(yīng)該被放置在你的網(wǎng)絡(luò)應(yīng)用程序的WEB-INF/classes 目錄下面。根據(jù)log4j.configuratorClass 系統(tǒng)屬性的定義 ,文件將通過將使用客戶自己的configurator—— com.foo.BarConfigurator被讀取。每個網(wǎng)絡(luò)應(yīng)用程序使用不同的默認(rèn)配置文件,因為每個文件都是和一個網(wǎng)絡(luò)應(yīng)用程序相關(guān)的。

          例子

          Windows shell 命令

             set TOMCAT_OPTS=-Dlog4j.configuration=file:/c:/foobar.lcf
          告訴log4j使用文件c:\foobar.lcf 作為默認(rèn)的配置文件。這個配置文件完全由 URL file:/c:/foobar.lcf指定。因此,這個相同的配置文件將被所有網(wǎng)絡(luò)應(yīng)用程序使用。  c:\foobar.lcf

          不同的網(wǎng)絡(luò)應(yīng)用程序通過它們各自的classloaders裝載log4j的類。因此,每個 log4j環(huán)境的image會獨自地,沒有任何相互協(xié)調(diào)地行動。例如,在多個網(wǎng)絡(luò)應(yīng)用程序的配置中,FileAppenders若定義得完全相同,它們就會編寫相同的文件。這樣的結(jié)果就不那么令人滿意。你必須保證不同的網(wǎng)絡(luò)應(yīng)用程序的log4j配置不使用相同的系統(tǒng)資源。

          初始化servlet

          還可以使用一個特別的servlet來進行l(wèi)og4j初始化。這里就是個示例:

          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>
                                                              

          編寫一個initialization servlet 是最靈活的方式來初始化log4j。不受任何限制,你可以在這個servlet的init()方法里放入任何代碼。

          Nested Diagnostic Contexts

          實際情況下的大多數(shù)系統(tǒng)都需要同時處理多個客戶端問題。在這種系統(tǒng)的典型的多線程實施中,通常是不同的線程去分別處理不同的客戶需求。Logging特別適合于復(fù)雜的程序跟蹤和排錯。一個通常的處理辦法是通過給每個客戶產(chǎn)生一個新的分離開的logger來達(dá)到把不同的客戶的日志輸出信息區(qū)分開來。但這促進了loggers的增殖,加大了logging的管理負(fù)擔(dān)。

          一個更簡潔的技術(shù)是獨特地標(biāo)記來自于同一個客戶的每一個日志請求。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

          要獨特地標(biāo)記每個日志請求,用戶把上下文信息送入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類中所有的方法都是靜態(tài)的。假設(shè)NDC打印功能被打開,每一次若有日志請求,相應(yīng)的log4j組件就把這個當(dāng)前線程的整個 NDC stack包括在日志輸出中打印出來。這樣做不需要用戶干預(yù),用戶只需要在代碼中明確指定的幾點通過pushpop方法將正確的信息放到NDC中就行了。相反,per-client logger方法需要在代碼中作很多更改。

          為了說明這一點,我們舉個有關(guān)一個servlet把信息內(nèi)容發(fā)送到多個客戶的例子。這個Servlet程序在開始接到客戶端的請求,執(zhí)行其它代碼之前,首先創(chuàng)建一個NDC。該上下文信息可能是客戶端的主機名,以及其他請求中固有的信息,通常是包含在cookies中的信息。因此即便這個Servlet程序可能同時要服務(wù)于多個客戶,由相同的代碼啟動的這些logs,比如屬于同一個logger,它們?nèi)匀荒軌虮粎^(qū)分開來,因為不同的客戶端請求具有不同的NDC stack。這與在客戶請求期間把一個實例化的logger傳遞給所有要被執(zhí)行的代碼的復(fù)雜性形成了反差。

          然而,一些復(fù)雜的應(yīng)用程序,比如虛擬網(wǎng)絡(luò)服務(wù)器,必須依據(jù)虛擬主機的上下文語言環(huán)境,以及發(fā)布請求的軟體組件來作不同的log。最近的log4j發(fā)行版支持多階層樹。這一功能的加強允許每個虛擬主機擁有它自己的logger階層版本。

          性能

          一個經(jīng)常提出的爭議就是logging的運算開銷。這種關(guān)注是有道理的,因為即便是一個中等大小的應(yīng)用程序至少也會產(chǎn)生幾千個log輸出。許多工作都花費在測量和改進logging性能上。Log4j聲明它是快速和靈活的:速度第一,靈活性第二。

          用戶需要清楚地了解下面這些與性能相關(guān)的問題:

          1. Logging performance when logging is turned off.

            當(dāng)logging被完全關(guān)閉或只是set of levels被關(guān)閉,日志請求的開銷是方法的調(diào)用和整數(shù)的比較。在一個233 MHz Pentium II機器上,這種開銷通常在5 to 50 毫微秒范圍內(nèi)。  set of levels

            不過,方法的調(diào)用包含有參數(shù)的建造上的“隱閉”開銷。

            例如下面的logger cat程序段中:

                 logger.debug("Entry number: " + i + " is " + String.valueOf(entry[i]));
                                                        
            不管message被日志記錄與否,構(gòu)造message參數(shù)的開銷還是有的,比如說,把整數(shù)i 和數(shù)組entry[i]轉(zhuǎn)化為String,連接中間字串。參數(shù)構(gòu)造的這種開銷可能很高,它依賴于所介入的參數(shù)數(shù)量有多少。

            為了避免這種參數(shù)構(gòu)造開銷,把以上的代碼段改寫為:

                  if(logger.isDebugEnabled() {
                                                        logger.debug("Entry number: " + i + " is " + String.valueOf(entry[i]));
                                                        }
                                                        

            如果排錯功能不被使用,就不會有參數(shù)構(gòu)造上的開銷。但是,另一方面,如果 logger的排錯功能被起用,就會有倆倍的開銷用于評估logger是否被起用:一次是判斷debugEnabled,一次是判斷debug是否被啟用。但這不是極重的負(fù)擔(dān),因為評估logger的時間只有整個log語句執(zhí)行時間的1%

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

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

          2. The performance of deciding whether to log or not to log when logging is turned on.

             

            本質(zhì)上影響性能的因素是logger的層次關(guān)系。當(dāng)logging功能被打開時,log4j仍然需要把log請求的級別去與request logger的級別作比較。不過,有些loggers 并沒有指派的優(yōu)先級別,但它可以從它的上一層logger那里繼承優(yōu)先級別。因此在繼承優(yōu)先級之前,logger可能需要搜索它的ancestors。

            Log4j在這方面做了很大的努力,以便使這種階層的優(yōu)先級別搜尋(hierarchy walk )盡可能的快速。例如,子代loggers僅僅只和它們現(xiàn)有的ancestors鏈接。在前面的BasicConfigurator示例中,叫做com.foo.Bar的logger 直接與 root logger鏈接,繞過了不存在的com或com.foo loggers。這極大地提高了優(yōu)先級別搜尋的速度。

            階層的優(yōu)先級搜尋(walking the hierarchy )的開銷在于它比logging完全關(guān)閉時要慢三倍。

          3. Actually outputting log messages

            這里講的是log輸出的格式化和把log信息發(fā)送到目標(biāo)所在地的開銷。Log4j在這方面也下了大力氣讓格式化能盡快執(zhí)行。對appenders也是一樣。通常情況下,格式化語句的開銷可能是100到300微秒的處理時間。確切數(shù)字請參看 org.apache.log4.performance.Logging

          盡管log4j具有許多功能特性,但速度是第一設(shè)計目標(biāo)。為了提高性能,一些 log4j的部件曾經(jīng)被重寫過許多次。即使這樣,log4j的貢獻者們不斷提出新的優(yōu)化辦法。你應(yīng)該很驚喜地發(fā)現(xiàn)當(dāng)以SimpleLayout來配置時,性能測試顯示使用 log4j日志和使用System.out.println日志同樣快。

          結(jié)論

          Log4j是用Java編寫的一個非常流行的logging開發(fā)包。它的一個顯著特性之一是在loggers里運用了繼承的概念。使用這種logger的層次關(guān)系,就可能準(zhǔn)確地控制每一個log語句的輸出。這樣減少了log信息的輸出量并降低了logging的開銷。

          Log4j API的優(yōu)點之一是它的可管理性。一旦log語句被插入到代碼中,他們就能被配置文件控制而無需重新編譯源代碼。Log信息的輸出能夠有選擇地被起用或關(guān)閉,用戶能夠按照自己選擇的格式將這些log信息輸出到許多不同的輸出設(shè)備中。Log4j軟件包的設(shè)計是在代碼中保留log語句的同時不造成很大的性能損失。

          posted on 2007-07-23 17:01 前方的路 閱讀(192) 評論(0)  編輯  收藏 所屬分類: Java技術(shù)
          主站蜘蛛池模板: 盐源县| 天等县| 搜索| 江达县| 射阳县| 醴陵市| 永川市| 田阳县| 彭山县| 德格县| 上林县| 遵义县| 江门市| 沂南县| 密山市| 宣武区| 日土县| 西平县| 木兰县| 布尔津县| 门头沟区| 汶上县| 陇南市| 溆浦县| 中超| 澄迈县| 科技| 樟树市| 浪卡子县| 鄂伦春自治旗| 靖远县| 永嘉县| 慈利县| 且末县| 左云县| 襄汾县| 克什克腾旗| 商丘市| 金华市| 黎城县| 咸阳市|