摘自:http://wucuixia.blog.sohu.com/12057602.html

一、日志背景
我們在編程時經常不可避免地要使用到一些日志操作,比如開發階段的調試信息、運行時的日志記錄及審計。調查顯示,日志代碼占代碼總量的4%。通常大家可以簡單地使用System.out.println()語句輸出日志信息,但是在發布時,通常不想在正式的版本中打印這些開發時的調試信息,于是又要手工地把這些語句刪除,所以大量的這樣的System.out.println()調試語句會帶來麻煩。更多做法是把它封閉一個簡單的輸出,比如:
 1public final class Debug 
 2 
{
 3    public static final boolean debugOn = true
;
 4  public static void println(String msg) 
{
 5      if
 (debugOn) 
 6       
{
 7
           System.out.println(msg);
 8        }

 9     }

10 }
 
這樣就可把代碼中要用System.out.println()輸出調試信息的地方全用Debug.println()替代,當在發布時只需把Debug類中的debugOn改成false即可。
這樣做雖然在一定程度上解決了問題,但如果需要的是更復雜的日志系統呢,比如把日志信息保存為文件等別的形式;又或是當系統在試運行了一段時間后我們又要更改某些試運行時的測試信息。如果真的遇到這樣的情況,也行就只有修改代碼了,這樣又給開發工作帶來了麻煩。
Log4J是Apache軟件基金會Jakarta項目下的一個子項目,是用Java編寫的優秀日志工具包。通過Log4J可以在不修改代碼的情況下,方便、靈活地控制任意粒度的日志信息的開啟或關閉,然后使用定制的格式,把日志信息輸出到一個或多個需要的地方。并且,Log4J還有一條平滑的學習曲線,在三分鐘內就可學會它的簡單使用。隨著使用深入,你會發現Log4J功能的強大,幾乎可以滿足日志方面的所有需要。


二、LOG4J

1 第一個log4j程序及其原理

讓我們從現在開始記時,看完成第一個log4j程序要不要3分鐘。首先log4j-1.2.7.jar考到你的類路徑下。然后創建一個類,代碼如下:
 1 package TestLog4j;
 2 import
 org.apache.log4j.Logger;
 3 import
 org.apache.log4j.BasicConfigurator;
 4import
 org.apache.log4j.PropertyConfigurator;
 5import
 org.apache.log4j.Priority;;
 6

 7 public class
 TestLog4j 
 8
{
 9   //代碼(1)

10   static Logger logger = Logger.getLogger(TestLog4j.class.getName());
11    public TestLog4j(){}

12   public static void main(String[] args)
13 
{
14  //代碼(2)

15   BasicConfigurator.configure();
16         //代碼(3)

17         logger.debug("Start of the main() in TestLog4j");
18         logger.info("Just testing a log message with priority set to INFO"
);
19                logger.warn("Just testing a log message with priority set to WARN"
);
20           logger.error("Just testing a log message with priority set to ERROR"
);
21          logger.fatal("Just testing a log message with priority set to FATAL"
);
22           logger.log(Priority.DEBUG, "Testing a log message use a alternate form"
);
23          logger.debug("End of the main() in TestLog4j"
);
24       }

25 }
 
最后運行這個類,你就會看到運行結果為:
0 [main] DEBUG TestLog4j.TestLog4j  - Start of the main() in TestLog4j
10 [main] INFO TestLog4j.TestLog4j  - Just testing a log message with priority set to INFO
10 [main] WARN TestLog4j.TestLog4j  - Just testing a log message with priority set to WARN
21 [main] ERROR TestLog4j.TestLog4j - Just testing a log message with priority set to ERROR
21 [main] FATAL TestLog4j.TestLog4j  - Just testing a log message with priority set to FATAL
111 [main] DEBUG TestLog4j.TestLog4j  - Testing a log message use a alternate form
111 [main] DEBUG TestLog4j.TestLog4j  - End of the main() in TestLog4j
好了,看一看你的表,應該不到3分鐘吧。在這短短的3分鐘里,我們做了些什么呢?下面我們來分析一下代碼。
1)    首先代碼(1)先通過Logger類的getLogger()方法得到一個Logger類的對象。在getLogger()方法中,通常把所在的類的Class對象或是所在類的全名作為參數。運用log4j輸出日志要用到Logger對象。
2)    然后代碼(2)進行一些必要的初始化,如要把調試信息輸出到哪。當用System.out.println()時可以很明確的知道要把信息輸出到標準輸出設備且只能輸出到那里。運用log4j,我們可以輸出到許多地方,如控制臺、文件、HTML文件等,至于要輸出到哪里,就要自己進行初始化。在代碼(2),我們調用自帶的初始化方法來完成初始化。用這個方法進行初始化就不能體現出log4j的靈活性,所以基本上不會這樣做。Log4j提供了用XML文件或 Java配置文件來配置設置的方法,在下面我們將進行介紹。
3)    接著代碼(3)就是輸出信息的代碼了。你可以看到代碼(3)中嘗試了用幾種不同的方法來輸出信息,對于這幾種信息的作用,我會在下面進行介紹,你現在只需把它當成是輸出語句最后,我們來看一下運行結果(日志信息)的意義。第一個數字是指程序開始運行到運行該日志語句所經歷的毫秒數(用來做一點運行效率分析也不錯),“[main]”是日志事件發生的線程,隨后的“DEBUG”、“INFO”等信息是相應日志信息的優先級別,“TestLog4j.TestLog4”是當前TestLog4所在的包和名稱,最后是日志信息。就行。

