天天看點

[轉]JCL-Over-SLF4J+SLF4J+Logback 使用方式

我們前面已經講過了 SLF4J 的兩種用法:SLF4J+Log4J  和 SLF4J+Logback,那是在比較理想的情況下,所用元件隻使用了 SLF4J 這一種統一日志架構的時候。可是 JCL 一直影響深遠,SLF4J 漸入佳境的時個,在你的項目中很可能所用的元件,它們分别用了 JCL 和 SLF4J 兩種元件。比如說在項目中用了 Hibernate 3.5 和 Struts,或其他 Apache 的一些開源元件,你大約也不想用了 SLF4J 的元件日志資訊輸出到 A 處,用了 JCL 的元件日志輸出到 B 處,那你自己寫的代碼中的日志資訊該往哪兒寫呢?

中國人一直都願追求大一統,不喜歡城邦制的便于分而治之。但說到日志輸出還是得統一到單一通道中來,一方面多個通道浪費資源,另方面也便于配置和管理。那麼既然 SLF4J 是趨勢,當 SLF4J 和 JCL 被丢到一個壇子裡,首先會讓 SLF4J 為主,JCL 為輔,也就是要把 JCL 橋接到 SLF4J 上來,通過 SLF4J 統一輸出日志資訊。于是也就是這篇要介紹的 SLF4J 使用模式:JCL-Over-SLF4J+SLF4J。

從前面對 SLF4J 的認識可知,即使把 JCL 轉嫁到 SLF4J,還是無法輸出日志,還需要一種日志實作,下層該用 Log4J 還得用 Log4J,想用 Logback 還是要用 Logback。是以到了 SLF4J 後還得往下走,也就是前面那兩條路 SLF4J+Log4J 和 SLF4J+Logback,本篇使用 SLF4J 的模式具體就要分為:

JCL-Over-SLF4J+SLF4J+Log4J 和 JCL-Over-SLF4J+SLF4J+Logback,這兩種實作方式差不多。隻是分别用的 jar 包和配置檔案不同,SLF4J+Log4J 和 SLF4J+Logback 原來要哪些檔案現在還是需要那些檔案,隻是都要加上 jcl-over-slf4j-1.5.11.jar 包。這裡說明 JCL-Over-SLF4J+SLF4J+Logback 的方式。

需要的配置檔案群組件包,下面四個 jar 檔案和一個 xml檔案都是要放在項目的 ClassPath 上。

1. slf4j-api-1.5.11.jar

2. logback-core-0.9.20.jar

3. logback-classic-0.9.20.jar

4. logback.xml 或 logback-test.xml

5. jcl-over-slf4j-1.5.11.jar

第 1 和第 5 個包在 http://www.slf4j.org/download.html 處下載下傳,第二第三個包在 http://logback.qos.ch/download.html 下載下傳,可能封包件名中的版本号有些差異。

下面是一個最簡單的 logback.xml 檔案内容

01 02 03 04 05 06 07 08 09 10 11 12

<?

xml

version

=

"1.0"

encoding

=

"UTF-8"

?>

<

configuration

>

<

appender

name

=

"stdout"

class

=

"ch.qos.logback.core.ConsoleAppender"

>

<

encoder

charset

=

"GBK"

>

<

pattern

>[Consociate] %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</

pattern

>

</

encoder

>

</

appender

<

root

level

=

"DEBUG"

>

<

appender-ref

ref

=

"stdout"

/>

</

root

>

</

configuration

>

為了看看效果,我們在輸入的 pattern 中加入了 [Consociate],來檢驗是否統一到單一的日志通道中去了。

使用 了 JCL 和 SLF4J  的代碼

01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20

package

com.unmi;

import

org.apache.commons.logging.Log;

import

org.apache.commons.logging.LogFactory;

import

org.slf4j.Logger;

import

org.slf4j.LoggerFactory;

public

class

TestJCLOverSlf4j {

//SLF4J 的 Logger

private

static

final

Logger logger = LoggerFactory.getLogger(

"From SLF4J"

);

//JCL 的 Log

private

static

final

Log log = LogFactory.getLog(

"From JCL"

);

//分别用上面的 logger 和 log 輸出日志,從輸出可以看到它們統一到一個通道中了

public

static

void

main(String[] args) {

logger.info(

"Hello {}"

,

"From SLF4J"

);

log.info(

"Hello From JCL"

);

}

}

