Log4j是Apache的一個開放源代碼項目,通過使用Log4j,我們可以控制日誌信息輸出地;我們也可以控制每一條日誌的輸出格式;通過定義每一條日誌信息的級別,我們能夠更加細緻地控制日誌的生成過程。最令人感興趣的就是,這些可以通過一個配置文件來靈活地進行配置,而不需要修改應用的代碼。那麼是不是這樣,我們就可以完全使用log4j,而不需要擴展定製了呢?
1、基本介紹Log4j
Log4j是Apache的一個開放源代碼項目,通過使用Log4j,我們可以控制日誌信息輸出地;我們也可以控制每一條日誌的輸出格式;通過定義每一條日誌信息的級別,我們能夠更加細緻地控制日誌的生成過程。最令人感興趣的就是,這些可以通過一個配置文件來靈活地進行配置,而不需要修改應用的代碼。
log4j的好處在於:
1) 通過修改配置文件,就可以決定log信息的目的地——控制檯、文件、GUI組件、甚至是套接口服務器、NT的事件記錄器、UNIX Syslog守護進程等
2) 通過修改配置文件,可以定義每一條日誌信息的級別,從而控制是否輸出。在系統開發階段可以打印詳細的log信息以跟蹤系統運行情況,而在系統穩定後可以關閉log輸出,從而在能跟蹤系統運行情況的同時,又減少了垃圾代碼(System.out.println(......)等)。
3) 使用log4j,需要整個系統有一個統一的log機制,有利於系統的規劃。
那麼是不是這樣,我們就可以完全使用log4j,而不需要擴展定製了呢?當然不是這樣,因爲每個項目的需求不一樣,而且log4j本身也提供了靈活的擴展機制。下面我們說說log4j常用的擴展方式。
2、擴展點Log4j
2.1 自己的日誌系統
每一個項目都想有自己的一套日誌系統,而不受其他項目、jar包的影響。所以日誌系統需要獨立存在,且有相應的自己單獨的配置文件而不受影響。我們先來看看common-logging和log4j的默認工作流程:
common-logging
當我們用Log log = LogFactory.getLog(“loggerName”);取得log時,讓我們看看他是如何工作的?
1)、首先在classpath下尋找自己的配置文件commons-logging.properties,如果找到,則使用其中定義的Log實現類
2)、如果找不到commons-logging.properties文件,則在查找是否已定義系統環境變量org.apache.commons.logging.Log,找到則使用其定義的Log實現類
3)、查看classpath中是否有Log4j的包,如果發現,則自動使用Log4j作爲日誌實現類
4)、使用JDK自身的日誌實現類(JDK1.4以後纔有日誌實現類)
5)、使用commons-logging自己提供的一個簡單的日誌實現類SimpleLog
commons-logging總是能找到一個日誌實現類,並且儘可能找到一個"最合適"的日誌實現類.
.可以不需要配置文件
.自動判斷有沒有Log4j包,有則自動使用之
.最悲觀的情況下也總能保證提供一個日誌實現(SimpleLog)
log4j
當我們用Logger.getLogger(loggerName);取得log時,讓我們看看他是如何工作的?
1)、查找是否已定義系統環境變量log4j.configuration,找到則使用其定義的Log配置文件;否則搜索log4j.xml,如果存在,則執行4)
2)、如果不存在,搜索log4j.properties
3)、如果不存在,則使用程序默認的日誌倉庫
4)、如果存在,註冊配置日誌倉庫
5)、去日誌倉庫中查詢,如果不存在,則new一個
從上面我們可以看到無論是common-logging還是log4j,以及其他的日誌開源系統都會提供一套默認的遍歷規則,去搜索他的log記錄實例。一般情況下,我們都會使用它的默認規則,但是這樣的話,我們就會受制於它,比如如果有人在默認規則中改變了某一個條件。
如果我們不想使用它的默認規則,該怎麼辦?很簡單,在log4j中是通過日誌倉庫來維護一套獨立日誌系統,只要我們直接使用它的日誌倉庫,寫一個我們自己的日誌工廠就可以了。具體如下:
Public final class CustomLogFactory {
//日誌倉庫實例
Static LoggerRepository h = new Hierarchy(new RootLogger(Level.DEBUG));
Static {
//配置文件定製日誌倉庫屬性,這裏面你可以做更多的文章
//來定義自己的配置文件位置等等
new PropertyConfigurator().configure(h, urlConfigFile);
}
public static Logger getLogger(String loggerName) {
return h.getLogger(loggerName);
}
}
這樣你的日誌系統完全獨立了。是不是很容易?
2.2 自己的日誌記錄器
Log4j的logger本身提供八種級別的日誌記錄,如果你想讓你的logger沒有級別概念,或者讓日誌信息支持國際化,那該怎麼辦?
Log4j用於第三方擴展的記錄日誌方法API
public void log(String callerFQCN, Priority level, Object message, Throwable t);
擴展示例
1)、沒有級別概念
Public final class CustomLogger {
//log4j日誌記錄器
Logger log4j;
//自己的FQCN
Private static final String FQCN = CustomLogger.class.getName();
//構造方法
Private CustomLogger(String loggerName) {
log4j = CustomLogFactory.getLogger(loggerName);
}
//工廠方法
Public static CustomLogger getLogger(String loggerName) {
Return new CustomLogger(loggerName);
}
Public Boolean isEntryEnabled() {
Return log4j. isEnabledFor(Level.INFO);
}
//程序入口日誌
//你完全可以在message上做些手腳,加上一些自己的特殊的日誌信息
//當然你也可以後面介紹的Appender的時候加
Public void entry(String message, Throwable t) {
Log4j.log(FQCN , Level.INFO, message, t);
}
Public Boolean isExitEnabled() {
Return log4j. isEnabledFor(Level.INFO);
}
//程序結束日誌
Public void exit(String message, Throwable t) {
Log4j.log(FQCN , Level.INFO, message, t);
}
}
2)、支持國際化
主要是在message取得上做點事情。就像上面那個Public void entry(String message, Throwable t);如果你把API改爲Public void entry(String messageID, Throwable t);實現改爲:
Public void entry(String messageID, Throwable t) {
Log4j.log(FQCN , Level.INFO, getMessage(messageID), t);
}
//取得相應的國際化信息
Private String getMessage(String messageID) {
Return InternalResource.getLogMessage(messageID, Locale.getDefault());
}
2.3 自己的日誌輸出地
Log4j本身提供了大量的默認Appender實現,已經能很好的解決大部分應用,但是有時候我們還是有一些自己的需要,比如只有在輸出到控制檯Appender時加一個運行時才能確定的固定前綴。我們可以這麼寫:
@Override
protected void subAppend(LoggingEvent event) {
StringBuffer msgBuf = new StringBuffer();
//固定前綴
msgBuf.append('[');
msgBuf.append(Manager.current ().getName());
msgBuf.append(']');
//根據配置文件中的格式化信息格式化event
msgBuf.append(this.layout.format(event));
this.qw.write(msgBuf.toString());
……
}
}
也就是我們想定製某一種日誌輸出地,直接繼承相應的log4j中的類似的Appender,然後複寫相應的方法即可。最後在配置文件中使用自己定義的Appender類就行了。
2.4 自己的日誌過濾器
Log4j的日誌輸出控制級別比較中有一個缺陷就是隻能大於某個級別時才能輸出,沒有小於某個級別時才輸出的控制,當然一般不會用到,如果有類似這樣的需求是不是就做不到了呢?不是的,這時候你可以使用日誌過濾器來實現。比如有這樣一個需求,輸出到控制檯的日誌信息,當級別小於WARN時,用System.out;當大於等於WARN時,用System.err。
過濾器定義如下:
public final class CustomWarnLevelFilter extends org.apache.log4j.spi.Filter {
@Override
public int decide(LoggingEvent event) {
//大於等於WARN的日誌不允許輸出
if(event.getLevel().toInt() >= Level. WARN.toInt()) {
return DENY;
} else {
return ACCEPT;
}
}
}
配置文件中可以這樣配置:
<param name="Target" value="System.out"/>
<layout class="org.apache.log4j.PatternLayout">
<param name="ConversionPattern" value="[%d{yyyy-MM-dd HH:mm:ss,SSS}][%p][%C][Line:%L] %m%n"/>
</layout>
<filter class=" CustomWarnLevelFilter "/>
</appender>
<appender class="com.primeton.ext.common.log.EOSConsoleAppender" name="CONSOLE_ERR">
<param name="Target" value="System.err"/>
<param name="Threshold" value="WARN"/>
<layout class="org.apache.log4j.PatternLayout">
<param name="ConversionPattern" value="[%d{yyyy-MM-dd HH:mm:ss,SSS}][%p][%C][Line:%L] %m%n"/>
</layout>
</appender>
<root>
<level value="DEBUG"/>
<appender-ref ref="CONSOLE_OUT"/>
<appender-ref ref="CONSOLE_ERR"/>
</root>
因爲log4j設計的靈活性,所以當然也有其他的方式來達到這個目的。
上面簡單介紹了對log4j常用的擴展方式,你還可以擴展像layout、errorhandler等等,這裏不再贅述。
3、性能影響因素Log4j
Log4j一直被人所詬病的也就是它的性能問題了,所以在這裏簡要的說明一下使用log4j的注意事項:
1)、如果你的日誌實例的名稱不是經常變化,請將它定義爲static變量。
2)、如果你的message構造很複雜,那麼在構造之前,請先使用isXXXEnabled()判斷。
3)、配置文件中格式控制如果不需要,儘量不要輸出類名和行號。
4)、根據log4j的級別判斷控制流程,在配置文件中儘可能配置的比較細緻。
Logger.info(message)的比較流程:
a. 比較日誌倉庫的threashold
b. 與自身的有效Level(沒有則是父親的)比較
c. 與Appender的threashold比較
d. 詢問Appender的filter
e. Layout格式化
f. 記錄日誌
5)、如果Appender是文件類型,請不要把文件大小設的太小。至少設爲10MB(<param name="MaxFileSize" value="10MB"/>)。
6)、如果不是即時調試程序,把你的級別設定爲高級別,最好是threshold=INFO之上。
7)、如果不是想即時看到日誌信息,你也可以把Appender的ImmediateFlush 設爲false(<param name="ImmediateFlush" value="false"/>)。
4、附錄
4.1 Log4j基本概念
日誌倉庫(或者容器)loggerRepository:跟一個配置文件相對應,顧名思義,裏面存放着日誌實例。
1)、屬性:threashold(閾值)
2)、默認實現:org.apache.log4j.Hierarchy
3)、日誌系統━日誌倉庫━配置文件
日誌記錄器:根日誌和子日誌(繼承的概念,用“.”來區分,日誌名稱的重要性)
1)、的概念(用於第三方封裝和繼承)FQCN
2)、屬性:level, appender, additivity(是否繼承父日誌的appender)
Level:日誌級別
Appender:日誌輸出端
屬性:threashold(閾值),filter,Layout,Filter,ErrorHandler
ErrorHandler:錯誤處理器
4.2 Log4j配置文件詳細說明(*.properties和*.xml)
4.2.1 屬性文件Properties
properties屬性文件
|
|||
編號
|
配置項
|
配置項描述
|
示例
|
1
|
log4j.threshold
|
閾值項
|
log4j.threshold = error
|
2
|
log4j.rootLogger
|
根日誌屬性項
|
log4j.rootLogger = info,stdout1,stdout2
|
3
|
log4j.category.
|
子日誌屬性項(舊)
|
log4j.category.com.eos = NULL,stdout1
|
4
|
log4j.logger.
|
子日誌屬性項(新)
|
log4j.logger.com.eos.log = debug,stdout2
|
5
|
log4j.additivity.
|
appender是否繼承設置
|
log4j.additivity.com.eos = false
|
6
|
log4j.appender.
|
輸出目的地定義項
|
log4j.appender.stdout2 = org.apache.log4j.ConsoleAppender
|
7
|
log4j.appender.A.layout
|
輸出格式定義項
|
log4j.appender.stdout2.layout = org.apache.log4j.PatternLayout
|
4.2.2 文件xml
xml文件
|
|||
編號
|
配置項
|
配置項描述
|
示例
|
1
|
threshold
|
閾值項
|
<log4j:configuration xmlns:log4j="http://jakarta.apache.org/log4j/" debug="false" threshold="null">
|
2
|
root
|
根日誌屬性項
|
<root>
<priority value="info"/> <appender-ref ref="CONSOLE"/> </root> |
3
|
priority
|
級別項(舊)
|
<priority value="info"/>
|
4
|
level
|
級別項(新)
|
<level value="info"/>
|
5
|
category
|
子日誌屬性項(舊)
|
<category additivity="true" name="com.eos.log">
|
6
|
logger
|
子日誌屬性項(新)
|
<logger additivity="true" name="com.eos">
|
7
|
appender-ref
|
輸出端控制項
|
<appender-ref ref="CONSOLE"/>
|
8
|
additivity
|
appender是否繼承設置
|
<logger additivity="true" name="com.eos">
|
9
|
appender
|
輸出目的地定義項
|
<appender class="org.apache.log4j.ConsoleAppender" name="CONSOLE">
|
10
|
layout
|
輸出格式定義項
|
<layout class="org.apache.log4j.PatternLayout">
|
4.2.3 詳細說明(只針對Log4j常用的,用戶可以自定義)Appender
Appender繼承關係
Appender基本種類
org.apache.log4j.ConsoleAppender(控制檯)
org.apache.log4j.FileAppender(文件)
org.apache.log4j.DailyRollingFileAppender(每天產生一個日誌文件)
org.apache.log4j.RollingFileAppender(文件大小到達指定尺寸的時候產生一個新的文件)
org.apache.log4j.WriterAppender(將日誌信息以流格式發送到任意指定的地方)
· ConsoleAppender選項
Threshold=WARN:指定日誌消息的輸出最低層次。
ImmediateFlush=true:默認值是true,意謂着所有的消息都會被立即輸出。
Target=System.err:默認情況下是:System.out,指定輸出控制檯
· FileAppender 選項
Threshold=WARN:指定日誌消息的輸出最低層次。
ImmediateFlush=true:默認值是true,意謂着所有的消息都會被立即輸出。
File=mylog.txt:指定消息輸出到mylog.txt文件。
Append=false:默認值是true,即將消息增加到指定文件中,false指將消息覆蓋指定的文件內容。
· DailyRollingFileAppender 選項
Threshold=WARN:指定日誌消息的輸出最低層次。
ImmediateFlush=true:默認值是true,意謂着所有的消息都會被立即輸出。
File=mylog.txt:指定消息輸出到mylog.txt文件。
Append=false:默認值是true,即將消息增加到指定文件中,false指將消息覆蓋指定的文件內容。
DatePattern='.'yyyy-ww:每週滾動一次文件,即每週產生一個新的文件。當然也可以指定按月、周、天、時和分。即對應的格式如下:
1)'.'yyyy-MM: 每月
2)'.'yyyy-ww: 每週
3)'.'yyyy-MM-dd: 每天
4)'.'yyyy-MM-dd-a: 每天兩次
5)'.'yyyy-MM-dd-HH: 每小時
6)'.'yyyy-MM-dd-HH-mm: 每分鐘
n RollingFileAppender 選項
Threshold=WARN:指定日誌消息的輸出最低層次。
ImmediateFlush=true:默認值是true,意謂着所有的消息都會被立即輸出。
File=mylog.txt:指定消息輸出到mylog.txt文件。
Append=false:默認值是true,即將消息增加到指定文件中,false指將消息覆蓋指定的文件內容。
MaxFileSize=100KB: 後綴可以是KB, MB 或者是 GB. 在日誌文件到達該大小時,將會自動滾動,即將原來的內容移到mylog.log.1文件。
MaxBackupIndex=2:指定可以產生的滾動文件的最大數。
4.2.4 詳細說明(只針對Log4j,用戶可以自定義)Layout
Log4j的Layout基本種類
org.apache.log4j.HTMLLayout(以HTML表格形式佈局),
org.apache.log4j.PatternLayout(可以靈活地指定佈局模式),
org.apache.log4j.SimpleLayout(包含日誌信息的級別和信息字符串),
org.apache.log4j.TTCCLayout(包含日誌產生的時間、線程、類別等等信息)
· HTMLLayout選項
LocationInfo=true:默認值是false,輸出java文件名稱和行號
Title=my app file: 默認值是 Log4J Log Messages.
n PatternLayout 選項
log4j.appender.A1.layout.ConversionPattern=%-4r %-5p %d{yyyy-MM-dd HH:mm:ssS} %c %m%n
這裏需要說明的就是日誌信息格式中幾個符號所代表的含義:
%X: 信息輸出時左對齊;
%p: 輸出日誌信息優先級,即DEBUG,INFO,WARN,ERROR,FATAL,
%d: 輸出日誌時間點的日期或時間,默認格式爲ISO8601,也可以在其後指定格式,比如:%d{yyy MMM dd HH:mm:ss,SSS},輸出類似:2002年10月18日 22:10:28,921
%r: 輸出自應用啓動到輸出該log信息耗費的毫秒數
%c: 輸出日誌信息所屬的類目,通常就是所在類的全名
%t: 輸出產生該日誌事件的線程名
%l: 輸出日誌事件的發生位置,相當於%C.%M(%F:%L)的組合,包括類目名、發生的線程,以及在代碼中的行數。舉例:Testlog4.main(TestLog4.java:10)
%x: 輸出和當前線程相關聯的NDC(嵌套診斷環境),尤其用到像java servlets這樣的多客戶多線程的應用中。
%%: 輸出一個"%"字符
%F: 輸出日誌消息產生時所在的文件名稱
%L: 輸出代碼中的行號
%m: 輸出代碼中指定的消息,產生的日誌具體信息
%n: 輸出一個回車換行符,Windows平臺爲"/r/n",Unix平臺爲"/n"輸出日誌信息換行,可以在%與模式字符之間加上修飾符來控制其最小寬度、最大寬度、和文本的對齊方式。如:
1)%20c:指定輸出category的名稱,最小的寬度是20,如果category的名稱小於20的話,默認的情況下右對齊。
2)%-20c:指定輸出category的名稱,最小的寬度是20,如果category的名稱小於20的話,"-"號指定左對齊。
3)%.30c:指定輸出category的名稱,最大的寬度是30,如果category的名稱大於30的話,就會將左邊多出的字符截掉,但小於30的話也不會有空格。
4)%20.30c:如果category的名稱小於20就補空格,並且右對齊,如果其名稱長於30字符, 就從左邊交遠銷出的字符截掉。
· XMLLayout 選項
LocationInfo=true:默認值是false,輸出java文件和行號
4.3 日誌配置文件內容範例
4.3.1 log4j.properties
#DEBUG, CONSOLE,FILE,ROLLING_FILE,MAIL,DATABASE
log4j.logger.org.apache=INFO, FILE
log4j.additivity.org.apache=false
###################
# Console Appender
###################
log4j.appender.CONSOLE=org.apache.log4j.ConsoleAppender
log4j.appender.Threshold=DEBUG
log4j.appender.CONSOLE.Target=System.out
log4j.appender.CONSOLE.layout=org.apache.log4j.PatternLayout
log4j.appender.CONSOLE.layout.ConversionPattern=%d [%p] - %m%n
#####################
# File Appender
#####################
log4j.appender.FILE=org.apache.log4j.FileAppender
log4j.appender.FILE.File=file.log
log4j.appender.FILE.Append=false
log4j.appender.FILE.layout=org.apache.log4j.PatternLayout
log4j.appender.FILE.layout.ConversionPattern=%d [%p] - %m%n
########################
# Rolling File
########################
log4j.appender.ROLLING_FILE=org.apache.log4j.RollingFileAppender
log4j.appender.ROLLING_FILE.Threshold=ERROR
log4j.appender.ROLLING_FILE.File=rolling.log
log4j.appender.ROLLING_FILE.Append=true
log4j.appender.ROLLING_FILE.MaxFileSize=10KB
log4j.appender.ROLLING_FILE.MaxBackupIndex=1
log4j.appender.ROLLING_FILE.layout=org.apache.log4j.PatternLayout
log4j.appender.ROLLING_FILE.layout.ConversionPattern=%d [%p] - %m%n
####################
# Socket Appender
####################
log4j.appender.SOCKET=org.apache.log4j.RollingFileAppender
log4j.appender.SOCKET.RemoteHost=localhost
log4j.appender.SOCKET.Port=5001
log4j.appender.SOCKET.LocationInfo=true
# Set up for Log Facter 5
log4j.appender.SOCKET.layout=org.apache.log4j.PatternLayout
log4j.appender.SOCET.layout.ConversionPattern=%d [%p] - %m%n
########################
# Log Factor 5 Appender
########################
log4j.appender.LF5_APPENDER=org.apache.log4j.lf5.LF5Appender
log4j.appender.LF5_APPENDER.MaxNumberOfRecords=2000
########################
# SMTP Appender
#######################
log4j.appender.MAIL=org.apache.log4j.net.SMTPAppender
log4j.appender.MAIL.Threshold=FATAL
log4j.appender.MAIL.BufferSize=10
[email protected]
log4j.appender.MAIL.SMTPHost=mail.primeton.com
log4j.appender.MAIL.Subject=Log4J Message
[email protected]
log4j.appender.MAIL.layout=org.apache.log4j.PatternLayout
log4j.appender.MAIL.layout.ConversionPattern=%d [%p] - %m%n
########################
# JDBC Appender
#######################
log4j.appender.DATABASE=org.apache.log4j.jdbc.JDBCAppender
log4j.appender.DATABASE.URL=jdbc:mysql://localhost:3306/test
log4j.appender.DATABASE.driver=com.mysql.jdbc.Driver
log4j.appender.DATABASE.user=root
log4j.appender.DATABASE.password=
log4j.appender.DATABASE.sql=INSERT INTO LOG4J (Message) VALUES (%d [%p] - %m%n)
log4j.appender.DATABASE.layout=org.apache.log4j.PatternLayout
log4j.appender.DATABASE.layout.ConversionPattern=%d [%p] - %m%n
log4j.appender.A1=org.apache.log4j.DailyRollingFileAppender
log4j.appender.A1.File=SampleMessages.log4j
log4j.appender.A1.DatePattern=yyyyMMdd-HH'.log4j'
log4j.appender.A1.layout=org.apache.log4j.xml.XMLLayout
###################
#自定義Appender
###################
log4j.appender.im = net.cybercorlin.util.logger.appender.IMAppender
log4j.appender.im.host = mail.cybercorlin.net
log4j.appender.im.username = username
log4j.appender.im.password = password
log4j.appender.im.recipient = [email protected]
log4j.appender.im.layout=org.apache.log4j.PatternLayout
log4j.appender.im.layout.ConversionPattern =%d [%p] - %m%n
#注意:在屬性配置文件中,屬性值的第一個一定是級別,輸出端可有可無,以逗號分割。(而xml文件格式沒有這種限制)
4.3.2 log4j.xml
< xml version="1.0" encoding="UTF-8" >
<!DOCTYPE log4j:configuration SYSTEM "log4j.dtd">
<log4j:configuration xmlns:log4j="http://jakarta.apache.org/log4j/" debug="false" threshold="null">
<appender class="org.apache.log4j.ConsoleAppender" name="CONSOLE">
<param name="Target" value="System.out"/>
<param name="Threshold" value="INFO"/>
<layout class="org.apache.log4j.PatternLayout">
<param name="ConversionPattern" value="%d [%p] - %m%n "/>
</layout>
<filter class="org.apache.log4j.varia.DenyAllFilter"/>
<errorHandler class="org.apache.log4j.varia. FallbackErrorHandler"/>
</appender>
<appender class="org.apache.log4j.FileAppender" name="FILE">
<param name="File" value="file.log"/>
<param name="Append" value="false"/>
<param name="Threshold" value="INFO"/>
<layout class="org.apache.log4j.PatternLayout">
<param name="ConversionPattern" value="%d [%p] - %m%n "/>
</layout>
</appender>
<appender class="org.apache.log4j.RollingFileAppender" name="ROLLING_FILE">
<param name="Threshold" value="INFO"/>
<param name="File" value="rolling.log"/>
<param name="Append" value="false"/>
<param name="MaxFileSize" value="10KB"/>
<param name="MaxBackupIndex" value="1"/>
<layout class="org.apache.log4j.PatternLayout">
<param name="ConversionPattern" value="%d [%p] - %m%n "/>
</layout>
</appender>
<logger additivity="false" name="com.eos">
<level value="info"/>
<appender-ref ref="CONSOLE"/>
</logger>
<category additivity="true" name="com.eos.log">
<priority value="warn"/>
</category>
<root>
<priority value="info"/>
<appender-ref ref="CONSOLE"/>
</root>
</log4j:configuration>