2 實例原理


雖然完成了第一程序了,但程序中的內容還是不太了解。好,現在我就對上面的例子用到的log4j的原理進行講解。在以后的章節中,我都會采取這種先實例,再根據實例來介紹所涉及的log4j原理的方法。
2.1 記錄器Logger

Logger類是在log4j1.2以后才有的,以前是用Category類來實現現在的Logger類的功能的。從API可知,Logger類是Category類的子類。Logger類的代碼如下:
  1. package org.apache.log4j;
  2. public class Logger {
  3.     // 創建和取回方法:
  4.     public static Logger getRootLogger();
  5. public static Logger getLogger(String name);
  6. public static Logger getLogger(Class class1);
  7.     // 打印方法:
  8.     public void debug(Object message);
  9.     public void info(Object message);
  10.     public void warn(Object message);
  11.     public void error(Object message);
  12.     public void fatal(Object message);
  13.     // 常用打印方法:
  14.     public void log(Level l, Object message);
  15. }

在討論Logger類中的方法之前,我先講一下log4j中的級別(level)的概念。
2.1.1 級別Level

Log4j中的日志級別分為五種:DEBUG、INFO、WARN、ERROR和FATAL,這五種級別從左到右級別依次增加。
2.1.2 Logger中的打印函數與級別

對于每一個記錄器,我們都可對它賦于一定的級別,而打印函數打印的即是相應級別的信息。當對一個級別為A的Logger調用級別為B的打印方法時,只有當 B>=A時才會進行打印。例如,如果有一個級別為WARN的Logger對象logger,只有對它調用 logger.warn (message)、logger.error (message)和logger.fatal (message)這三個打印函數才會打印信息;而調用logger.debug (message)和logger.info (message)則不會打印信息,因為 debug()函數只有當logger的級別為DEBUG時才打印信息,info()函數只有當logger的級別為INFO時才打印信息。
除了對應于每一個級別有一個打印函數外,在Logger類中還有一個log(),它可以讓你通過參數來指定一個打印信息的打印級別。

引入級別后就可通過修改調試的級別來控制某個調試信息是否輸出。假設我們有的信息是在開發時才需要輸出的(稱為測試信息),那么我們把輸出測試信息的 Logger的級別在開發時設為DEBUG級別的,并用debug(Object message)函數來進行打印。當要發布系統時,只需把相應的 Logger的級別調高就可以屏蔽掉測試信息。

二 動態配置log4j
1 配置外部配置文件來配置的基本步驟
1.1 一個運用配置文件的實例
Log4j之所以能成功的原因之一是它的靈活性。但如果只是簡單的調用BasicConfigurator.configure()來進行配置工作,
那么所有的配置都是在函數中寫死的,以后修改配置就要修改原代碼,這就不能體現出log4j的靈活性了,
所以基本上不會通過BasicConfigurator.configure()來進行配置工作的。
為了增加軟件的靈活性,最常用的做法就是使用配置文件,如web.xml之于J2EE,struts-config.xml之于struts一樣,
log4j也提供了讓我們把配置信息從程序轉移到配置文件中的方法。Log4j提供了兩種方式的配置文件:
XML文件和Java的property配置文件。通過把配置信息轉移到外部文件中,當我們要修改配置信息時,
就可以直接修改配置文件而不用去修改代碼了,下面,我們就來完成一個通過配置文件來實現log4j
的實例。
例2-a:

java 代碼
  1. package TestLog4j;   
  2. import org.apache.log4j.Logger;   
  3. import org.apache.log4j.BasicConfigurator;   
  4. import org.apache.log4j.PropertyConfigurator;   
  5. import org.apache.log4j.Priority;    
  6. public class TestLog4j    
  7. {   
  8. static Logger logger = Logger.getLogger(TestLog4j.class.getName());   
  9. public TestLog4j(){}   
  10.   
  11. public static void main(String[] args)   
  12. {   
  13. //通過BasicConfigurator類來初始化   
  14. //BasicConfigurator.configure();   
  15. //(1)通過配置文件來初始化   
  16. PropertyConfigurator.configure("F:\\nepalon\\log4j.properties");   
  17.   
  18. logger.debug("Start of the main() in TestLog4j"); //代碼(2)   
  19. logger.info("Just testing a log message with priority set to INFO");   
  20. logger.warn("Just testing a log message with priority set to WARN");   
  21. logger.error("Just testing a log message with priority set to ERROR");   
  22. logger.fatal("Just testing a log message with priority set to FATAL");   
  23. logger.log(Priority.WARN, "Testing a log message use a alternate form");   
  24. logger.debug(TestLog4j.class.getName()); //代碼(2)   
  25. }   
  26. }   