我們在上面代碼中,既使用了 JCL 統一日志架構,也使用了 SLF4J 的統一日志架構。要注意一點,從 JCL 橋接過來的 log 不能輸出參數化消息了。上面代碼使用了 org.apache.commons.logging.Log,import org.apache.commons.logging.LogFactory,但你卻用不着引入 commons-logging.jar 包。

執行上面的代碼,看到輸出:

本文原始連結 http://unmi.cc/jcl-over-slf4j-slf4j/, 來自 隔葉黃莺 Unmi Blog

[Consociate] 23:19:39.890 [main] INFO  From SLF4J - Hello From SLF4J

[Consociate] 23:19:39.921 [main] INFO  From JCL - Hello From JCL

很明顯示 JCL 架構和 SLF4J 架構的日志輸出都統一到了一個通道中來了,為什麼呢? SLF4J 使用的是 Logback 輸出的資訊,這一點沒問題的,而 JCL 是不認識 Logback 的,是以 JCL 架構的輸出必然是繞道到 SLF4J,最後也是由 Logback 輸出的。

實作分析:

我們打開 jcl-over-slf4j-1.5.11.jar,看到裡面有兩個包 org.apache.commons.logging 和 org.apache.commons.logging.impl,并有相應的類,這就是為什麼,雖然在代碼中有:

import org.apache.commons.logging.Log;

import org.apache.commons.logging.LogFactory;

卻不用把 commons-logging.jar 包引入到類路徑上的原因。

再深入下 jcl-over-slf4j-1.5.11.jar,看到其中還有個檔案 /META-INF/services/org.apache.commons.logging.LogFactory,内容為:

org.apache.commons.logging.impl.SLF4JLogFactory

# Axis gets at JCL through its own mechanism as defined by Commons Discovery, which

# in turn follows the instructions found at:

# http://java.sun.com/j2se/1.3/docs/guide/jar/jar.html#Service Provider

JCL 運作時使用了 SLF4JLogFactory,進而完成了 JCL 的日志實作委托給了 SLF4J,再由 SLF4J 進一步完成具體的日志輸出。

采用 JCL-Over-SLF4J+SLF4J+Log4J 使用模式也是相似的,這裡就不詳述了。總結下就是 JCL 把 SLF4J 當作它的日志實作。

再來想象個問題:如果我們把這兩個包 jcl-over-slf4j-1.5.11.jar 和 slf4j-jcl-1.5.11.jar 都放到 ClassPath 下會有什麼情況呢?JCL 代理給 SLF4J,SLF4J 又綁定到 JCL,對了,死循環,StackOverFlow 錯誤:

SLF4J: Detected both jcl-over-slf4j.jar AND slf4j-jcl.jar on the class path, preempting StackOverflowError.

SLF4J: See also http://www.slf4j.org/codes.html#jclDelegationLoop for more details.

java.lang.ExceptionInInitializerError

at org.slf4j.impl.StaticLoggerBinder.<init>(StaticLoggerBinder.java:82)

at org.slf4j.impl.StaticLoggerBinder.<clinit>(StaticLoggerBinder.java:51)

at org.slf4j.LoggerFactory.getSingleton(LoggerFactory.java:230)

at org.slf4j.LoggerFactory.bind(LoggerFactory.java:121)

at org.slf4j.LoggerFactory.performInitialization(LoggerFactory.java:112)

at org.slf4j.LoggerFactory.getILoggerFactory(LoggerFactory.java:275)

at org.slf4j.LoggerFactory.getLogger(LoggerFactory.java:248)

at com.unmi.TestJCLOverSlf4j.<clinit>(TestJCLOverSlf4j.java:10)

Caused by: java.lang.IllegalStateException: Detected both jcl-over-slf4j.jar AND slf4j-jcl.jar on the class path, preempting StackOverflowError. See alsohttp://www.slf4j.org/codes.html#jclDelegationLoop for more details.

at org.slf4j.impl.JCLLoggerFactory.<clinit>(JCLLoggerFactory.java:64)

... 8 more

Exception in thread "main"

繼續閱讀