淺談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中是通過日誌倉庫來維護一套獨立日誌系統,只要我們直接使用它的日誌倉庫,寫一個我們自己的日誌工廠就可以了。具體如下:
1.Public final class CustomLogFactory { 2.//日誌倉庫實例 3.Static LoggerRepository h = new Hierarchy(new RootLogger(Level.DEBUG)); 4. 5.Static { 6.//配置文件定製日誌倉庫屬性,這裏面你可以做更多的文章 7.//來定義自己的配置文件位置等等 8.new PropertyConfigurator().configure(h, urlConfigFile); 9.} 10. 11.public static Logger getLogger(String loggerName) { 12.return h.getLogger(loggerName); 13.} 14.}

 這樣你的日誌系統完全獨立了。是不是很容易?
2.2   自己的日誌記錄器
Log4j的logger本身提供八種級別的日誌記錄,如果你想讓你的logger沒有級別概念,或者讓日誌信息支持國際化,那該怎麼辦?
Log4j用於第三方擴展的記錄日誌方法API
public boolean isEnabledFor(Priority level);
public void log(String callerFQCN, Priority level, Object message, Throwable t);
擴展示例
1)        沒有級別概念
1.Public final class CustomLogger { 2.//log4j日誌記錄器 3.Logger log4j; 4. 5.//自己的FQCN 6.Private static final String FQCN = CustomLogger.class.getName(); 7. 8.//構造方法 9.Private CustomLogger(String loggerName) { 10.log4j = CustomLogFactory.getLogger(loggerName); 11.} 12. 13.//工廠方法 14.Public static CustomLogger getLogger(String loggerName) { 15.Return new CustomLogger(loggerName); 16.} 17. 18.Public Boolean isEntryEnabled() { 19.Return log4j. isEnabledFor(Level.INFO); 20.} 21.//程序入口日誌 22.//你完全可以在message上做些手腳,加上一些自己的特殊的日誌信息 23.//當然你也可以後面介紹的Appender的時候加 24.Public void entry(String message, Throwable t) { 25.Log4j.log(FQCN , Level.INFO, message, t); 26.} 27. 28.Public Boolean isExitEnabled() { 29.Return log4j. isEnabledFor(Level.INFO); 30.} 31.//程序結束日誌 32.Public void exit(String message, Throwable t) { 33.Log4j.log(FQCN , Level.INFO, message, t); 34.} 35.}

 2)        支持國際化
主要是在message取得上做點事情。就像上面那個Public void entry(String message, Throwable t);如果你把API改爲Public void entry(String messageID, Throwable t);實現改爲:

1.Public void entry(String messageID, Throwable t) { 2.Log4j.log(FQCN , Level.INFO, getMessage(messageID), t); 3.} 4.//取得相應的國際化信息 5.Private String getMessage(String messageID) { 6.Return InternalResource.getLogMessage(messageID, Locale.getDefault()); 7.}

 2.3   自己的日誌輸出地