在這個例子中,我們用PropertyConfigurator.configure("F:\\nepalon \\log4j.properties")代替BasicConfigurator.configure()進行配置。 PropertyConfigurator.configure()函數的參數可以是一個properties文件所在路徑的String對象,可以是一個properties文件所在路徑的URL對象,也可以是一個properties對象。通過 PropertyConfigurator.configure()可以通過指定的properties文件來配置信息。如果要用XML文件進行信息配置,可以在代碼中調用DOMConfigurator()函數來進行配置工作。在這里,我們只以properties文件來完成例子。接著,我們來看一下 log4j.properties文件中都有些什么東西:
例2-b:
log4j.rootLogger = DEBUG, A1
log4j.appender.A1 = org.apache.log4j.ConsoleAppender
log4j.appender.A1.layout = org.apache.log4j.PatternLayout
log4j.appender.A1.layout.ConversionPattern = %-4r [%t] %-5p %c %x - %m%n
運行這個實例,運行結果為
0 [main] DEBUG TestLog4j.TestLog4j - Start of the main() in TestLog4j
20 [main] INFO TestLog4j.TestLog4j - Just testing a log message with priority set to INFO
20 [main] WARN TestLog4j.TestLog4j - Just testing a log message with priority set to WARN
20 [main] ERROR TestLog4j.TestLog4j - Just testing a log message with priority set to ERROR
20 [main] FATAL TestLog4j.TestLog4j - Just testing a log message with priority set to FATAL
180 [main] WARN TestLog4j.TestLog4j - Testing a log message use a alternate form
180 [main] DEBUG TestLog4j.TestLog4j - TestLog4j.TestLog4j
下面,我們分析一下這個配置文件。
1) 由于每一個Logger對旬都有一個級別,文件的第一行就是定義了一個Logger及其級別。在這里定義了一個根記錄器(root logger),
這涉及到記錄器的層次問題,在些暫時不深入討論,在后面的章節再進行討論。
2) 第二行定義了一個名為A1的輸出流,這個流就是控制臺,所以通過Logger對象打印的信息會在控制臺輸出。
3) 第三行定義了打印信息的布局。在這里我們用PatternLayout作為此記錄器的布局,PatternLayout允許你以靈活的格式來打印信息。
4) 第四行指定的打印信息的具體格式,從結果可知,這個實例的打印格式為:當前打印語句所使用的時間 [日志所在的線程] 打印的級別
當前日志所在的類的全名 日志信息。

現在我們來修改一下這個記錄器的級別,把第一行的DEBUG改為INFO,再運行程序,結果將變為:
0 [main] INFO TestLog4j.TestLog4j - Just testing a log message with priority set to INFO
10 [main] WARN TestLog4j.TestLog4j - Just testing a log message with priority set to WARN
10 [main] ERROR TestLog4j.TestLog4j - Just testing a log message with priority set to ERROR
10 [main] FATAL TestLog4j.TestLog4j - Just testing a log message with priority set to FATAL
10 [main] WARN TestLog4j.TestLog4j - Testing a log message use a alternate form
由于這個Logger的級別變為INFO,而代碼(2)是調用debug()函數來輸出日志信息時只能當記錄器級別為DEBUG時才輸出信息,
所以代碼(2)將不輸出信息。

1.2 實例原理

1.2.1 初始化配置信息
如果要通過JAVA的properties文件來配置信息,那么在代碼中就要通過PropertyConfigurator.configure()
函數從properties文件中加載配置信息,這個函數有三種參數形式:
一個properties文件所在路徑的String對象,可以是一個properties文件所在路徑的URL對象,
也可以是一個properties對象。如果要用XML文件來配置信息,則可用類型的
DOMConfigurator()函數來從一個XML文件中加載配置信息。

1.2.2 輸出端Appender
在上面的例子中,我們都是簡單的把日志信息輸出到控制臺中。其實在log4j中還可以把日志信息輸出到其它的輸出端,
對于同一個日志信息,我們還可以讓它同時輸出到多個輸出端中,如同時在控制臺和文件中進行打印。
一個輸出端就是一個appender。要在配置文件中定義一個appender有三步:

1) 在定義一個記錄器的同時定義出該記錄器的輸出端appender。在例2的配置文件的第一句log4j.rootLogger = DEBUG, A1中,
我們定義了一個根記錄器,它的級別為DEBUG,它有一個appender名為A1。定義根記錄器的格式為log4j.rootLogger = [ level ],
appendName1, appendName2, …appendNameN。同一個記錄器可有多個輸出端。

