1. 程式人生 > >java日誌和SLF4J隨想

java日誌和SLF4J隨想

原文地址 譯者:劉小劉

本文漫談java中的日誌:以前怎樣使用日誌,以及類似SLF4J的庫為我們帶來了什麼。

日誌是建立軟體時的基本需求之一,常見的用例如:

  • 軟體開發過程中的除錯
  • 生產環境下診斷bug
  • 出於安全目的而跟蹤訪問
  • 建立統計使用的資料
  • 等等


無論用途為何,日誌都應該是詳盡、可配置和可靠的。
歷史
在早期,java日誌使用System.out.println(), System.err.println() 或 e.printStackTrace()。除錯資訊輸出到標準輸出System.out,錯誤資訊輸出到標準錯誤System.err。在生產環境中,二者都被重定向:前者重定向到null(注:不輸出),後者重定向到需要的日誌檔案。這種做法夠用但有很大的缺點:不可配置。它是個是或否的開關,要麼全記錄要麼完全不,不能在某一層或某個包上關注具體的日誌。

Log4J充當了救星,它滿足了人們對日誌框架的幾乎所有需求。它引入了許多日誌框架中今天仍在使用的概念(它是我首個使用的框架所以請原諒我若某個概念其實不是它發明的):

  • Logger的概念,每個logger可以單獨配置
  • Appender的概念,每個appender可以將日誌輸出到它想要的任何地方(檔案、資料庫、訊息等等)
  • Level的概念,開發人員可以單獨配置是否輸出每條日誌

之後,Sun意識到需要在JDK中提供日誌特性,它沒有直接使用Log4J,而是模仿Log4J建立了自己的API。然而,新API的完成度不及Log4J。如果你想使用JDK1.4的日誌API,你可能必須建立自己的Appender-在java API中叫Handler-因為能直接用的日誌目的地只有控制檯和檔案。

使用這些框架都需要多份配置,因為不管你選擇哪個,至少有一個你的依賴會使用另一個。Apache Commons Logging是一個將它自己與日誌框架連線的API橋樑。庫應該呼叫commons-logging,這樣庫使用的實際框架和你的工程是相同的,而不是它強制使用的。現實不總是這樣,所以Commons Logging沒有解決雙重配置問題。此外,Commons Logging還會遇到類載入的問題,導致NoClassDefFoundError報錯。

最後,Log4J核心的開發者由於此處不便細說的原因退出了Log4J工程。他建立了另一個日誌框架,這個本應成為Log4J第二版的日誌框架被命名為SLF4J。

一些奇怪的事實

以下是前述框架困擾我的一些事實,它們不一定都是缺陷但值得指出來:

  • Log4J通過maven強制依賴JMS, Mail和JMX,意味著如果你不嫌麻煩特意排除它們那這些就會出現在你工程的類路徑中。
  • 類似地,Commons Logging通過maven強制依賴Avalon(另一個日誌框架),Log4J, LogKit和Servlet API(!)
  • Log4J中始終包含一個Swing日誌檢視器,即使它是用在無需展現的環境中,例如批處理或應用伺服器。
  • Log4J 1.3版的主頁重定向到1.2版,而2.0版還在實驗室階段。

選擇哪個框架?

Log4J是可以選擇的框架(對大多數)但它不再開發了。1.2版是參考,1.3版廢棄了而2.0還處在它的早期階段。
Commons Logging是作為庫的好選擇(相比應用),但我無法忍受類載入器的問題,一次就夠了(最後,我拋開Commons Logging直接用了Log4J).
JDK1.4日誌是標準而且不存在多版本共用的問題,但它缺少太多特性,如果不二次開發如資料庫介面卡或其它就不能使用。太糟了。。。但仍未回答這個問題:選擇哪個框架?

最近,我公司的架構師決定使用SLF4J,為什麼會選擇它?

SLF4J
SLF4J不及Log4J使用普遍,因為許多架構師和開發者熟悉Log4J而不知道SLF4J,或不關注SLF4J而堅持使用Log4J。此外,Log4J滿足了許多工程的所有日誌需求。不過,有趣的是,Hibernate使用了SLF4J。它擁有一些Log4J沒有的美好特性。

簡單的語法

看這個Log4J示例:
Logger.debug("Hello " + name);
由於字串拼接的問題(注:上述語句會先拼接字串,再根據當前級別是否低於debug決定是否輸出本條日誌,即使不輸出日誌,字串拼接操作也會執行),許多公司強制使用下面的語句,這樣只有當前處於DEBUG級別時才會執行字串拼接:
if (logger.isDebugEnabled()) {

LOGGER.debug(“Hello ” + name);
}

它避免了字串拼接問題,但有點太繁瑣了是不是?相對地,SLF4J提供下面這樣簡單的語法:
LOGGER.debug("Hello {}", name);
它的形式類似第一條示例,而又沒有字串拼接問題,也不像第二條那樣繁瑣。

SLF4J API和實現
此外,SLF4J很好地解耦了API和實現,所以你在開發環境和生產環境中使用它的API都可以極佳地適配。例如,你可以強制使用SLF4J的API,而保持生產環境中用了幾年的舊的Log4J.properties檔案。SLF4J的日誌實現是LogKit。

SLF4J橋接
SLF4J具有橋接的特性,你可以移除你的工程及其依賴元件使用的所有Log4J和commons-logging包,只使用SLF4J。
SLF4J為每一種日誌框架提供一個JAR包:它模仿其它日誌框架的API但將實現引向SLF4J的API(進而使用環境中真實的框架)。一點警告:小心不要讓classpath中同時出現同一種日誌的橋接庫和實現庫,否則會陷入迴圈。例如,使用Log4J橋接庫時,每個Log4J的API被引向SLF4J,如果SLF4J的Log4J實現同時存在,會再次引向Log4J,這樣迴圈下去。

SLF4JAPI和Log4J實現
綜合所有考慮,我的建議是使用SLF4J的API和Log4J的實現。這樣,你仍像以前一樣配置Log4J,但呼叫更簡單的SLF4J介面。要這樣做,你需要:

操作 位置 描述
加入classpath slf4j-api.jar* 主API,沒有它就無法使用SLF4J
slf4j-log4j.jar* SLF4J的Log4J實現
jul-to-slf4j.jar* 允許重定向 JDK 1.4日誌到 SLF4J
jcl-over-slf4j.jar* 重定向commons-logging呼叫到SLF4J
從classpath移除 commons-logging.jar* 會跟 jcl-over-slf4j.jar裡的commons-logging API衝突
SLF4JBridgeHandler.install()** 主應用 重定向JDK 1.4日誌呼叫到SLF4J
* Jar名可能包含版本
**只在你需要單一入口或少量呼叫

如果你的應用在應用伺服器中執行,你可能需要修改它的庫和/或配置來達成以上修改。
更多:
SLF4J專案
Log4J專案
Commons Logging專案
Commons Logging類載入問題