背景
對于有經驗的開發者來說,日志記錄的重要性顯而易見。例如程序中的異常處理和安全性都依賴于Logging的功能來幫助履行它們的指責。應用程序中的日志記錄主要基于三個目的:監視代碼中變量的變化情況,周期性的記錄到文件中供其他應用進行統計分析工作;跟蹤代碼運行時軌跡,作為日后審計的依據;擔當集成開發環境中的調試器的作用,向文件或控制臺打印代碼的調試信息。經驗表明日志記錄是開發周期中的重要組成部分。
最簡單的做法就是在代碼中嵌入許多的打印語句,但是這樣打印語句會充斥代碼的主體,顯然不是一個好方法。因此,使用成熟的框架例如Log4j,則會更具靈活性。
Log4j簡介
Log4j 框架是用 Java 語言編寫的標準日志記錄框架。作為 Jakarta 項目的一部分,它在 Apache 軟件許可證(Apache Software License)下分發,以速度和靈活性為中心概念:Log4j 環境是完全可配置的,通過使用Log4j,我們可以控制日志信息輸送的目的地是控制臺、文件、GUI組件、甚至是套接口服務器、NT的事件記錄器、UNIX Syslog守護進程等;我們也可以控制每一條日志的輸出格式;通過定義每一條日志信息的級別,我們能夠更加細致地控制日志的生成過程。
Log4j由三個重要的部件構成:記錄器(Loggers)、輸出源(Appenders)和布局(Layouts)。
記錄器按照布局中指定的格式把日志信息寫入一個或多個輸出源。輸出源可以是控制臺、文本文件、XML文件或Socket,甚至還可以把信息寫入到Windows事件日志或通過電子郵件發送。我們可以通過配置文件來部署這些組件。
其實您也可以完全不使用配置文件,而是在代碼中配置Log4j環境。但是,使用配置文件將使您的應用程序更加靈活。本文從描述 log4j 體系結構的主要組件著手。然后是描述基本用法和配置的簡單示例。
定義配置文件
Log4j支持兩種配置文件格式,一種是XML格式的文件,一種是Java特性文件(鍵=值)。下面我們介紹使用Java特性文件做為配置文件的方法:
一、 配置記錄器。
Log4j允許程序員定義多個記錄器,每個記錄器有自己的名字。但有一個記錄器叫根記錄器,它永遠存在,且不能通過名字檢索或引用,在配置文件中,可以如下定義根記錄器:
log4j.rootLogger = [ level ] , appenderName, appenderName, …
Level是記錄器的級別,它是日志記錄的優先級,分為OFF、FATAL、ERROR、WARN、INFO、DEBUG、ALL或者您定義的級別。Log4j建議只使用四個級別:ERROR、WARN、INFO、DEBUG:
DEBUG < INFO < WARN < ERROR < FATAL
右邊的級別比左邊的高。如果一條log信息的級別,大于等于記錄器的級別值,那么記錄器就會記錄它。例如level被設置為INFO級別,那么應用程序中所有的DEBUG的日志信息將不被打印出來。通過在這里定義的級別,您可以控制到應用程序中相應級別的日志信息的開關。
appenderName是輸出源的名字,它指定日志信息輸出到哪個地方。您可以為一個記錄器指定多個輸出源。
在一些配置文件中,你可能會看到下面的語句:
log4j.rootCategory = [ level ] , appenderName, appenderName, …
在早期的Log4j版本中,org.apache.Category實現了記錄器的功能,為了提高向后兼容性,Logger擴展了Category,因此rootCategory和rootLogger是可以互換的,但最后Category將從類庫中刪除,因此請使用Logger類。
除了根記錄器之外,log4j允許程序員定義多個記錄器,每個記錄器有自己的名字:
log4j.logger.loggerName = [ level ] , appenderName, appenderName, …
在Log4J中Logger是具有層次關系的,Log4j支持配置的記錄器之間的“父子關系”,記錄器之間通過名字來表明隸屬關系(或家族關系),它們有一個共同的根,位于最上層,其它Logger遵循類似包的層次:記錄器a.b,與記錄器a.b.c之間是父子關系,而記錄器a與a.b.c之間是祖先與后代的關系。例如:
static Logger root = Logger.getRootLogger();
static Logger log1 = Logger.getLogger("cc");
static Logger log2 = Logger.getLogger("cc.ejb");
static Logger log3 = Logger.getLogger("cc.ejb.my.TestApp");
上面代碼中,log1是log2的父親,是log3的祖先,而root是所有log1、log2、log3的祖先,它們都從root中繼承。所以,一般情況下,僅需要配置好rootLogger,其它子記錄器都會從中繼承rootLogger的配置。如果修改了rootLogger的配置,其它所有的子記錄器也會繼承這種變化。這樣就大大地方便了配置。
如果一個應用中包含了上千個類都需要日志,那么我們是否需要配置上千個Logger呢?我們通過一個簡單的辦法來解決這個問題: 用每一個java類文件名(包含該類的包名)定義一個記錄器,這是一種有用并且直觀的記錄器實例名的定義方式。例如在配置文件中定義了一個com.foo的記錄器:
log4j.logger.com.foo=WARN
在com.foo中的一個java類bar,我們通過其本類的名字獲得一個記錄器“com.foo.Bar”:
package com.foo;
class Bar{
static Logger log=Logger.getLogger(bar.Class.getName());
.....
}
由于記錄器com.foo.Bar 沒有指定的級別,它從com.foo(在配置文件中其級別設置成WARN) 繼承級別。并且這樣我們就能方便的從大量log信息中判斷出它們各自的來源。當然了,這不是硬性規定的,實際上Log4j沒有對設置記錄器的實例名做什么限制,程序員可以根據自己的喜好隨意定義。
二、日志信息輸出源Appender
log4j 還允許日志記錄請求打印到多個輸出目的地,按 log4j 的叫法是輸出源。一個記錄器可以有多個輸出源。一條log信息如果可被這個記錄器處理,則該記錄器會把這條信息送往每個它所擁有的輸出源,以及層次結構中更高級的輸出源。例如,根記錄器以控制臺作為輸出源,則所有可被紀錄的日志都將至少打印到控制臺。
配置日志信息輸出源,其語法為:
log4j.appender.appenderName = fully.qualified.name.of.appender.class
log4j.appender.appenderName.option1 = value1
…
log4j.appender.appenderName.option = valueN
Log4j提供的appender有以下幾種:
- org.apache.log4j.ConsoleAppender(控制臺)
- org.apache.log4j.FileAppender(文件)
- org.apache.log4j.DailyRollingFileAppender(每天產生一個日志文件)
- org.apache.log4j.RollingFileAppender(文件大小到達指定尺寸的時候產生一個新的文件)
- org.apache.log4j.WriterAppender(將日志信息以流格式發送到任意指定的地方)
- org.apache.log4j.SocketAppender (Socket)
- org.apache.log4j.NtEventLogAppender (NT的Event Log)
- org.apache.log4j.JMSAppender (電子郵件)
請注意,可以通過覆蓋缺省行為,這樣就不再附加累積的輸出源:
log4j.additivity.loggerName=false
注意,不要把一個輸出源附加到多個記錄器上,否則會得到“Attempted to append to closed appender named xxx”的信息。
三、配置日志信息的格式(布局),其語法為:
log4j.appender.appenderName.layout = fully.qualified.name.of.layout.class
log4j.appender.appenderName.layout.option1 = value1
…
log4j.appender.appenderName.layout.option = valueN
其中,Log4j提供的layout有以下幾種:
- org.apache.log4j.HTMLLayout(以HTML表格形式布局)
- org.apache.log4j.PatternLayout(可以靈活地指定布局模式)
- org.apache.log4j.SimpleLayout(包含日志信息的級別和信息字符串)
- org.apache.log4j.TTCCLayout(包含日志產生的時間、線程、類別等等信息)
如果采用了PatternLayout, 則Log4J采用類似C語言中的printf函數的打印格式格式化日志信息,打印參數如下:
- %m 輸出代碼中指定的消息
- %p 輸出優先級,即DEBUG,INFO,WARN,ERROR,FATAL
- %r 輸出自應用啟動到輸出該log信息耗費的毫秒數
- %c 輸出所屬的類目,通常就是所在類的全名
- %t 輸出產生該日志事件的線程名
- %n 輸出一個回車換行符,Windows平臺為“\r\n”,Unix平臺為“\n”
- %d 輸出日志時間點的日期或時間,默認格式為ISO8601,也可以在其后指定格式,比如:%d{yyy MMM dd HH:mm:ss,SSS},輸出類似:2002年10月18日 22:10:28,921
- %l 輸出日志事件的發生位置,包括類目名、發生的線程,以及在代碼中的行數。舉例:Testlog4.main(TestLog4.java:10)
四、例子
下面是一個完整的Log4j配置文件,這個配置文件指定了兩個輸出源stdout和R。前者把日志信息輸出到控制臺,后者是一個輪轉日志文件。最大的文件是100KB,當一個日志文件達到最大尺寸時,Log4J會自動把example.log重命名為example.log.1,然后重建一個新的example.log文件,依次輪轉。
log4j.rootLogger=debug, stdout, R
log4j.appender.stdout=org.apache.log4j.FileAppender
log4j.appender.stdout.File=System.out
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
log4j.logger.cc.ejb.my=error,out
log4j.appender.out=org.apache.log4j.ConsoleAppender
log4j.appender.out.layout=org.apache.log4j.PatternLayout
log4j.appender.out.layout.ConversionPattern=%p %t %c - %m%n
log4j.logger.cc.ejb.my.son=debug
log4j.additivity.cc.ejb.my.son=false
在代碼中使用Log4j
一、得到記錄器
使用Log4j,第一步就是獲取日志記錄器,這個記錄器將負責控制日志信息。其語法為:
public static Logger getLogger( String name)
通過指定的名字獲得記錄器,如果必要的話,則為這個名字創建一個新的記錄器。Name一般取本類的名字,比如:
static Logger logger = Logger.getLogger ( ServerWithLog4j.class.getName () )
二、讀取配置文件
當獲得了日志記錄器之后,第二步將配置Log4j環境,其語法為:
BasicConfigurator.configure (): 自動快速地使用缺省Log4j環境。
PropertyConfigurator.configure ( String configFilename) :讀取使用Java的特性文件編寫的配置文件。
DOMConfigurator.configure ( String filename ) :讀取XML形式的配置文件。
三、插入記錄信息(格式化日志信息)
當上兩個必要步驟執行完畢,您就可以輕松地使用不同優先級別的日志記錄語句插入到您想記錄日志的任何地方,其語法如下:
Logger.debug ( Object message ) ;
Logger.info ( Object message ) ;
Logger.warn ( Object message ) ;
Logger.error ( Object message ) ;
四、例子
我們通過下面這個簡單的例子,來演示在程序如何使用Log4j,您可以修改配置文件以得到不同日志信息。
package cc.ejb.my;
import org.apache.log4j.Logger;
import org.apache.log4j.PropertyConfigurator;
import my.son.Foo;
public class TestApp {
static Logger logger=Logger.getLogger(TestApp.class.getName());
public static void main(String[] args) {
PropertyConfigurator.configure("log4j.properties");
logger.info("Applcaiton Starts");
logger.warn("Bar Starts");
Bar bar=new Bar();
logger.error("Bar Errors");
bar.doIt();
logger.warn("Bar Exits");
logger.info("Foo Starts");
Foo foo=new Foo();
logger.error("Foo Errors");
foo.doit();
logger.warn("Foo exits ");
logger.info("Applcaition Exits");
}
}
class Bar
{
static Logger logger = Logger.getLogger(Bar.class.getName());
public void doIt() {
logger.debug("Did it again!");
}
}
package cc.ejb.my.son;
import org.apache.log4j.Logger;
public class Foo {
private Logger log=Logger.getLogger(Foo.class.getName());
public Foo() {
log.info("Foo Initialzie");
}
public void doit()
{
log.debug("Do it in Foo");
}
}