OMG,到底在尋找什么..................
          (構造一個完美的J2EE系統所需要的完整知識體系)
          posts - 198,  comments - 37,  trackbacks - 0
          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,?appenders和layouts。這三類組件一起應用,可以讓開發人員能夠根據日志的類型和級別進行記錄,并且能在程序運行時控制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?階層的最上層。它在兩個方面很特別:

          它總是存在的,
          不能通過使用它的名字直接得到它。
          通過這個類的靜態方法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可以被指派優先級別。DEBUG,?INFO,?WARN,?ERROR?和FATAL這組級別在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.Y?和?X.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,?Px?和Pxyz。Logger?X.Y?從它的父輩X那里繼承它的級別值。Logger
          name(名稱)?指派
          級別?繼承
          級別?
          根?Proot?Proot?
          X??Px?Px?
          X.Y??none?Px?
          X.Y.Z?none?Px?
          例子?

          在示例4中,loggers?root和X?分別被指派級別值Proot和Px。Logger?X.Y和X.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?時才能夠被執行。
          ?


          這個規則是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(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方法,返回的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信息輸出到console,files,GUI?components,remote?socket?servers,?JMS,NT?Event?Loggers,和?remote?UNIX?Syslog?daemons。它還可以同時將log信息輸出到多個輸出設備中。???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?appender和console?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。
          ?


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

          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信息輸出到它的目的地。PatternLayout?是標準log4j發行包中的一部分,它讓用戶根據和C語言中的printf方法相似的轉換模式指定輸出格式。

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

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

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

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

          把resource變量轉換為一個URL。

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


          如果不能找到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包括在日志輸出中打印出來。這樣做不需要用戶干預,用戶只需要在代碼中明確指定的幾點通過push和pop方法將正確的信息放到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.?
          當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。在我看來,這是為了小的性能增加而付出大的代價。


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

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


          Actually?outputting?log?messages?
          這里講的是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.?
          posted on 2006-12-27 13:47 OMG 閱讀(509) 評論(0)  編輯  收藏 所屬分類: Log4j

          <2006年12月>
          262728293012
          3456789
          10111213141516
          17181920212223
          24252627282930
          31123456

          常用鏈接

          留言簿(1)

          隨筆分類

          隨筆檔案

          IT風云人物

          文檔

          朋友

          相冊

          經典網站

          搜索

          •  

          最新評論

          閱讀排行榜

          評論排行榜

          主站蜘蛛池模板: 驻马店市| 苍溪县| 沧州市| 周宁县| 明星| 霍邱县| 九龙坡区| 昌黎县| 石楼县| 赣榆县| 城口县| 丘北县| 通道| 错那县| 且末县| 乌鲁木齐县| 三亚市| 张掖市| 萨嘎县| 万宁市| 岳西县| 赣榆县| 襄汾县| 通州区| 中山市| 长岭县| 龙胜| 阳信县| 万安县| 措美县| 宝山区| 桂平市| 和林格尔县| 德钦县| 大兴区| 丰镇市| 丹东市| 虎林市| 铜鼓县| 汝南县| 宜春市|