我們前面已經(jīng)講過了 SLF4J 的兩種用法:SLF4J+Log4J 和 SLF4J+Logback,那是在比較理想的情況下,所用組件只使用了 SLF4J 這一種統(tǒng)一日志框架的時(shí)候??墒?JCL 一直影響深遠(yuǎn),SLF4J 漸入佳境的時(shí)個(gè),在你的項(xiàng)目中很可能所用的組件,它們分別用了 JCL 和 SLF4J 兩種組件。比如說在項(xiàng)目中用了 Hibernate 3.5 和 Struts,或其他 Apache 的一些開源組件,你大約也不想用了 SLF4J 的組件日志信息輸出到 A 處,用了 JCL 的組件日志輸出到 B 處,那你自己寫的代碼中的日志信息該往哪兒寫呢?
中國人一直都在追求大一統(tǒng),不喜歡城邦制而便于分而制之。但說到日志輸出還是得統(tǒng)一到單一通道中,一方面多個(gè)通道浪費(fèi)資源,另方面也便于配置和管理。那么既然 SLF4J 是趨勢,當(dāng) SLF4J 和 JCL 被丟到一個(gè)壇子里,首先會讓 SLF4J 為主,JCL 為輔,也就是要把 JCL 橋接到 SLF4J 上來,通過 SLF4J 統(tǒng)一輸出日志信息。于是也就是這篇要介紹的 SLF4J 使用模式:JCL-Over-SLF4J+SLF4J。
從前面對 SLF4J 的認(rèn)識可知,即使把 JCL 轉(zhuǎn)嫁到 SLF4J,還是無法輸出日志,還需要一種日志實(shí)現(xiàn),下層該用 Log4J 還得用 Log4J,想用 Logback 還是要用 Logback。所以到了 SLF4J 后還得往下走,也就是前面那兩條路 SLF4J+Log4J 和 SLF4J+Logback,本篇使用 SLF4J 的模式具體就要分為:
JCL-Over-SLF4J+ SLF4J+Log4J 和 JCL-Over-SLF4J+ SLF4J+Logback,這兩種實(shí)現(xiàn)方式差不多。只是分別用的 jar 包和配置文件不同,SLF4J+Log4J 和 SLF4J+Logback 原來要哪些文件現(xiàn)在還是需要那些文件,只是都要加上 jcl-over-slf4j-1.5.11.jar 包。這里說明 JCL-Over-SLF4J+ SLF4J+Logback 的方式。
需要的配置文件和組件包,下面四個(gè) jar 文件和一個(gè) xml文件都是要放在項(xiàng)目的 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 個(gè)包在 http://www./download.html 處下載,第二第三個(gè)包在 http://logback./download.html 下載,可能包文件名中的版本號有些差異。
下面是一個(gè)最簡單的 logback.xml 文件內(nèi)容
<?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],來檢驗(yàn)是否統(tǒng)一到單一的日志通道中去了。
使用 了 JCL 和 SLF4J 的代碼
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 輸出日志,從輸出可以看到它們統(tǒng)一到一個(gè)通道中了
public static void main(String[] args) {
logger.info("Hello {}","From SLF4J");
log.info("Hello From JCL");
}
}
我們在上面代碼中,既使用了 JCL 統(tǒng)一日志框架,也使用了 SLF4J 的統(tǒng)一日志框架。要注意一點(diǎn),從 JCL 橋接過來的 log 不能輸出參數(shù)化消息了。上面代碼使用了 org.apache.commons.logging.Log,import org.apache.commons.logging.LogFactory,但你卻用不著引入 commons-logging.jar 包。
執(zhí)行上面的代碼,看到輸出:
[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 框架的日志輸出都統(tǒng)一到了一個(gè)通道中來了,為什么呢? SLF4J 使用的是 Logback 輸出的信息,這一點(diǎn)沒問題的,而 JCL 是不認(rèn)識 Logback 的,所以 JCL 框架的輸出必然是繞道到 SLF4J,最后也是由 Logback 輸出的。
實(shí)現(xiàn)分析: 我們打開 jcl-over-slf4j-1.5.11.jar,看到里面有兩個(gè)包 org.apache.commons.logging 和 org.apache.commons.logging.impl,并有相應(yīng)的類,這就是為什么,雖然在代碼中有:
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
卻不用把 commons-logging.jar 包引入到類路徑上的原因。
再深入下 jcl-over-slf4j-1.5.11.jar,看到其中還有個(gè)文件 /META-INF/services/org.apache.commons.logging.LogFactory,內(nèi)容為:
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./j2se/1.3/docs/guide/jar/jar.html#Service Provider
JCL 運(yùn)行時(shí)使用了 SLF4JLogFactory,從而完成了 JCL 的日志實(shí)現(xiàn)委托給了 SLF4J,再由 SLF4J 進(jìn)一步完成具體的日志輸出。
采用 JCL-Over-SLF4J+ SLF4J+Log4J 使用模式也是相似的,這里就不詳述了??偨Y(jié)下就是 JCL 把 SLF4J 當(dāng)作它的日志實(shí)現(xiàn)。
再來想象個(gè)問題:如果我們把這兩個(gè)包 jcl-over-slf4j-1.5.11.jar 和 slf4j-jcl-1.5.11.jar 都放到 ClassPath 下會有什么情況呢?JCL 代理給 SLF4J,SLF4J 又綁定到 JCL,對了,死循環(huán),StackOverFlow 錯(cuò)誤:
SLF4J: Detected both jcl-over-slf4j.jar AND slf4j-jcl.jar on the class path, preempting StackOverflowError.
SLF4J: See also http://www./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 also http://www./codes.html#jclDelegationLoop for more details.
at org.slf4j.impl.JCLLoggerFactory.<clinit>(JCLLoggerFactory.java:64)
... 8 more
Exception in thread "main"