一、日志背景
我們在編程時經(jīng)常不可避免地要使用到一些日志操作,比如開發(fā)階段的調(diào)試信息、運行時的日志記錄及審計。調(diào)查顯示,日志代碼占代碼總量的4%。通常大家可以簡單地使用System.out.println()語句輸出日志信息,但是在發(fā)布時,通常不想在正式的版本中打印這些開發(fā)時的調(diào)試信息,于是又要手工地把這些語句刪除,所以大量的這樣的System.out.println()調(diào)試語句會帶來麻煩。更多做法是把它封閉一個簡單的輸出,比如:

2

3

4

5

6

7

8

9

10

這樣做雖然在一定程度上解決了問題,但如果需要的是更復(fù)雜的日志系統(tǒng)呢,比如把日志信息保存為文件等別的形式;又或是當(dāng)系統(tǒng)在試運行了一段時間后我們又要更改某些試運行時的測試信息。如果真的遇到這樣的情況,也行就只有修改代碼了,這樣又給開發(fā)工作帶來了麻煩。
Log4J是Apache軟件基金會Jakarta項目下的一個子項目,是用Java編寫的優(yōu)秀日志工具包。通過Log4J可以在不修改代碼的情況下,方便、靈活地控制任意粒度的日志信息的開啟或關(guān)閉,然后使用定制的格式,把日志信息輸出到一個或多個需要的地方。并且,Log4J還有一條平滑的學(xué)習(xí)曲線,在三分鐘內(nèi)就可學(xué)會它的簡單使用。隨著使用深入,你會發(fā)現(xiàn)Log4J功能的強大,幾乎可以滿足日志方面的所有需要。
二、LOG4J
1 第一個log4j程序及其原理
讓我們從現(xiàn)在開始記時,看完成第一個log4j程序要不要3分鐘。首先log4j-1.2.7.jar考到你的類路徑下。然后創(chuàng)建一個類,代碼如下:
2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

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
好了,看一看你的表,應(yīng)該不到3分鐘吧。在這短短的3分鐘里,我們做了些什么呢?下面我們來分析一下代碼。
1) 首先代碼(1)先通過Logger類的getLogger()方法得到一個Logger類的對象。在getLogger()方法中,通常把所在的類的Class對象或是所在類的全名作為參數(shù)。運用log4j輸出日志要用到Logger對象。
2) 然后代碼(2)進行一些必要的初始化,如要把調(diào)試信息輸出到哪。當(dāng)用System.out.println()時可以很明確的知道要把信息輸出到標準輸出設(shè)備且只能輸出到那里。運用log4j,我們可以輸出到許多地方,如控制臺、文件、HTML文件等,至于要輸出到哪里,就要自己進行初始化。在代碼(2),我們調(diào)用自帶的初始化方法來完成初始化。用這個方法進行初始化就不能體現(xiàn)出log4j的靈活性,所以基本上不會這樣做。Log4j提供了用XML文件或 Java配置文件來配置設(shè)置的方法,在下面我們將進行介紹。
3) 接著代碼(3)就是輸出信息的代碼了。你可以看到代碼(3)中嘗試了用幾種不同的方法來輸出信息,對于這幾種信息的作用,我會在下面進行介紹,你現(xiàn)在只需把它當(dāng)成是輸出語句最后,我們來看一下運行結(jié)果(日志信息)的意義。第一個數(shù)字是指程序開始運行到運行該日志語句所經(jīng)歷的毫秒數(shù)(用來做一點運行效率分析也不錯),“[main]”是日志事件發(fā)生的線程,隨后的“DEBUG”、“INFO”等信息是相應(yīng)日志信息的優(yōu)先級別,“TestLog4j.TestLog4”是當(dāng)前TestLog4所在的包和名稱,最后是日志信息。就行。
2 實例原理
雖然完成了第一程序了,但程序中的內(nèi)容還是不太了解。好,現(xiàn)在我就對上面的例子用到的log4j的原理進行講解。在以后的章節(jié)中,我都會采取這種先實例,再根據(jù)實例來介紹所涉及的log4j原理的方法。
2.1 記錄器Logger
Logger類是在log4j1.2以后才有的,以前是用Category類來實現(xiàn)現(xiàn)在的Logger類的功能的。從API可知,Logger類是Category類的子類。Logger類的代碼如下:
- package org.apache.log4j;
- public class Logger {
- // 創(chuàng)建和取回方法:
- public static Logger getRootLogger();
- public static Logger getLogger(String name);
- public static Logger getLogger(Class class1);
- // 打印方法:
- 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);
- // 常用打印方法:
- public void log(Level l, Object message);
- }
在討論Logger類中的方法之前,我先講一下log4j中的級別(level)的概念。
2.1.1 級別Level
Log4j中的日志級別分為五種:DEBUG、INFO、WARN、ERROR和FATAL,這五種級別從左到右級別依次增加。
2.1.2 Logger中的打印函數(shù)與級別
對于每一個記錄器,我們都可對它賦于一定的級別,而打印函數(shù)打印的即是相應(yīng)級別的信息。當(dāng)對一個級別為A的Logger調(diào)用級別為B的打印方法時,只有當(dāng) B>=A時才會進行打印。例如,如果有一個級別為WARN的Logger對象logger,只有對它調(diào)用 logger.warn (message)、logger.error (message)和logger.fatal (message)這三個打印函數(shù)才會打印信息;而調(diào)用logger.debug (message)和logger.info (message)則不會打印信息,因為 debug()函數(shù)只有當(dāng)logger的級別為DEBUG時才打印信息,info()函數(shù)只有當(dāng)logger的級別為INFO時才打印信息。
除了對應(yīng)于每一個級別有一個打印函數(shù)外,在Logger類中還有一個log(),它可以讓你通過參數(shù)來指定一個打印信息的打印級別。
引入級別后就可通過修改調(diào)試的級別來控制某個調(diào)試信息是否輸出。假設(shè)我們有的信息是在開發(fā)時才需要輸出的(稱為測試信息),那么我們把輸出測試信息的 Logger的級別在開發(fā)時設(shè)為DEBUG級別的,并用debug(Object message)函數(shù)來進行打印。當(dāng)要發(fā)布系統(tǒng)時,只需把相應(yīng)的 Logger的級別調(diào)高就可以屏蔽掉測試信息。
二 動態(tài)配置log4j
1 配置外部配置文件來配置的基本步驟
1.1 一個運用配置文件的實例
Log4j之所以能成功的原因之一是它的靈活性。但如果只是簡單的調(diào)用BasicConfigurator.configure()來進行配置工作,
那么所有的配置都是在函數(shù)中寫死的,以后修改配置就要修改原代碼,這就不能體現(xiàn)出log4j的靈活性了,
所以基本上不會通過BasicConfigurator.configure()來進行配置工作的。
為了增加軟件的靈活性,最常用的做法就是使用配置文件,如web.xml之于J2EE,struts-config.xml之于struts一樣,
log4j也提供了讓我們把配置信息從程序轉(zhuǎn)移到配置文件中的方法。Log4j提供了兩種方式的配置文件:
XML文件和Java的property配置文件。通過把配置信息轉(zhuǎn)移到外部文件中,當(dāng)我們要修改配置信息時,
就可以直接修改配置文件而不用去修改代碼了,下面,我們就來完成一個通過配置文件來實現(xiàn)log4j
的實例。
例2-a:
在這個例子中,我們用PropertyConfigurator.configure("F:\\nepalon \\log4j.properties")代替BasicConfigurator.configure()進行配置。 PropertyConfigurator.configure()函數(shù)的參數(shù)可以是一個properties文件所在路徑的String對象,可以是一個properties文件所在路徑的URL對象,也可以是一個properties對象。通過 PropertyConfigurator.configure()可以通過指定的properties文件來配置信息。如果要用XML文件進行信息配置,可以在代碼中調(diào)用DOMConfigurator()函數(shù)來進行配置工作。在這里,我們只以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
運行這個實例,運行結(jié)果為
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),
這涉及到記錄器的層次問題,在些暫時不深入討論,在后面的章節(jié)再進行討論。
2) 第二行定義了一個名為A1的輸出流,這個流就是控制臺,所以通過Logger對象打印的信息會在控制臺輸出。
3) 第三行定義了打印信息的布局。在這里我們用PatternLayout作為此記錄器的布局,PatternLayout允許你以靈活的格式來打印信息。
4) 第四行指定的打印信息的具體格式,從結(jié)果可知,這個實例的打印格式為:當(dāng)前打印語句所使用的時間 [日志所在的線程] 打印的級別
當(dāng)前日志所在的類的全名 日志信息。
現(xiàn)在我們來修改一下這個記錄器的級別,把第一行的DEBUG改為INFO,再運行程序,結(jié)果將變?yōu)椋?br />
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的級別變?yōu)镮NFO,而代碼(2)是調(diào)用debug()函數(shù)來輸出日志信息時只能當(dāng)記錄器級別為DEBUG時才輸出信息,
所以代碼(2)將不輸出信息。
1.2 實例原理
1.2.1 初始化配置信息
如果要通過JAVA的properties文件來配置信息,那么在代碼中就要通過PropertyConfigurator.configure()
函數(shù)從properties文件中加載配置信息,這個函數(shù)有三種參數(shù)形式:
一個properties文件所在路徑的String對象,可以是一個properties文件所在路徑的URL對象,
也可以是一個properties對象。如果要用XML文件來配置信息,則可用類型的
DOMConfigurator()函數(shù)來從一個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,將日志信息輸出到一個文件,通過指定文件的的尺寸,
當(dāng)文件大小到達指定尺寸的時候會自動把文件改名,如名為example.log的文件會改名為example.log.1,
同時產(chǎn)生一個新的example.log文件。如果新的文件再次達到指定尺寸,又會自動把文件改名為example.log.2,
同時產(chǎn)生一個example.log文件。依此類推,直到example.log. MaxBackupIndex,MaxBackupIndex的值可在配置文件中定義。
? org.apache.log4j.WriterAppender,將日志信息以流格式發(fā)送到任意指定的地方。
? org.apache.log4j.jdbc.JDBCAppender,通過JDBC把日志信息輸出到數(shù)據(jù)庫中。
在例2中,log4j.appender.A1 = org.apache.log4j.ConsoleAppender定義了名為A1的appender的輸出目的地為控制臺,
所以日志信息將輸出到控制臺。
3) 定義與所選的輸出目的地相關(guān)的參數(shù),定義格式為:
log4j.appender.appenderName.optionName1 = value1
……
log4j.appender.appenderName.optionNameN = valueN
其中一個最常用的參數(shù)layout將在下面介紹。
1.2.3 輸出格式(布局)layout
通過appender可以控制輸出的目的地,而如果要控制輸出的格式,就可通過log4j的layout組件來實現(xiàn)。
通過配置文件定義一個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 中l(wèi)og4j.appender.A1.layout = org.apache.log4j.PatternLayout定義了名為A1的appender的布局模式為PatternLayout。
2) 定義與所選的布局模式相關(guān)的設(shè)置信息,定義格式為:
log4j.appender.appenderName.layout.optionName1 = value1
……
log4j.appender.appenderName.layout.optionNameN = valueN
選擇了不同的布局模式可能會有不同的設(shè)置信息。實例2所選的布局模式PatternLayout的一個PatternLayout為ConversionPattern ,
通過定義這個PatternLayout的值,我們可以指定輸出信息的輸出格式。
在例2的配置文件中的定義如下log4j.appender.A1.layout.ConversionPattern = %-4r [%t] %-5p %c %x - %m%n。在下面,
我們將介紹布局模式PatternLayout的參數(shù)ConversionPattern的各個值代表的含義。
1.2.4 ConversionPattern參數(shù)的格式含義
格式名 含義
%c 輸出日志信息所屬的類的全名
%d 輸出日志時間點的日期或時間,默認格式為ISO8601,也可以在其后指定格式,比如:%d{yyy-MM-dd HH:mm:ss },
輸出類似:2002-10-18- 22:10:28
%f 輸出日志信息所屬的類的類名
%l 輸出日志事件的發(fā)生位置,即輸出日志信息的語句處于它所在的類的第幾行
%m 輸出代碼中指定的信息,如log(message)中的message
%n 輸出一個回車換行符,Windows平臺為“\r\n”,Unix平臺為“\n”
%p 輸出優(yōu)先級,即DEBUG,INFO,WARN,ERROR,F(xiàn)ATAL。如果是調(diào)用debug()輸出的,則為DEBUG,依此類推
%r 輸出自應(yīng)用啟動到輸出該日志信息所耗費的毫秒數(shù)
%t 輸出產(chǎn)生該日志事件的線程名
1.3 定義多個輸出目的地的實例
從上面的實例原理中我們已經(jīng)知道,同一個日志信息可以同時輸出到多個輸出目的地,在這個例子中,
我們將實現(xiàn)一個把日志信息同時輸出到控制器、一個文件中的實例和數(shù)據(jù)庫中。
這個實例的Java代碼我們沿用例2中的代碼,我們只需修改配置文件即可。這也體現(xiàn)了log4j的靈活性。
例3-a:
例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的備份文件數(shù)
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區(qū) 定義A3輸出到數(shù)據(jù)庫
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的特有參數(shù)及其運用的方法。
11區(qū)顯示了輸出端為JDBCAppender的特有參數(shù)及其運用方法。在這著重講解一下6、7、8行的作用。
6行指定日志信息輸出到哪個文件,7行指定日志文件的最大長度,最后要詳細介紹8行。第8行的參數(shù)是設(shè)置備份文件的個數(shù)的參數(shù),
在這里我們設(shè)置為3,表示最多有3個備份文件,具體作用為:
1) 當(dāng)example3.log文件的大小超過K時,就把文件改名為example3.log.1,同時生成一個新的example3.log文件
2) 當(dāng)example3.log文件的大小再次超過1K,又把文件改名為example3.log.1。但由于此時example3.log.1已存在,
則先把example3.log.1更名為example3.log.2,再把example3.log文件改名為example3.log.1
3) 同理,當(dāng)example3.log文件的大小再次超過1K,先把example3.log.2文件更名為example3.log.3,
把example3.log.1文件更名為example3.log.2,再把example3.log文件改名為example3.log.1
4) 當(dāng)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文件。
運行結(jié)果將分為兩部分
在控制器中:
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的總結(jié)
這個教程到這里,關(guān)于配置log4j的配置文件的基本原理已經(jīng)講完了,而且通過例3我們已經(jīng)可以完成基本的日志工作了。
現(xiàn)在,我們就做一個總結(jié)。配置一個配置文件的基本步驟如下:
#===============================================================================================
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的除布局模式外的其它相關(guān)參數(shù),如例3中第6、7、8定義了A2的相關(guān)參數(shù)。定義格式為
log4j.appender.appenderName.optionName1 = value1
……
log4j.appender.appenderName.optionNameN = valueN
如果除了布局模式外不需要定義別的參數(shù),可跳過這一步(如例3中的A1)。
4) 定義appender的布局模式。定義一個appender的布局模式的格式為
log4j.appender.appenderName.layout = fully.qualified.name.of.layout.class。
布局模式其實也是步驟3)中的一個部分,只是布局模式參數(shù)是每一個appender必須定義的參數(shù)。Log4j提供的布局模式有HTMLLayout、
PatternLayout和SimpleLayout。
5) 定義與所選的布局模式相關(guān)的設(shè)置信息,定義格式為
og4j.appender.appenderName.layout.optionName1 = value1
……
log4j.appender.appenderName.layout.optionNameN = valueN
2 記錄器的層次Logger hierarchy
2.1 何為記錄器的層次hierarchy
首先,我們先看一下何為層次,以我們最熟悉的繼承為例,下面是一張類圖
在這個繼承體系中,類B是類C的父類,類A是類C的祖先類,類D是類C的子類。這些類之間就構(gòu)成一種層次關(guān)系。
在這些具有層次關(guān)系的類中,子類都可繼承它的父類的特征,如類B的對象能調(diào)用類A中的非private實例變量和函數(shù);
而類C由于繼承自類B,所以類B的對象可以同時調(diào)用類A和類B中的非private實例變量和函數(shù)。
在log4j中,處于不同層次中的Logger也具有象類這樣的繼承關(guān)系。
2.2 記錄器的層次
如果一個應(yīng)用中包含了上千個類,那么也幾乎需要上千個Logger實例。如何對這上千個Logger實例進行方便地配置,就是一個很重要的問題。
Log4J采用了一種樹狀的繼承層次巧妙地解決了這個問題。在Log4J中Logger是具有層次關(guān)系的。
它有一個共同的根,位于最上層,其它Logger遵循類似包的層次。下面我們將進行介紹。
2.2.1 根記錄器root logger
就象一個Java中的Object類一樣,log4j中的logger層次中有一個稱之為根記錄器的記錄器,
其它所有的記錄器都繼承自這個根記錄器。根記錄器有兩個特征:
1) 根記錄器總是存在。就像Java中的Object類一樣,因為用log4j輸出日志信息是通過記錄器來實現(xiàn)的,所以只要你應(yīng)用了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中的類繼承一樣,
子記錄器可以繼承父記錄器的設(shè)置信息,也可以可以覆寫相應(yīng)的信息。
首先先看一下記錄器層次中的繼承有什么用處。假設(shè)程序中的每個包都具有一些基本的日志信息,
而包中的不同包可能會有些額外的日志信息要輸出,這種情況就可以象處理Java中的類一樣,運用Logger中的層次關(guān)系來達到目的。
假設(shè)有個名為A的包,我包下的所有類都要把日志信息輸出到控制臺中;A.B包除了輸出到控制臺外還要輸出到文件中;
A.C包除了輸出到控制臺中還要輸出到HTML文檔中。這樣我們就可以通過定義一個父記錄器A,它負責(zé)把日志信息輸出到控制臺中;
定義一個A的子記錄器A.B,它負責(zé)把日志信息輸出到文件中;定義一個A的子記錄器A.C,它負責(zé)把日志信息輸出到HTML文檔中。
記錄器遵循的是類似包的層次,這樣做為我們帶來了大大的方便。Logger類中的getLogger()方法可以取得Logger對象,
這個方法有三種參數(shù)形式String、Class和URL,其實不論是用哪一種,最終都是通過記錄器的名字來取得記錄器對象的。
如果要取得一個名為A.B的記錄器對象,我們可以Logger.getLogger(“A.B”)。但從上面的例子中,
我們都是通過Logger.getLogger(TestLog4j.class.getName())這種方法來取得記錄器對象。這是為什么呢?
現(xiàn)在我們假設(shè)A.B的包下有一個類BClass,那么我們調(diào)用BClass.class.getName()得到的是這個類的全名A.B.BClass。
所以當(dāng)調(diào)用Logger.getLogger(BClass.class.getName())時,最理想的情況是返回名為A.B.BClass的記錄器對象。
但是如果不存在名為A.B.BClass的記錄器時它會怎樣呢?其實通過Logger類的getLogger方法取得記錄器時存在下面兩種情況:
1) 如果存在與所要找的名字完全相同的記錄器,則返回相應(yīng)的記錄器對象。
當(dāng)調(diào)用Logger.getLogger(BClass.class.getName())時,如果定義了名為A.B.BClass的記錄器,它就返回該記錄器的對象。
2) 但如果找不到,它會嘗試返回在記錄器層次中與所要找的記錄器最接近的記錄器對象。
當(dāng)調(diào)用Logger.getLogger(BClass.class.getName())時,如果沒有定義了名為A.B.BClass的記錄器,
那會嘗試返回名為A.B的記錄器的對象;如果又沒有定義名為A.B的記錄器,它會嘗試返回名為A的記錄器的對象;
如果也沒定義名為A的記錄器,它就會返回根記錄器的對象,而根記錄器是必須存在的,所以你總能得到一個記錄器對象。
好了,現(xiàn)在我們回到前面的問題,我們?yōu)槭裁纯傄ㄟ^Logger.getLogger(BClass.class.getName())
這種以類全名作為參數(shù)來取得記錄器對象呢?其實這是為了管理方便。因為我們在定義設(shè)計Logger時也遵循類似包的規(guī)則,
使設(shè)計器的名稱與程序中的類包對應(yīng)。如上面的假設(shè)中我們的程序中有A包,A包下有B包和C包,
B包下又有類BClass,那么我們就可使設(shè)計器的名為A、A.B、A.C、A.B.BClass,以此類推。那么當(dāng)我們通過類命名來取得設(shè)計器對象時,
總能取到與所要的設(shè)計器最接近的設(shè)計器對象。
2.3 如何應(yīng)用記錄器的層次
2.3.1 如果定義及獲取不同層次的記錄器
任何一個記錄器的使用都有兩個步驟:
1) 在配置文件中定義相應(yīng)的記錄器。
在配置文件中定義記錄器的格式有兩種
? 定義根記錄器的格式為
log4j.rootLogger = [ level ], appendName1, appendName2, …appendNameN
? 定義一個非根記錄器的格式為
log4j.logger.loggerName1 = [ level ], appendName1,…appendNameN
……
log4j.logger.loggerNameM = [ level ], appendName1, …appendNameN
我們可以定義任意個非根記錄器。
2) 在代碼中調(diào)用Logger類的取得記錄器方法取得相應(yīng)的記錄器對象。
要取得根記錄器對象可通過Logger.getRootLogger()函數(shù),要取得非根記錄器可通過Logger.getLogger()函數(shù)。
理論知道就講到這里,紙上得來終覺淺,下面,我們來小小演練一下。
例4-a:
在類TestLog4j中我們調(diào)用了另一個類TestLog4j2,下面看一下類TestLog4j2的代碼。
例4-b:
最后我們來看一下配置文件。
例4-c:
log4j2.properties文件內(nèi)容
#1區(qū)
#### Use two appenders, one to log to console, another to log to a file
log4j.rootLogger = debug, stdout
#2區(qū)
#Print only messages of priority WARN or higher for your category
log4j.logger.TestLog4j= , R
log4j.logger.TestLog4j.TestLog4j2=WARN
#3區(qū)
#### 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區(qū)
#### 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
先看一下運行結(jié)果。
在控制臺中的結(jié)果為:
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
輸出文件的結(jié)果為:
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區(qū)中定義了一個根記錄器。這個根記錄器具有DEBUG級別并有一個名稱為stdout的輸出端appender。
2) 2區(qū)中的內(nèi)容是這一節(jié)的重點,也是應(yīng)用到記錄器層次的地方,但其實也只有兩句,充分體現(xiàn)了log4j的簡單性。在這里,
我們定義了兩個名稱分別為TestLog4j和TestLog4j.TestLog4j2設(shè)計器。
? 在定義TestLog4j記錄器時沒有指定級別,所以它的級別繼承自它的父記錄器,即要記錄器,所以它的級別也為DEBUG。
在定義TestLog4j記錄器時又定義了一個名稱為R的輸出端,所以它的輸出端有兩個,一個從根記錄器繼承而來的名為stdout的輸出端,
另一個為在此定義的名為R的輸出端。在此需要注意的是,在定義記錄器時必須先定義記錄器的級別,然后才是記錄器的輸出端。
如果只想定義輸出端而不定義級別,則雖然級別可以為空,但逗號分隔符不能省略。如定義TestLog4j記錄器的做法。
? 在定義TestLog4j.TestLog4j2記錄器時又指定了它的級別,由于一個記錄器的級別只能有一個,
所以新指定的級別將覆寫掉它的父記錄器的級別(這就象Java中的多態(tài))。我們沒有定義TestLog4j.TestLog4j2記錄器的輸出端,
所以它的輸出端將從它的父記錄器中繼承而來。它的父記錄器為estLog4j記錄器,所以它和estLog4j記錄器一樣具有兩個名稱分別為
stdout和R的輸出端。
3) 剩下的3區(qū)和4區(qū)分別設(shè)置了兩個輸出端的參數(shù)值。
接下來,回到我們的代碼,看一下是如何取得記錄器,在取記錄器時又發(fā)生了什么。
1) 例4-a中的代碼(2)中,語句Logger.getLogger()中的參數(shù)TestLog4j.class.getName()的值為TestLog4j. TestLog4j,
所以此語句的結(jié)果是取得一個名為TestLog4j. TestLog4j的記錄器的對象。但在配置文件中并沒有定義這樣的記錄器,
所以最終將返回與所需的名稱TestLog4j. TestLog4j最接近的記錄器對象,即名為TestLog4j的記錄器的對象。
2) 例4-b中的代碼(1)的原理與例4-a中的代碼(2)相似,期望取得的是名為TestLog4j.TestLog4j2. TestLog4j2的記錄器對象,
但最終返回的是TestLog4j.TestLog4j2記錄器的對象。
1 什么是final類
API中的某些類,如String,以及Math等,就是final類的典型例子。雖然在Java編程中并不經(jīng)常使用final類和final方法,但它們有著與眾不同的特點,即final類不能被繼承,不能被覆蓋,以及final類在執(zhí)行速度方面比一般類快。下面對final類和final方法的概念和編程技術(shù)分別加以討論,最后解釋為什么final類可以提高執(zhí)行速度。
2 不能繼承final類
有時在程序需要對繼承加以限制。例如某些處理特殊運算和操作的類,為了安全理由,不允許被其他類所繼承。final類沒有子類,即它處于繼承鏈的尾部,或者除了自動繼承Object之外,它們是獨立存在的支持類,例如執(zhí)行密碼管理的類,處理數(shù)據(jù)庫信息的管理類等等。
使用final類的另外一個理由是執(zhí)行速度。由于它的方法不能夠被覆蓋,所以其地址引用和裝載在編譯期間完成,而不是在運行期間由JVM進行復(fù)雜的裝載,因而簡單和有效。所以如果沒有必要,或者不存在有繼承的可能性時,盡量使用final類。當(dāng)然,在API類庫中不多使用final類是因為它們是標準程序,希望在實際軟件開發(fā)中得以廣泛使用。而具體的應(yīng)用軟件開發(fā)則不同于標準庫程序開發(fā)。
注意final數(shù)據(jù)和final類的不同。final數(shù)據(jù)指常量,即其值一旦初始化,就不能改變。而final類則指不能被其他類所繼承的類。
3 定義final類
在類名前加以關(guān)鍵字final,這個類就被定義為final類,如:
public final class SomeClass {
...
}
或者,
public final class SomeClass extends SuperClass {
...
}
public final class SomeClass {<br/> ...<br/>}<br/> <br/>或者,<br/> <br/>public final class SomeClass extends SuperClass {<br/> ...<br/>}
當(dāng)一個類被定義為final時,它的所有方法都自動成為final方法,但不影響對變量的定義。
4 不能覆蓋final方法
也可以在超類中定義某個方法為final方法。雖然這個類可以被繼承,但子類不能夠覆蓋final方法。API類中的許多方法,如print()和 println(),以及Math類中的所有方法都定義為final方法。在具體應(yīng)用軟件開發(fā)中,一些執(zhí)行特殊性運算和操作的方法,可以定義為final 方法。在方法的返回類型前加入關(guān)鍵字final,則定義該方法為final,如:
public final String printVersion() { //定義final方法
return version;
}
public final String printVersion() { //定義final方法<br/> return version;<br/>}
5 final參數(shù)
final參數(shù)的含義如同final變量一樣,是常數(shù)參數(shù),即當(dāng)方法接受了這個參數(shù)后,其值不能改變。如下代碼中定義方法的參數(shù)為final:
public void setVerison(final String version) { //定義常量參數(shù)
this.version = version;
}
在這個方法中使用如下語句產(chǎn)生語法錯誤:
version = "other version…"; //非法操作
public void setVerison(final String version) { //定義常量參數(shù)<br/> this.version = version;<br/>}<br/> <br/> 在這個方法中使用如下語句產(chǎn)生語法錯誤:<br/> <br/>version = "other version…"; //非法操作
6 提高執(zhí)行速度
final類可以提高執(zhí)行速度主要因為如下原因:
l 不涉及繼承和覆蓋。
l 其地址引用和裝載在編譯時完成。
l 在運行時不要求JVM執(zhí)行因覆蓋而產(chǎn)生的動態(tài)地址引用而花費時間和空間。
l 與繼承鏈上的一般對象相比,垃圾回收器在收回final對象所占據(jù)的地址空間時也相對簡單快捷。
但在某些情況下使用final方法并不能取得提高執(zhí)行速度的結(jié)果。因為并不是所有final方法其地址的裝載和引用在編譯時間完成。
假設(shè)類C繼承了B,B繼承了A,在類A中有final方法。對類C來講,調(diào)用A的final方法的確是inline編譯,即裝載在編譯時間完成;但對A和B 來講,可能沒有調(diào)用final方法。而在執(zhí)行期間,JVM動態(tài)裝載的方法有可能并不是C所調(diào)用的final方法。這種情況下,則不能夠取得提高執(zhí)行速度的結(jié)果。當(dāng)然,如果final方法在編譯時間裝載到JVM,而且沒有在執(zhí)行期間覆蓋的,可以取得inline效益,提高執(zhí)行速度。
作者建議是:不能僅僅因為考慮追求提高執(zhí)行速度而使用final類。在程序設(shè)計和代碼編寫時,應(yīng)首先考慮這個類所執(zhí)行的任務(wù)和安全因素,是否允許有子類。在這個前提下,盡量提高代碼的重復(fù)應(yīng)用性是面向?qū)ο笤O(shè)計和編程的宗旨。然后考慮是否使用final類和final方法。
投票成功,感謝您的投票