2) 定義appender的輸出目的地。定義一個appender的輸出目的地的格式為
log4j.appender.appenderName = fully.qualified.name.of.appender.class。log4j提供了以下幾種常用的輸出目的地:
? org.apache.log4j.ConsoleAppender,將日志信息輸出到控制臺                                                         
? org.apache.log4j.FileAppender,將日志信息輸出到一個文件
? org.apache.log4j.DailyRollingFileAppender,將日志信息輸出到一個,并且每天輸出到一個新的日志文件
? org.apache.log4j.RollingFileAppender,將日志信息輸出到一個文件,通過指定文件的的尺寸,
當文件大小到達指定尺寸的時候會自動把文件改名,如名為example.log的文件會改名為example.log.1,
同時產生一個新的example.log文件。如果新的文件再次達到指定尺寸,又會自動把文件改名為example.log.2,
同時產生一個example.log文件。依此類推,直到example.log. MaxBackupIndex,MaxBackupIndex的值可在配置文件中定義。

? org.apache.log4j.WriterAppender,將日志信息以流格式發送到任意指定的地方。
? org.apache.log4j.jdbc.JDBCAppender,通過JDBC把日志信息輸出到數據庫中。
在例2中,log4j.appender.A1 = org.apache.log4j.ConsoleAppender定義了名為A1的appender的輸出目的地為控制臺,
所以日志信息將輸出到控制臺。


3) 定義與所選的輸出目的地相關的參數,定義格式為:
log4j.appender.appenderName.optionName1 = value1
……
log4j.appender.appenderName.optionNameN = valueN
其中一個最常用的參數layout將在下面介紹。

1.2.3 輸出格式(布局)layout
通過appender可以控制輸出的目的地,而如果要控制輸出的格式,就可通過log4j的layout組件來實現。
通過配置文件定義一個appender的輸出格式,也通常需要兩個步驟:

1) 定義appender的布局模式。定義一個appender的布局模式的格式為
log4j.appender.appenderName.layout = fully.qualified.name.of.layout.class。Log4j提供的布局模式有以下幾種:
? org.apache.log4j.HTMLLayout,以HTML表格形式布局
? org.apache.log4j.PatternLayout,可以靈活地指定布局模式
? org.apache.log4j.SimpleLayout,包含日志信息的級別和信息字符串
在例2 中log4j.appender.A1.layout = org.apache.log4j.PatternLayout定義了名為A1的appender的布局模式為PatternLayout。

2) 定義與所選的布局模式相關的設置信息,定義格式為:
log4j.appender.appenderName.layout.optionName1 = value1
……
log4j.appender.appenderName.layout.optionNameN = valueN
選擇了不同的布局模式可能會有不同的設置信息。實例2所選的布局模式PatternLayout的一個PatternLayout為ConversionPattern ,
通過定義這個PatternLayout的值,我們可以指定輸出信息的輸出格式。
在例2的配置文件中的定義如下log4j.appender.A1.layout.ConversionPattern = %-4r [%t] %-5p %c %x - %m%n。在下面,
我們將介紹布局模式PatternLayout的參數ConversionPattern的各個值代表的含義。

1.2.4 ConversionPattern參數的格式含義
格式名 含義
%c 輸出日志信息所屬的類的全名
%d 輸出日志時間點的日期或時間,默認格式為ISO8601,也可以在其后指定格式,比如:%d{yyy-MM-dd HH:mm:ss },
輸出類似:2002-10-18- 22:10:28
%f 輸出日志信息所屬的類的類名
%l 輸出日志事件的發生位置,即輸出日志信息的語句處于它所在的類的第幾行
%m 輸出代碼中指定的信息,如log(message)中的message
%n 輸出一個回車換行符,Windows平臺為“\r\n”,Unix平臺為“\n”
%p 輸出優先級,即DEBUG,INFO,WARN,ERROR,FATAL。如果是調用debug()輸出的,則為DEBUG,依此類推
%r 輸出自應用啟動到輸出該日志信息所耗費的毫秒數
%t 輸出產生該日志事件的線程名

1.3 定義多個輸出目的地的實例
從上面的實例原理中我們已經知道,同一個日志信息可以同時輸出到多個輸出目的地,在這個例子中,
我們將實現一個把日志信息同時輸出到控制器、一個文件中的實例和數據庫中。
這個實例的Java代碼我們沿用例2中的代碼,我們只需修改配置文件即可。這也體現了log4j的靈活性。
例3-a:

sql 代碼
  1. create table log4j(   
  2. logID int primary key identity,   
  3. message varchar(1024),   
  4. priority varchar(10),   
  5. milliseconds int,   
  6. category varchar(256),   
  7. thread varchar(100),   
  8. NDC varchar(256),   
  9. createDate datetime,   
  10. location varchar(256),   
  11. caller varchar(100),   
  12. method varchar(100),   
  13. filename varchar(100),   
  14. line int  
  15. )  


例3-b:
#1 定義了兩個輸出端
log4j.rootLogger = INFO, A1, A2,A3

#2 定義A1輸出到控制器
log4j.appender.A1 = org.apache.log4j.ConsoleAppender
#3 定義A1的布局模式為PatternLayout
log4j.appender.A1.layout = org.apache.log4j.PatternLayout
#4 定義A1的輸出格式
log4j.appender.A1.layout.ConversionPattern = %-4r [%t] %-5p %c - %m%n

