1. 程式人生 > >一文講盡門面日誌slf4j和log4j、log4j2、logback依賴jar引用關係

一文講盡門面日誌slf4j和log4j、log4j2、logback依賴jar引用關係

公眾號Mac程式碼分割閱讀連結

前言

之前都是使用SparkStreaming開發,最近打算學習一下Flink,就從官網下載了Flink 1.11,打算搞一個客戶端,將程式提交在yarn上。因為Flink從1.7之後就不再提供Hadoop的依賴,所以很多依賴就要自己下載,於是各種ClassNotFoundException,其中以log*.class為首的格外猖狂,可能是因為flink和Hadoop的日誌實現有點區別,就一直哐哐哐報錯,slf4j、log4j、logback各種jar包十幾個,百度好久也沒搞清各個jar有什麼區別,用在何處,就打算自己總結一下。

log發展歷史

Long long Ago,和剛學Java的時候一樣,都是用System.out.println控制檯列印來檢查程式輸出是否符合自己的預期,這是一種比較原始的方法,無法自動區分日誌的型別,幾乎無法用於生產系統中。

從JDK1.4開始提供java.until.logging日誌框架來列印日誌,但是大佬覺得JUL太難用了,就自己手擼了個log4j,後來log4j發現安全漏洞,加上程式碼結構問題難以維護,於是從1.2就停止更新log4j,並又重新手擼了個log4j2,再後來,這個大佬又雙手擼了一個性能更高、功能更全的logback。

從此,這個大佬構建了log的世界,也創造了最常見的日誌框架:log4j、log4j2、logback。

SLF4J( Simple Logging Facade for Java )

目前已經提及了四個日誌框架,如果我們想用來記錄日誌,除了必要的配置檔案,還需要在程式碼中獲取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。

假設一個專案在漫長的升級過程中,想從jul升級到logback,那麼就需要修改程式碼來獲取新的Logger。如果100個class中使用了jul,就得修改100個地方,這是多麼一個繁瑣的工作!!

門面系統的作用

於是Apache Commons Logging出現了。

Common-logging提供了一個日誌入口,稱作"門面日誌",即它不負責寫日誌,而是「提供用一個統一的介面,通過jar來決定使用的日誌框架」,這樣就不要再更換框架的時候再修改程式碼了。後來開發了log4j的大佬又因為嫌棄Common-logging難用,開發了門面日誌框架「slf4j」,今天就拿slf4j講述門面日誌。

門面日誌和設計模式中的外觀模式如出一轍,本身不提供服務,為子系統提供統一的入口,封裝子系統的複雜性,便於客戶端呼叫。slf4j就像是菜鳥驛站,本身沒有快遞服務,但是提供順豐、中通等快遞服務,至於你想用順豐還是用中通,完全取決於你的想法。

使用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如何決定使用哪個框架日誌呢,並且引入哪些jar包呢?

如slf4j官方圖所示:

依賴關係圖

如圖就是slf4j和日誌框架的組合依賴結構圖,使用slf4j需要首先匯入「slf4j-api.jar」,

和log4j配合,需要匯入「log4j.jar」,以及橋接包「slf4j-log412.jar」。

官方圖美中不足的是沒有log4j2依賴jar的關係,和log4j2配合需要匯入log4j2的「log4j-api.jar」、「log4j-core.jar」和橋接包「log4j-slf4j-impl.jar」。

logback只需要匯入「logback-classic.jar」和「logback-core.jar」即可,不需要橋接包。

什麼是橋接包,為什麼logback沒有

先讓來讓我們看看slf4j從LoggerFactory.getLogger()開始,到底幹了什麼。

流程圖如下:

slf4j工作流程圖

原理就是就是讓ClassLoader從classpath(依賴的jar)中找到「StaticLoggerBinder」這個類,然後利用他來返回log4j、logback中的Logger,然後列印日誌。

所謂的橋接包,就是實現StaticLoggerBinder類,用來連線slf4j和日誌框架。因為log4j和log4j2剛開始沒有StaticLoggerBinder這個類,為了不改變程式結構,只能重新寫一個新的jar來實現StaticLoggerBinder。而logback出現slf4j之後,於是在logback本身的jar中實現了StaticLoggerBinder,所以就不需要橋接包。

StaticLoggerBinder實現了使用底層日誌框架建立Logger的功能,各自的StaticLoggerBinder為slf4j提供的Logger,再提供給使用者列印日誌。

log4j和log4j2橋接包及logback依賴裡,都有StaticLoggerBinder類。

logback的StaticLoggerBinder
log4j的StaticLoggerBinder
log4j2的StaticLoggerBinder

使用總結

"Class path contains multiple SLF4J bindings."

在使用slf4j的時候會遇到以上的報告資訊。我也曾遇到過web服務因為slf4j問題啟動失敗。究其根本是因為logback-classic、log4j-slf4j-impl、slf4j-log412、slf4j-jdk這些jar不能同時存在。他們都實現了StaticLoggerBinder類而導致衝突,slf4j無法確定到底用哪個日誌框架。

結語

以上是結合官網以及自己除錯程式碼的一些總結,希望對大家瞭解slf4j及其應用有幫助,其中不足的地方還望指出,共同進步,共勉!!!



寫的都是日常工作中的親身實踐,處於自己的角度從0寫到1,保證能夠真正讓大家看懂。

文章會在公眾號 [「入門到放棄之路」] 首發,期待你的關注。

公眾號