天天看點

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示例:

<code>logger.debug("hello " + name);</code>

由于字元串拼接的問題(注:上述語句會先拼接字元串,再根據目前級别是否低于debug決定是否輸出本條日志,即使不輸出日志,字元串拼接操作也會執行),許多公司強制使用下面的語句,這樣隻有目前處于debug級别時才會執行字元串拼接:

<code>if (logger.isdebugenabled()) {</code>

logger.debug(“hello ” + name);

}

它避免了字元串拼接問題,但有點太繁瑣了是不是?相對地,slf4j提供下面這樣簡單的文法:

<code>logger.debug("hello {}", name);</code>

它的形式類似第一條示例,而又沒有字元串拼接問題,也不像第二條那樣繁瑣。

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沖突

<code>slf4jbridgehandler.install()</code>**

主應用

重定向jdk 1.4日志調用到slf4j

* jar名可能包含版本

**隻在你需要單一入口或少量調用

如果你的應用在應用伺服器中運作,你可能需要修改它的庫和/或配置來達成以上修改。

更多:

<a href="http://articles.qos.ch/classloader.html">commons logging類加載問題</a>