#5 定義A2輸出到文件
log4j.appender.A2 = org.apache.log4j.RollingFileAppender
#6 定義A2要輸出到哪一個文件
log4j.appender.A2.File = F:\\nepalon\\classes\\example3.log
#7 定義A2的輸出文件的最大長度
log4j.appender.A2.MaxFileSize = 1KB
#8 定義A2的備份文件數
log4j.appender.A2.MaxBackupIndex = 3
#9 定義A2的布局模式為PatternLayout
log4j.appender.A2.layout = org.apache.log4j.PatternLayout
#10 定義A2的輸出格式
log4j.appender.A2.layout.ConversionPattern = %d{yyyy-MM-dd hh:mm:ss}:%p %t %c - %m%n

#11區 定義A3輸出到數據庫
log4j.appender.A3 = org.apache.log4j.jdbc.JDBCAppender
log4j.appender.A3.BufferSize = 40
log4j.appender.A3.Driver = com.microsoft.jdbc.sqlserver.SQLServerDriver
log4j.appender.A3.URL = jdbc:microsoft:sqlserver://127.0.0.1:1433;DatabaseName=nepalon
log4j.appender.A3.User = sa
log4j.appender.A3.Password =
log4j.appender.A3.layout = org.apache.log4j.PatternLayout
log4j.appender.A3.layout.ConversionPattern = INSERT INTO log4j
(createDate, thread, priority, category, message) values(getdate(), '%t', '%-5p', '%c', '%m')
配置文件中的6、7、8行顯示了輸出端為RollingFileAppender的特有參數及其運用的方法。

11區顯示了輸出端為JDBCAppender的特有參數及其運用方法。在這著重講解一下6、7、8行的作用。
6行指定日志信息輸出到哪個文件,7行指定日志文件的最大長度,最后要詳細介紹8行。第8行的參數是設置備份文件的個數的參數,
在這里我們設置為3,表示最多有3個備份文件,具體作用為:
1) 當example3.log文件的大小超過K時,就把文件改名為example3.log.1,同時生成一個新的example3.log文件
2) 當example3.log文件的大小再次超過1K,又把文件改名為example3.log.1。但由于此時example3.log.1已存在,
則先把example3.log.1更名為example3.log.2,再把example3.log文件改名為example3.log.1
3) 同理,當example3.log文件的大小再次超過1K,先把example3.log.2文件更名為example3.log.3,
把example3.log.1文件更名為example3.log.2,再把example3.log文件改名為example3.log.1
4) 當example3.log文件的大小再次超過1K,先把example3.log.2文件更名為example3.log.3,舊的example3.log.3文件將被覆蓋;
把example3.log.1文件更名為example3.log.2,舊的example3.log.2文件被覆蓋;
最后把example3.log文件改名為example3.log.1并覆蓋掉舊的example3.log.1文件。

運行結果將分為兩部分
在控制器中:
0 [main] INFO TestLog4j.TestLog4j - Just testing a log message with priority set to INFO
11 [main] WARN TestLog4j.TestLog4j - Just testing a log message with priority set to WARN
21 [main] ERROR TestLog4j.TestLog4j - Just testing a log message with priority set to ERROR 21
[main] FATAL TestLog4j.TestLog4j - Just testing a log message with priority set to FATAL
21 [main] WARN TestLog4j.TestLog4j - Testing a log message use a alternate form
在文件example3.log中:
2003-12-18 04:23:02:INFO main TestLog4j.TestLog4j - Just testing a log message with priority set to INFO
2003-12-18 04:23:02:WARN main TestLog4j.TestLog4j - Just testing a log message with priority set to WARN
2003-12-18 04:23:02:ERROR main TestLog4j.TestLog4j - Just testing a log message with priority set to ERROR
2003-12-18 04:23:02:FATAL main TestLog4j.TestLog4j - Just testing a log message with priority set to FATAL
2003-12-18 04:23:02:WARN main TestLog4j.TestLog4j - Testing a log message use a alternate form

1.4 配置log4j的總結
這個教程到這里,關于配置log4j的配置文件的基本原理已經講完了,而且通過例3我們已經可以完成基本的日志工作了。
現在,我們就做一個總結。配置一個配置文件的基本步驟如下:

#===============================================================================================
1) 定義一個Logger。在定義Logger時指定該Logger的級別級其輸出目的地。定義Logger的格式為
log4j.rootLogger = [ level ], appendName1, appendName2, …appendNameN。
2) 定義appender的輸出目的地。定義一個appender的輸出目的地的格式為
log4j.appender.appenderName = fully.qualified.name.of.appender.class。
log4j提供的輸出端有ConsoleAppender、FileAppender 、DailyRollingFileAppender、RollingFileAppender和WriterAppender。
3) 定義appender的除布局模式外的其它相關參數,如例3中第6、7、8定義了A2的相關參數。定義格式為
log4j.appender.appenderName.optionName1 = value1
……
log4j.appender.appenderName.optionNameN = valueN
如果除了布局模式外不需要定義別的參數,可跳過這一步(如例3中的A1)。
4) 定義appender的布局模式。定義一個appender的布局模式的格式為
log4j.appender.appenderName.layout = fully.qualified.name.of.layout.class。
布局模式其實也是步驟3)中的一個部分,只是布局模式參數是每一個appender必須定義的參數。Log4j提供的布局模式有HTMLLayout、
PatternLayout和SimpleLayout。
5) 定義與所選的布局模式相關的設置信息,定義格式為
og4j.appender.appenderName.layout.optionName1 = value1
……
log4j.appender.appenderName.layout.optionNameN = valueN