Log4j本身提供了大量的默認Appender實現,已經能很好的解決大部分應用,但是有時候我們還是有一些自己的需要,比如只有在輸出到控制檯Appender時加一個運行時才能確定的固定前綴。我們可以這麼寫:
1.public final class CustomConsoleAppender extends org.apache.log4j.ConsoleAppender { 2. 3.@Override 4.protected void subAppend(LoggingEvent event) { 5.StringBuffer msgBuf = new StringBuffer(); 6. 7.//固定前綴 8.msgBuf.append('['); 9.msgBuf.append(Manager.current ().getName()); 10.msgBuf.append(']'); 11. 12.//根據配置文件中的格式化信息格式化event 13.msgBuf.append(this.layout.format(event)); 14.this.qw.write(msgBuf.toString()); 15.…… 16.} 17.}

 也就是我們想定製某一種日誌輸出地,直接繼承相應的log4j中的類似的Appender,然後複寫相應的方法即可。最後在配置文件中使用自己定義的Appender類就行了。
2.4   自己的日誌過濾器
Log4j的日誌輸出控制級別比較中有一個缺陷就是隻能大於某個級別時才能輸出,沒有小於某個級別時才輸出的控制,當然一般不會用到,如果有類似這樣的需求是不是就做不到了呢?不是的,這時候你可以使用日誌過濾器來實現。比如有這樣一個需求,輸出到控制檯的日誌信息,當級別小於WARN時,用System.out;當大於等於WARN時,用System.err。
過濾器定義如下:
1.public final class CustomWarnLevelFilter extends org.apache.log4j.spi.Filter { 2.@Override 3.public int decide(LoggingEvent event) { 4.//大於等於WARN的日誌不允許輸出 5.if(event.getLevel().toInt() >= Level. WARN.toInt()) { 6.return DENY; 7.} else { 8.return ACCEPT; 9.} 10.} 11.}

 配置文件中可以這樣配置:
1.<appender class="com.primeton.ext.common.log.EOSConsoleAppender" name="CONSOLE_OUT"> 2.<param name="Target" value="System.out"/> 3.<layout class="org.apache.log4j.PatternLayout"> 4.<param name="ConversionPattern" value="[%d{yyyy-MM-dd HH:mm:ss,SSS}][%p][%C][Line:%L] %m%n"/> 5.</layout> 6.<filter class=" CustomWarnLevelFilter "/> 7.</appender> 8. 9.<appender class="com.primeton.ext.common.log.EOSConsoleAppender" name="CONSOLE_ERR"> 10.<param name="Target" value="System.err"/> 11.<param name="Threshold" value="WARN"/> 12.<layout class="org.apache.log4j.PatternLayout"> 13.<param name="ConversionPattern" value="[%d{yyyy-MM-dd HH:mm:ss,SSS}][%p][%C][Line:%L] %m%n"/> 14.</layout> 15.</appender> 16. 17.<root> 18.<level value="DEBUG"/> 19.<appender-ref ref="CONSOLE_OUT"/> 20.<appender-ref ref="CONSOLE_ERR"/> 21.</root>

 因爲log4j設計的靈活性,所以當然也有其他的方式來達到這個目的。
 
上面簡單介紹了對log4j常用的擴展方式,你還可以擴展像layout、errorhandler等等,這裏不再贅述。
3       性能影響因素Log4j
Log4j一直被人所詬病的也就是它的性能問題了,所以在這裏簡要的說明一下使用log4j的注意事項:
1)        如果你的日誌實例的名稱不是經常變化,請將它定義爲static變量。
2)        如果你的message構造很複雜,那麼在構造之前,請先使用isXXXEnabled()判斷。
3)        配置文件中格式控制如果不需要,儘量不要輸出類名和行號。
4)        根據log4j的級別判斷控制流程,在配置文件中儘可能配置的比較細緻。
Logger.info(message)的比較流程:
                                      i.              比較日誌倉庫的threashold
                                    ii.              與自身的有效Level(沒有則是父親的)比較
                                  iii.              與Appender的threashold比較
                                   iv.              詢問Appender的filter
                                     v.              Layout格式化
                                   vi.              記錄日誌
5)        如果Appender是文件類型,請不要把文件大小設的太小。至少設爲10MB(<param name="MaxFileSize" value="10MB"/>)。
6)        如果不是即時調試程序,把你的級別設定爲高級別,最好是threshold=INFO之上。
7)        如果不是想即時看到日誌信息,你也可以把Appender的ImmediateFlush 設爲false(<param name="ImmediateFlush" value="false"/>)。
 
4       附錄
4.1   Log4j基本概念
Log4j體系結構圖


 
 
日誌倉庫(或者容器)loggerRepository:跟一個配置文件相對應,顧名思義,裏面存放着日誌實例。
1)        屬性:threashold(閾值)
2)        默認實現:org.apache.log4j.Hierarchy
3)        日誌系統━日誌倉庫━配置文件
 
日誌記錄器:根日誌和子日誌(繼承的概念,用“.”來區分,日誌名稱的重要性)
1)        的概念(用於第三方封裝和繼承)FQCN
2)        屬性:level, appender, additivity(是否繼承父日誌的appender)
 
Level:日誌級別
 
Appender:日誌輸出端
屬性:threashold(閾值),filter,Layout,Filter,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
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章