稍微介紹一下 java 處理 log 的東西
特別是 slf4j 的種類太多,或許也沒有人能很好的說明各自用處。產(chǎn)生混亂的原因有七成是由 slf4j 造成的。這種混亂的局面進(jìn)行了簡(jiǎn)單整理。主要分為三個(gè)類型的 jar。
1、 接口(Interface)
主要有這些
- commons-logging
- slf4j
他們也就只提供了接口的功能,并沒實(shí)現(xiàn) log 的輸出功能。
接口要是太老的話,自己提供的功能也會(huì)有不足的地方。這些 jar 雖然含有有 log 輸出的功能,但只是簡(jiǎn)單的實(shí)現(xiàn)。
2、 適配器(Adapter)
主要有這些
- jcl-over-slf4j.XXX.jar(把 commons-logging 的處理交給 slf4j 來處理)
- jul-to-slf4j.XXX.jar(把 java.util.logging 的處理交給 slf4j 來處理)
- log4j-over-slf4j.XXX.jar(把 log4j 的處理交給 slf4j 來處理)
適配器的作用是,代理各接口與實(shí)際處理 logger 的任務(wù)。從外部看起來都是相同的方法,但是內(nèi)部卻是各自來實(shí)現(xiàn)自己的功能。除了處理 log 之外,也有『slf4j-jdk14』這樣的兼容不同 JDK 版本來處理的適配器。正因?yàn)橛辛诉m配器,即使在 commons-logging、log4j 中追加了 slf4j 的時(shí)候也能進(jìn)行工作。
適配器通過設(shè)置文件來進(jìn)行適配,log4j.jar 不存在的時(shí)候會(huì)讀取 log4j.xml 文件。適配器是很認(rèn)真的來工作的。但是因?yàn)橛辛诉m配器來進(jìn)行各種處理,這也是造成了 jar 文件混亂而使得 log 的輸出復(fù)雜的原因。
另外,「jul」是「java.util.logging」の簡(jiǎn)稱,「jcl」是「Jakarta Commons Logging」的簡(jiǎn)稱而不是「Java Class Library」的簡(jiǎn)稱。這些簡(jiǎn)稱的理解困難也是造成 log 的輸出復(fù)雜的原因之一。
3、 實(shí)現(xiàn)
主要有這些
- java.util.logging
- log4j
- logback
logback 是 log4j 的開發(fā)者開發(fā)出來的下一代 log 處理。通過對(duì)接口的實(shí)現(xiàn),如果做得不好的話會(huì)造成處理速度變慢,或者就沒有對(duì)接口提供的方法進(jìn)行處理。
slf4j 適配器
- slf4j-log4j12-XXXX.jar(把 slf4j 的處理交給 log4j1.2 來處理)
- slf4j-jdk14-XXX.jar(在jdk1.4中,把 slf4j 的處理交給 java.util.logging 來處理)
- slf4j-nop-XXX.jar(全部的 log 內(nèi)容都不輸出)
- slf4j-simple-XXX.jar(log 等級(jí)為 INFO 以上的內(nèi)容輸出到 System.err 中)
- slf4j-log4j-XXX.jar(把 slf4j 的處理交給 log4j 來處理)
- slf4j-jcl-XXX.jar(把 slf4j 的處理交給 commons.logging 來處理)
使用『slf4j』的時(shí)候被稱為適配器的 jar 有很多個(gè),這是為了使用 slf4j 的時(shí)候選擇不同的實(shí)現(xiàn)方法,因此在 classpath 中不能含有多個(gè)。如果有多個(gè)適配器的時(shí)候,slf4j 無法知道該交給那個(gè)適配器來進(jìn)行處理。
而使用 slf4j + logback 的時(shí)候是不需要適配器的,只需要用 slf4j-api。
[圖片上傳失敗...(image-51e5ae-1533264514580)]
這是 slf4j 官網(wǎng)的圖釋,然而完全不知道什么是什么。。。
使用 slf4j + logback
接下來讓我們?cè)囍鴱摹篶ommons-logging + log4j』變到『slf4j + logback』。手動(dòng)管理 jar 有點(diǎn)困難,下面以使用 maven 來進(jìn)行講解。
添加 slf4j 與 logbak 的依賴
在 pom.xml 中添加下面兩個(gè)依賴
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.7.25</version>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.2.3</version>
</dependency>
去除 commons-logging 與 log4j 的依賴
這個(gè)處理實(shí)在是很麻煩,無法在 maven 中簡(jiǎn)單的就去掉,需要對(duì)每個(gè) jar 進(jìn)行去除的處理。這是因?yàn)橛泻芏?jar 包中依賴了這兩個(gè)東西。如果是使用 jdk1.5 以后的話 slf4j-jdk14 也是可以一起刪除掉的。
<exclusions>
<exclusion>
<artifactId>commons-logging</artifactId>
<groupId>commons-logging</groupId>
</exclusion>
</exclusions>
<exclusions>
<exclusion>
<artifactId>log4j</artifactId>
<groupId>log4j</groupId>
</exclusion>
</exclusions>
<exclusions>
<exclusion>
<artifactId>slf4j-jdk14</artifactId>
<groupId>org.slf4j</groupId>
</exclusion>
</exclusions>
這個(gè)處理使用 eclipse 的 m2e 插件的話可以比較簡(jiǎn)單的進(jìn)行。打開 pom.xml 文件,再打開 Dependency Hierarchy 標(biāo)簽,在 Filter 里輸入這兩個(gè) jar 的名字的話,就會(huì)表示出全部依賴的 jar 包。接下來右鍵點(diǎn)擊 jar 包選擇「Exclude Maven Artifact...」就可以了。
最終我們需要的 jar 包如下
- slf4j-api-XXX.jar
- logback-classic-XXX.jar
- logback-core-XXX.jar
- jcl-over-slf4j.XXX.jar
- jul-to-slf4j.XXX.jar
- log4j-over-slf4j.XXX.jar
雖然說有點(diǎn)多,其實(shí)必要的 jar 包只有最初的三個(gè)。
替換 log 的配置文件
log4j.xml、log4j.dtd、log4j.properties 不再需要了直接刪掉。而使用的『logback.xml』文件參照下面的例子適當(dāng)修改。
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<prudent>true</prudent>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>app.%d{yyyy-MM-dd}.log</fileNamePattern>
<maxHistory>30</maxHistory>
</rollingPolicy>
<encoder>
<pattern>%date{yyyy/MM/dd HH:mm:ss:SSS} %.5level - %logger{0}.%.20method %msg%n</pattern>
</encoder>
</appender>
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<target>System.out</target>
<encoder>
<pattern>%date{yyyy/MM/dd HH:mm:ss:SSS} %.5level - %logger{0}.%.20method %msg%n</pattern>
</encoder>
</appender>
<root>
<level value="info" />
<appender-ref ref="FILE" />
<appender-ref ref="STDOUT" />
</root>
</configuration>
如果使用了舊的寫法的話tomcat啟動(dòng)的時(shí)候會(huì)出現(xiàn)警告,這時(shí)候需要參考 Logback Error Codes 來進(jìn)行修改。
從網(wǎng)上的消息來看 ch.qos.logback.classic.PatternLayout 已經(jīng)是 DEPRECATED(不推薦使用)了,修改這里就好。encoder 與 layout 沒有進(jìn)行自定義化,這兩個(gè)不指定 class 也沒關(guān)系。雖然這有點(diǎn)麻煩,但這樣就能從舊的方式變成新的方式了吧。
jar 中存在之前處理 log 時(shí)出現(xiàn)的情況的處理
即使是按照之前的方法變成了 slf4j+logback 的時(shí)候,如果還存在之前處理的log的jar包的話,會(huì)出現(xiàn)「Exception in thread "main" java.lang.NoClassDefFoundError: org/apache/commons/logging/LogFactory」這樣的問題。這時(shí)候就需要使用帥氣的適配器來處理了。
所謂的把原來的方法交出來處理,就是使用之前說的適配器『交給 slf4j 來處理』。比如依賴了 commons-logging 卻出現(xiàn)了 NoClassDefFoundError 的話,把 jcl-over-slf4j.XXX.jar 加入就行。
- jcl-over-slf4j.XXX.jar(把 commons-logging 的處理交給 slf4j 來處理)
- jul-to-slf4j.XXX.jar(把 java.util.logging 的處理交給 slf4j 來處理)
- log4j-over-slf4j.XXX.jar(把 log4j 的處理交給 slf4j 來處理)
把所有的東西交給 slf4j 來處理,之后再變成 slf4j => logback 的形式來處理的話,就能完全的進(jìn)行轉(zhuǎn)移了。
簡(jiǎn)單的使用方法
大概就是如下所示,jcl 的話也差不多。
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class Hoge {
private static Logger LOGGER = LoggerFactory.getLogger(Hoge.class);
// 「あいう」と出力されます
public static void main(String[] args) {
LOGGER.info("あ{}う", "い");
}
}
{}是占位符。從 import 來看的話只有 slf4j,logback 相關(guān)的 import 都沒有。雖然只寫了 slf4j 的代碼,然而 slf4j 會(huì)從 classpath 中找到 logback,然后使用它來實(shí)現(xiàn)。雖然表面上看不出來,但是還是的使用了 logback 來進(jìn)行處理,所以請(qǐng)各位放心。
logger 的變遷
實(shí)現(xiàn)
- log4j v1 版本的流行
- log4j 的作者重新開發(fā)出 logback
- log2j v2 版本出現(xiàn) <- 現(xiàn)在的時(shí)間點(diǎn)
是這樣的感覺,覺得 logback 已經(jīng)成熟的時(shí)候,log4j 的 version 2 就出現(xiàn)了,變成了無法解決誰是業(yè)內(nèi)標(biāo)準(zhǔn)的狀態(tài)。
接口
- commons.logging
- SLF4J
接口只有這兩個(gè)??傊?SLF4J 貌似成為了業(yè)內(nèi)標(biāo)準(zhǔn),所以使用 SLF4J 的話不會(huì)有什么問題。說到 slf4j 的適配器,或許會(huì)出現(xiàn)在今后的 JDK1.8 中像 stream、lambda 內(nèi)部使用的 logger,或者是處理不同的JDK的差異的實(shí)現(xiàn)變得更多都說不定。
從這么多 logger 想到的
接口、適配器、實(shí)現(xiàn),這些都雖說是 java 的東西,從個(gè)人來說的話 『合并成一個(gè)jar,通過設(shè)定文件來改變動(dòng)作的話就好了』。要是按照現(xiàn)在的話每當(dāng)新的 logger 出現(xiàn)的時(shí)候,又會(huì)增加適配器,從而 jar 包也會(huì)變多。
雖說各 jar 分開不發(fā)生耦合的道理也是正確的,然而從現(xiàn)狀來看的話無論是誰都覺得是混亂的狀態(tài),不理解 slf4j 而使用的話就會(huì)在 tomcat 啟動(dòng)的時(shí)候出現(xiàn)
Multiple bindings were found on the class path
這樣的錯(cuò)誤,誰都不會(huì)注意到這樣的錯(cuò)誤的項(xiàng)目有很多。
(這個(gè)時(shí)候 slf4j 會(huì)通過 /dev/null 把輸出給取消因此 slf4j 不會(huì)生效,只剩下 log4j 來進(jìn)行處理)
古人說過『自由即是不自由』,比起自由還是希望得到能注重單純的東西。如果要注重性能的話,那或許還是讓同一個(gè)公司來制作的話是最好的結(jié)果吧。
PS:這篇文章的翻譯從開坑到完成花了1年半左右的時(shí)間,終于把坑給填完了,紀(jì)念一下。