2 記錄器的層次Logger hierarchy
2.1 何為記錄器的層次hierarchy
首先,我們先看一下何為層次,以我們最熟悉的繼承為例,下面是一張類圖

在這個繼承體系中,類B是類C的父類,類A是類C的祖先類,類D是類C的子類。這些類之間就構成一種層次關系。
在這些具有層次關系的類中,子類都可繼承它的父類的特征,如類B的對象能調用類A中的非private實例變量和函數;
而類C由于繼承自類B,所以類B的對象可以同時調用類A和類B中的非private實例變量和函數。
在log4j中,處于不同層次中的Logger也具有象類這樣的繼承關系。

2.2 記錄器的層次
如果一個應用中包含了上千個類,那么也幾乎需要上千個Logger實例。如何對這上千個Logger實例進行方便地配置,就是一個很重要的問題。
Log4J采用了一種樹狀的繼承層次巧妙地解決了這個問題。在Log4J中Logger是具有層次關系的。
它有一個共同的根,位于最上層,其它Logger遵循類似包的層次。下面我們將進行介紹。
2.2.1 根記錄器root logger
就象一個Java中的Object類一樣,log4j中的logger層次中有一個稱之為根記錄器的記錄器,
其它所有的記錄器都繼承自這個根記錄器。根記錄器有兩個特征:
1) 根記錄器總是存在。就像Java中的Object類一樣,因為用log4j輸出日志信息是通過記錄器來實現的,所以只要你應用了log4j,
根記錄器就肯定存在的。
2) 根記錄器沒有名稱,所以不能通過名稱來取得根記錄器。但在Logger類中提供了getRootLogger()的方法來取得根記錄器。
2.2.2 記錄器的層次
Logger遵循類似包的層次。如
static Logger rootLog = Logger.getRootLogger();
static Logger log1 = Logger.getLogger("test4j");
static Logger log2 = Logger.getLogger("test4j.test4j2");
static Logger log3 = Logger.getLogger("test4j.test4j2.test4j2");
那么rootLog是log2的祖先子記錄器,log1是log2的父子記錄器,log3是log2的子記錄器。記錄器象Java中的類繼承一樣,
子記錄器可以繼承父記錄器的設置信息,也可以可以覆寫相應的信息。
首先先看一下記錄器層次中的繼承有什么用處。假設程序中的每個包都具有一些基本的日志信息,
而包中的不同包可能會有些額外的日志信息要輸出,這種情況就可以象處理Java中的類一樣,運用Logger中的層次關系來達到目的。
假設有個名為A的包,我包下的所有類都要把日志信息輸出到控制臺中;A.B包除了輸出到控制臺外還要輸出到文件中;
A.C包除了輸出到控制臺中還要輸出到HTML文檔中。這樣我們就可以通過定義一個父記錄器A,它負責把日志信息輸出到控制臺中;
定義一個A的子記錄器A.B,它負責把日志信息輸出到文件中;定義一個A的子記錄器A.C,它負責把日志信息輸出到HTML文檔中。
記錄器遵循的是類似包的層次,這樣做為我們帶來了大大的方便。Logger類中的getLogger()方法可以取得Logger對象,
這個方法有三種參數形式String、Class和URL,其實不論是用哪一種,最終都是通過記錄器的名字來取得記錄器對象的。
如果要取得一個名為A.B的記錄器對象,我們可以Logger.getLogger(“A.B”)。但從上面的例子中,
我們都是通過Logger.getLogger(TestLog4j.class.getName())這種方法來取得記錄器對象。這是為什么呢?
現在我們假設A.B的包下有一個類BClass,那么我們調用BClass.class.getName()得到的是這個類的全名A.B.BClass。
所以當調用Logger.getLogger(BClass.class.getName())時,最理想的情況是返回名為A.B.BClass的記錄器對象。
但是如果不存在名為A.B.BClass的記錄器時它會怎樣呢?其實通過Logger類的getLogger方法取得記錄器時存在下面兩種情況:
1) 如果存在與所要找的名字完全相同的記錄器,則返回相應的記錄器對象。
當調用Logger.getLogger(BClass.class.getName())時,如果定義了名為A.B.BClass的記錄器,它就返回該記錄器的對象。
2) 但如果找不到,它會嘗試返回在記錄器層次中與所要找的記錄器最接近的記錄器對象。
當調用Logger.getLogger(BClass.class.getName())時,如果沒有定義了名為A.B.BClass的記錄器,
那會嘗試返回名為A.B的記錄器的對象;如果又沒有定義名為A.B的記錄器,它會嘗試返回名為A的記錄器的對象;
如果也沒定義名為A的記錄器,它就會返回根記錄器的對象,而根記錄器是必須存在的,所以你總能得到一個記錄器對象。
好了,現在我們回到前面的問題,我們為什么總要通過Logger.getLogger(BClass.class.getName())
這種以類全名作為參數來取得記錄器對象呢?其實這是為了管理方便。因為我們在定義設計Logger時也遵循類似包的規則,
使設計器的名稱與程序中的類包對應。如上面的假設中我們的程序中有A包,A包下有B包和C包,
B包下又有類BClass,那么我們就可使設計器的名為A、A.B、A.C、A.B.BClass,以此類推。那么當我們通過類命名來取得設計器對象時,
總能取到與所要的設計器最接近的設計器對象。

