前言
SparkStreaming用久了,打算學(xué)習(xí)一下Flink,就從官網(wǎng)下載了Flink 1.11,打算搞一個客戶端,將程序提交在yarn上。因?yàn)镕link從1.7之后就不再提供Hadoop的依賴,所以很多依賴就要自己下載,于是各種ClassNotFoundException,其中以log*.class為首的格外猖狂,可能是因?yàn)閒link和Hadoop的日志實(shí)現(xiàn)有點(diǎn)區(qū)別,就一直哐哐哐報錯,slf4j、log4j、logback各種jar包十幾個,百度好久也沒搞清各個jar有什么區(qū)別,用在何處,就打算自己總結(jié)一下。
log發(fā)展歷史
Long long Ago,和我剛學(xué)Java的時候一樣,都是用System.out.println來看看程序輸出的結(jié)果是否符合自己的預(yù)期。后來發(fā)現(xiàn)這種方式真的有點(diǎn)low,從JDK1.4開始提供java.until.logging,后來大佬發(fā)現(xiàn)JUL太難用了,就自己手?jǐn)]了個log4j,后來log4j發(fā)現(xiàn)安全漏洞,加上代碼結(jié)構(gòu)問題難以維護(hù),于是從1.2就停止更新log4j,并又重新手?jǐn)]了個log4j2,后來這個大佬手?jǐn)]了一個性能更高、功能更全的logback,從此,這個大佬構(gòu)建了log的世界,也創(chuàng)造了最常見的日志框架:log4j、log4j2、logback。
SLF4J( Simple Logging Facade for Java )
目前已經(jīng)提及的四個日志框架,如果我們想用來記錄日志的話,需要完成它們必要的配置,并且在代碼中獲取Logger,打印日志。
引入代碼如下:
// 使用log4j,需要log4j.jar
import org.apache.log4j.Logger;
Logger logger_log4j = Logger.getLogger(Test.class);
logger_log4j.info("Hello World!");
// 使用log4j2,需要log4j-api.jar、log4j-core.jar
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
Logger logger_log4j2 = LogManager.getLogger(Test.class);
logger_log4j2.info("Hello World!");
// logback,需要logback-classic.jar、logback-core.jar
import ch.qos.logback.classic.Logger;
import ch.qos.logback.classic.LoggerContext;
Logger logger_logback = new LoggerContext().getLogger(Test.class);
logger_logback.info("Hello World!");
// java.until.logging,簡稱jul
import java.util.logging.Logger;
Logger logger_jul = Logger.getLogger("java.Test");
從上面不難看出,使用不同的日志框架,就要引入不同的jar包,使用不同的代碼獲取Logger。假設(shè)一個項目在漫長的升級過程中,想從jul升級到logback,還得需要修改代碼。如果100個class中使用了jul,就得修改100個地方,這是多么一個繁瑣的工作。于是Apache Commons Logging出現(xiàn)了。
Common-logging提供了一個日志入口,稱作"門面日志",即它不負(fù)責(zé)寫日志,而是提供用一個統(tǒng)一的接口,通過jar來決定使用的日志框架,這樣就不要再更換框架的時候再修改代碼了。后來開發(fā)了log4j的大佬在嫌棄Common-logging難用之后又開發(fā)了同樣功能的slf4j,今天就拿slf4j講述門面日志。
門面日志和設(shè)計模式中的外觀模式如出一轍,就是為子系統(tǒng)提供統(tǒng)一的入口,封裝子系統(tǒng)的復(fù)雜性,便于客戶端調(diào)用。slf4j就像是菜鳥驛站,本身沒有快遞服務(wù),但是提供順豐、中通等快遞服務(wù),至于你想用順豐還是用中通,完全取決于你的想法。
使用slf4j的代碼如下:
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
Logger logger = LoggerFactory.getLogger(Test.class);
logger.info("Hello World!")
這行代碼就像是你拿了個要寄出的東西(logger)在菜鳥驛站里, 思考到底用哪家快遞?A minute later...決定用順豐(logback),就填了順豐的快遞單(放入logback.jar),但是你看微信余額還有10塊,還是用中通(log4j)吧,于是你就退了順豐的單子(移除logback.jar),填了中通的快遞單(放入log4j.jar)。
那么slf4j如何決定使用哪個框架日志呢?如slf4j官方圖所示:

這就是slf4j和其他框架的組合,使用slf4j需要首先導(dǎo)入slf4j-api.jar,和log4j配合,你就要導(dǎo)入log4j.jar,以及他們之間的橋接包slf4j-log412.jar。這個官方圖美中不足的地方是,沒有l(wèi)og4j2的配合方式,和log4j2配合需要導(dǎo)入橋接包log4j-slf4j-impl.jar和log4j2的log4j-api.jar、log4j-core.jar。logback只需要導(dǎo)入logback-classic和logback-core.jar即可,不需要橋接包。
什么是橋接包,為什么logback沒有
先讓來讓我們看看slf4j從LoggerFactory.getLogger()開始,到底干了什么。
流程圖如下:

原理就是就是讓ClassLoader從classpath(依賴的jar)中找到StaticLoggerBinder這個類,然后利用他來返回log4j、logback等Logger,然后實(shí)現(xiàn)打印日志。
所謂的橋接包,就是實(shí)現(xiàn)了StaticLoggerBinder類來連接slf4j和日志框架。因?yàn)閘og4j和log4j2剛開始沒有StaticLoggerBinder這個類,為了不改變程序結(jié)構(gòu),于是寫了一個新的包來實(shí)現(xiàn)StaticLoggerBinder。而logback出現(xiàn)slf4j之后,所以就在logback中實(shí)現(xiàn)了StaticLoggerBinder,所以就不需要橋接包。
StaticLoggerBinder大致實(shí)現(xiàn)了本文第一段代碼里面的功能,各自的StaticLoggerBinder為slf4j提供各自的Logger,再提供給用戶。
log4j和log4j2橋接包及l(fā)ogback結(jié)構(gòu)如圖所示,里面都有StaticLoggerBinder類。



使用總結(jié)
"Class path contains multiple SLF4J bindings."
在使用slf4j的時候會遇到以上的報告信息。我也曾遇到過web服務(wù)因?yàn)閟lf4j問題啟動失敗。究其根本是因?yàn)閘ogback-classic、log4j-slf4j-impl、slf4j-log412、slf4j-jdk這些jar不能同時存在。他們都實(shí)現(xiàn)了StaticLoggerBinder類而導(dǎo)致沖突,slf4j無法確定到底用哪個日志框架。
結(jié)語
這是自己從官網(wǎng)上和自己調(diào)試的代碼的一些總結(jié),希望對大家了解slf4j及其應(yīng)用有幫助,若發(fā)現(xiàn)其中不足的地方,還望指出,共同進(jìn)步。共勉!??!
本文由博客群發(fā)一文多發(fā)等運(yùn)營工具平臺 OpenWrite 發(fā)布