slf4j優於log4j的原因
每一個Java程式員都知道日誌對於任何一個Java應用程式,尤其是服務端程式是至關重要的,而很多程式員也已經熟悉各種不同的日誌庫如java.util.logging、Apache log4j、logback。
- 我強烈建議,在java中任何新的程式碼開發,都應使用SLF4J而不是任何的日誌API,包括log4J。
本文將解釋為什麼使用SLF4J比log4j或者java.util.logging要優秀?
一、抽象
SLF4J(Simple logging Facade for Java)不是一個真正的日誌實現,而是一個抽象層( abstraction layer),它允許你在後臺使用任意一個日誌類庫。如果是在編寫供內外部都可以使用的API或者通用類庫,那麼你真不會希望使用你類庫的客戶端必須使用你選擇的日誌類庫。
如果一個專案已經使用了log4j,而你載入了一個類庫,比方說 Apache Active MQ——它依賴於於另外一個日誌類庫logback,那麼你就需要把它也載入進去。但如果Apache Active MQ使用了SLF4J,你可以繼續使用你的日誌類庫而無語忍受載入和維護一個新的日誌框架的痛苦。
總的來說,SLF4J使你的程式碼獨立於任意一個特定的日誌API ,這是一個對於開發API的開發者很好的思想。雖然抽象日誌類庫的思想已經不是新鮮的事物而且Apache commons logging也已經在使用這種思想了,但現在SLF4J正迅速成為Java世界的日誌標準。
二、佔位符
在程式碼中表示為{}的特性。佔位符是一個非常類似於在String的format()方法中的%s,它會在執行時被某個提供的實際字串所替換。
這不僅降低了你程式碼中字串連線次數 ,而且還節省了新建的String物件 。
因為String物件是不可修改的並且它們建立在一個String池中,它們消耗堆記憶體( heap memory)而且大多數時間他們是不被需要的,例如當你的應用程式在生產環境以ERROR級別執行時候,一個String使用在DEBUG語句就是不被需要的。
通過使用SLF4J,你可以在執行時延遲字串的建立 ,這意味著只有需要的String物件才被建立 。而如果你已經使用log4j,那麼你已經對於在if條件中使用debug語句這種變通方案十分熟悉了,但SLF4J的佔位符就比這個好用得多。
Log4j:
if (logger.isDebugEnabled()) { logger.debug("Processing trade with id: " + id + " symbol: " + symbol); }
SLF4J:
logger.debug("Processing trade with id: {} and symbol : {} ", id, symbol);
在SLF4J,我們不需要字串連線而且不會導致暫時不需要的字串消耗。而是以一個以佔位符和以引數傳遞實際值的模板格式下寫日誌資訊 。你可能會在想萬一我有很多個引數怎麼辦?那麼你可以選擇使用變數引數版本的日誌方法或者用以Object陣列傳遞。這是一個相當的方便和高效方法的打日誌方法。記住,在生產最終日誌資訊的字串之前,這個方法會檢查一個特定的日誌級別是不是打開了,這不僅降低了記憶體消耗而且預先降低了CPU去處理字串連線命令的時間。這裡是使用SLF4J日誌方法的程式碼,來自於slf4j-log4j12-1.6.1.jar中的Log4j的介面卡類Log4jLoggerAdapter。
public void debug(String format, Object arg1, Object arg2) { if (logger.isDebugEnabled()) { FormattingTuple ft = MessageFormatter.format(format, arg1, arg2); logger.log(FQCN, Level.DEBUG, ft.getMessage(), ft.getThrowable()); } }
三、原因總結
- 在你的開源或內部類庫中使用SLF4J會使得它獨立於任何一個特定的日誌實現,這意味著不需要管理多個日誌配置或者多個日誌類庫,你的客戶端會很感激這點。
- SLF4J提供了基於佔位符的日誌方法,這通過去除檢查isDebugEnabled(), isInfoEnabled()等等,提高了程式碼可讀性。
- 通過使用SLF4J的日誌方法,你可以延遲構建日誌資訊(Srting)的開銷,直到你真正需要,這對於記憶體和CPU都是高效的。
- 作為附註,更少的暫時的字串意味著垃圾回收器(Garbage Collector)需要做更好的工作,這意味著你的應用程式有為更好的吞吐量和效能。
再次強調,在java中任何新的程式碼開發,都應使用SLF4J而不是任何的日誌API,包括log4J。
四、slf4j與log4j常見衝突
4.1. 問題一
描述
java.lang.IllegalAccessError: tried to access field org.slf4j.impl.Static.. java.lang.IllegalAccessError: tried to access field org.slf4j.impl.StaticLoggerBinder.SINGLETON from class org.slf4j.LoggerFactory
問題原因:jar檔案版本衝突
類 org.slf4j.impl.StaticLoggerBinder在slf4j-api 中是類的公有靜態變數:
public static final StaticLoggerBinder SINGLETON = new StaticLoggerBinder();
而在slf4j-log4j12(slf4j-nop.jar, slf4j-simple.jar, slf4j-log4j12.jar, slf4j-jdk14.jar or logback-classic.jar其中之一)中確是私有變數:
private static final StaticLoggerBinder SINGLETON = new StaticLoggerBinder();
解決方案:
-
修改slf的原始碼,將這個變數有私有改為公有,再打包,問題可解決。
-
slf4j-api.jar 刪除,再匯入同版本的slf4j-api-1.5.6.jar 和slf4j-log4j12-1.5.6.jar ,問題可解決。
4.2 問題二
問題描述:
log4j:WARN No appenders could be found for logger (xxx.yyy.zzz). log4j:WARN Please initialize the log4j system properly.
問題解決:
在src下面新建file名為log4j.properties內容如下:
# Configure logging for testing: optionally with log file log4j.rootLogger=WARN, stdout # log4j.rootLogger=WARN, stdout, logfile log4j.appender.stdout=org.apache.log4j.ConsoleAppender log4j.appender.stdout.layout=org.apache.log4j.PatternLayout log4j.appender.stdout.layout.ConversionPattern=%d %p [%c] - %m%n log4j.appender.logfile=org.apache.log4j.FileAppender log4j.appender.logfile.File=target/spring.log log4j.appender.logfile.layout=org.apache.log4j.PatternLayout log4j.appender.logfile.layout.ConversionPattern=%d %p [%c] - %m%n
其他情形下的問題解決:
在Eclipse中開發相關專案時,在控制檯經常看到如下資訊:
log4j:WARN No appenders could be found for logger log4j:WARN Please initialize the log4j system properly.
此處輸出資訊並不是錯誤資訊而僅只是警告資訊,因為log4j無法輸出日誌,log4j是一個日誌輸入軟體包。可以將Struts或Hibernate等壓縮包解壓,內有log4j.properties檔案,將它複製到專案src資料夾或將log4j.properties放到 \WEB-INF\classes資料夾中即可。
4.3 問題三
問題描述
log4j:WARN No appenders could be found for logger (org.springframework.web.context.ContextLoader). log4j:WARN Please initialize the log4j system properly.
問題解決
在網上查了一下,多是說把ContextLoaderListener改為SpringContextServlet,但我這樣改了沒用。後來在一個英文網站上看到一個遇到同樣問題的帖子,他是這樣改的:
<context-param> <param-name>log4jConfigLocation</param-name> <param-value>/WEB-INF/config/log4j.properties</param-value> </context-param>
<!-- 定義LOG4J監聽器 --> <listener> <listener-class> org.springframework.web.util.Log4jConfigListener </listener-class> </listener>
這樣改了問題就解決了,不用再修改ContextLoaderListener。