2.3 如何應用記錄器的層次
2.3.1 如果定義及獲取不同層次的記錄器
任何一個記錄器的使用都有兩個步驟:
1) 在配置文件中定義相應的記錄器。
在配置文件中定義記錄器的格式有兩種
? 定義根記錄器的格式為
log4j.rootLogger = [ level ], appendName1, appendName2, …appendNameN
? 定義一個非根記錄器的格式為
log4j.logger.loggerName1 = [ level ], appendName1,…appendNameN
……
log4j.logger.loggerNameM = [ level ], appendName1, …appendNameN
我們可以定義任意個非根記錄器。
2) 在代碼中調用Logger類的取得記錄器方法取得相應的記錄器對象。
要取得根記錄器對象可通過Logger.getRootLogger()函數,要取得非根記錄器可通過Logger.getLogger()函數。
理論知道就講到這里,紙上得來終覺淺,下面,我們來小小演練一下。
例4-a:

java 代碼
  1. package TestLog4j;   
  2. import org.apache.log4j.Logger;   
  3. import org.apache.log4j.PropertyConfigurator;   
  4. import org.apache.log4j.Priority;   
  5. import TestLog4j.TestLog4j2.TestLog4j2;   
  6.   
  7. public class TestLog4j    
  8. {   
  9. static Logger logger = Logger.getLogger(TestLog4j.class.getName()); //(2)   
  10. public TestLog4j(){}   
  11.   
  12. public static void main(String[] args)   
  13. {   
  14. //同時輸出到控制臺和一個文件的實例并實現了Logger的繼承   
  15. PropertyConfigurator.configure("F:\\nepalon\\log4j2.properties");   
  16.   
  17. logger.debug("Start of the main() in TestLog4j");   
  18. logger.info("Just testing a log message with priority set to INFO");   
  19. logger.warn("Just testing a log message with priority set to WARN");   
  20. logger.error("Just testing a log message with priority set to ERROR");   
  21. logger.fatal("Just testing a log message with priority set to FATAL");   
  22. logger.log(Priority.WARN, "Testing a log message use a alternate form");   
  23. logger.debug(TestLog4j.class.getName());   
  24. TestLog4j2 testLog4j2 = new TestLog4j2(); //(1)   
  25. testLog4j2.testLog();   
  26. }   
  27. }   


在類TestLog4j中我們調用了另一個類TestLog4j2,下面看一下類TestLog4j2的代碼。
例4-b:

java 代碼
  1. package TestLog4j.TestLog4j2;   
  2. import org.apache.log4j.Logger;   
  3. import org.apache.log4j.PropertyConfigurator;   
  4. import org.apache.log4j.Priority;   
  5.   
  6. public class TestLog4j2    
  7. {   
  8. static Logger logger = Logger.getLogger(TestLog4j2.class.getName()); //(1)   
  9. public TestLog4j2(){}   
  10.   
  11. public void testLog()   
  12. {   
  13. //同時輸出到控制臺和一個文件的實例   
  14. PropertyConfigurator.configure("F:\\nepalon\\log4j2.properties");   
  15. logger.debug("2Start of the main()");   
  16. logger.info("2Just testing a log message with priority set to INFO");   
  17. logger.warn("2Just testing a log message with priority set to WARN");   
  18. logger.error("2Just testing a log message with priority set to ERROR");   
  19. logger.fatal("2Just testing a log message with priority set to FATAL");   
  20. logger.log(Priority.DEBUG, "Testing a log message use a alternate form");   
  21. logger.debug("2End of the main()");   
  22. }   
  23. }   


最后我們來看一下配置文件。
例4-c:
log4j2.properties文件內容
#1區
#### Use two appenders, one to log to console, another to log to a file
log4j.rootLogger = debug, stdout

#2區
#Print only messages of priority WARN or higher for your category
log4j.logger.TestLog4j= , R
log4j.logger.TestLog4j.TestLog4j2=WARN

#3區
#### First appender writes to console
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

#4區
#### Second appender writes to a file
log4j.appender.R=org.apache.log4j.RollingFileAppender
log4j.appender.R.File=F:\\nepalon\\classes\\TestLog4j\\example.log

# Control the maximum log file size
log4j.appender.R.MaxFileSize=100KB
# Archive log files (one backup file here)
log4j.appender.R.MaxBackupIndex=1

log4j.appender.R.layout=org.apache.log4j.PatternLayout
log4j.appender.R.layout.ConversionPattern=%d{yyyy-MM-dd hh:mm:ss}:%p %t %c - %m%n
先看一下運行結果。
在控制臺中的結果為:
DEBUG [main] (?:?) - Start of the main() in TestLog4j
INFO [main] (?:?) - Just testing a log message with priority set to INFO
WARN [main] (?:?) - Just testing a log message with priority set to WARN
ERROR [main] (?:?) - Just testing a log message with priority set to ERROR
FATAL [main] (?:?) - Just testing a log message with priority set to FATAL
WARN [main] (?:?) - Testing a log message use a alternate form
DEBUG [main] (?:?) - TestLog4j.TestLog4j

WARN [main] (?:?) - 2Just testing a log message with priority set to WARN
ERROR [main] (?:?) - 2Just testing a log message with priority set to ERROR
FATAL [main] (?:?) - 2Just testing a log message with priority set to FATAL
輸出文件的結果為:
2003-12-19 04:19:44:DEBUG main TestLog4j.TestLog4j - Start of the main() in TestLog4j
2003-12-19 04:19:44:INFO main TestLog4j.TestLog4j - Just testing a log message with priority set to INFO
2003-12-19 04:19:44:WARN main TestLog4j.TestLog4j - Just testing a log message with priority set to WARN
2003-12-19 04:19:44:ERROR main TestLog4j.TestLog4j - Just testing a log message with priority set to ERROR
2003-12-19 04:19:44:FATAL main TestLog4j.TestLog4j - Just testing a log message with priority set to FATAL
2003-12-19 04:19:44:WARN main TestLog4j.TestLog4j - Testing a log message use a alternate form
2003-12-19 04:19:44:DEBUG main TestLog4j.TestLog4j - TestLog4j.TestLog4j

2003-12-19 04:19:44:WARN main TestLog4j.TestLog4j2.TestLog4j2 - 2Just testing a log message with priority set to WARN
2003-12-19 04:19:44:ERROR main TestLog4j.TestLog4j2.TestLog4j2 - 2Just testing a log message with priority set to ERROR
2003-12-19 04:19:44:FATAL main TestLog4j.TestLog4j2.TestLog4j2 - 2Just testing a log message with priority set to FATAL

首先,先來看一下配置文件都有些什么東西。
1) 在1區中定義了一個根記錄器。這個根記錄器具有DEBUG級別并有一個名稱為stdout的輸出端appender。
2) 2區中的內容是這一節的重點,也是應用到記錄器層次的地方,但其實也只有兩句,充分體現了log4j的簡單性。在這里,
我們定義了兩個名稱分別為TestLog4j和TestLog4j.TestLog4j2設計器。
? 在定義TestLog4j記錄器時沒有指定級別,所以它的級別繼承自它的父記錄器,即要記錄器,所以它的級別也為DEBUG。
在定義TestLog4j記錄器時又定義了一個名稱為R的輸出端,所以它的輸出端有兩個,一個從根記錄器繼承而來的名為stdout的輸出端,
另一個為在此定義的名為R的輸出端。在此需要注意的是,在定義記錄器時必須先定義記錄器的級別,然后才是記錄器的輸出端。
如果只想定義輸出端而不定義級別,則雖然級別可以為空,但逗號分隔符不能省略。如定義TestLog4j記錄器的做法。
? 在定義TestLog4j.TestLog4j2記錄器時又指定了它的級別,由于一個記錄器的級別只能有一個,
所以新指定的級別將覆寫掉它的父記錄器的級別(這就象Java中的多態)。我們沒有定義TestLog4j.TestLog4j2記錄器的輸出端,
所以它的輸出端將從它的父記錄器中繼承而來。它的父記錄器為estLog4j記錄器,所以它和estLog4j記錄器一樣具有兩個名稱分別為
stdout和R的輸出端。
3) 剩下的3區和4區分別設置了兩個輸出端的參數值。
接下來,回到我們的代碼,看一下是如何取得記錄器,在取記錄器時又發生了什么。
1) 例4-a中的代碼(2)中,語句Logger.getLogger()中的參數TestLog4j.class.getName()的值為TestLog4j. TestLog4j,
所以此語句的結果是取得一個名為TestLog4j. TestLog4j的記錄器的對象。但在配置文件中并沒有定義這樣的記錄器,
所以最終將返回與所需的名稱TestLog4j. TestLog4j最接近的記錄器對象,即名為TestLog4j的記錄器的對象。
2) 例4-b中的代碼(1)的原理與例4-a中的代碼(2)相似,期望取得的是名為TestLog4j.TestLog4j2. TestLog4j2的記錄器對象,
但最終返回的是TestLog4j.TestLog4j2記錄器的對象。