Java日志實現(xiàn)知多少识樱?

骨灰級程序員

1.Java日志歷史

Java 擁有功能和性能都非常強大的日志庫嗤无,但不幸的是,這樣的日志庫有不止一個——相信每個Java程序員都曾經迷失在JUL(Java Util Log), JCL(Commons Logging), Log4j, SLF4J, Logback怜庸,Log4j2 等等的迷宮中当犯。讓我們回顧下講講這段“腥風血雨”的歷史。

  • Java Util Log

    來自官方JDK割疾,帶著標準和權威的光環(huán)嚎卫,小名:JUL。從JDK1.4 才開始加入(2002年)杈曲,當時各種第三方日志組件及其盛行驰凛,且JUL性能和使用的確又沒有其他組件方便。雖然JDK1.5對其進行了改進担扑,但還是不影響很多項目不選用該組件的命運恰响。

  • Log4j 1.x
    在JUL推出的前一年,Gülcü 發(fā)布了Log4j 1.x涌献,雖然進不了JDK胚宦,但是Log4j 1.x進入了Apache 基金會頂級項目。Log4j 在設計上非常優(yōu)秀燕垃,對后續(xù)的 Java Log 框架有長久而深遠的影響枢劝,也產生了Log4c, Log4s, Log4perl 等到其他語言的移植。但是卜壕,后浪推前浪您旁,前浪的性能終究還是不斷被后期的日志框架趕超,比如后期的LogbackLog4j2 轴捎。

  • JCL
    那么問題來了鹤盒,JDK官方自帶JUL,第三方有Log4J侦副,不同項目或者開源Jar侦锯,采用了不同的日志實現(xiàn)庫,那么是不是意味著要整合使用秦驯,得寫多個配置文件呢尺碰。正式這個問題,帶來了JCL的出現(xiàn)。JCL亲桥,大名:Commons Logging洛心,同樣也是Apache下的項目,但是JCL 是一個Log Facade(門面Api)两曼,只提供 Log API皂甘,不提供實現(xiàn)。在程序中日志創(chuàng)建和記錄都是用JCL中的接口悼凑,在真正運行時偿枕,然后有適配器Adapter 來使用 Log4j 或者 JUL 作為Log 實現(xiàn)。(當前ClassPath中有什么實現(xiàn)户辫,如果有Log4j 就是用 Log4j, 如果啥都沒有就是用 JDK 的 JUL)渐夸。是不是感覺從面向對象的高度,JCL有種很先進的感覺渔欢。這就是面向接口編程的體現(xiàn)墓塌。

    這樣,在你的項目中奥额,如果用Log4j, 就添加 Log4j 的jar包進去苫幢,然后寫一個 Log4j 的配置文件;如果喜歡用JUL垫挨,就只需要寫個 JUL 的配置文件韩肝。如果有其他的新的日志庫出現(xiàn),也只需要它提供一個Adapter九榔,運行的時候把這個日志庫的 jar 包加進去哀峻。


    JCL

合久必分,分久必合哲泊。歷史就是這樣剩蟀,日志組件在接下去的歷史演進中,又出現(xiàn)了跌宕起伏切威。一種平衡替換另一種平衡育特。

  • SLF4J/Logback
    Gülcü (對頭,又是ta)認為 JCL 的 API 設計得不好先朦,容易讓使用者寫出性能有問題的代碼且预,Gülcü ,不安于現(xiàn)狀烙无,不基于JCL添加實現(xiàn)類,而是創(chuàng)立了SLF4J 和 Logback項目遍尺,目的就是為了提高日志組件的性能截酷。SLF4J的全稱:Simple Logging Facade for Java,看其意思就是門面API乾戏,而Logback作為其實現(xiàn)類迂苛。當然 Logback 則是作為 Log4j 的繼承者來開發(fā)的三热,提供了性能更好的實現(xiàn),異步 logger三幻,F(xiàn)ilter等更多的特性【脱現(xiàn)在事情變復雜了。我們有了兩個流行的 Log Facade念搬,以及三個流行的 Log Implementation抑堡。


    jcl+slf4j

當你感覺現(xiàn)在差不多了吧的時候,三國時期的故事朗徊,其實又開始上演了首妖。

  • Log4j2
    維護 Log4j 的人似乎坐立不安,他們不想坐視用戶一點點被 SLF4J /Logback 蠶食爷恳,繼而搞出了 Log4j2有缆。
    Log4j2 和 Log4j1.x 并不兼容,設計上很大程度上模仿了 SLF4J/Logback温亲,性能上也獲得了很大的提升棚壁。
    Log4j2 也做了 Facade/Implementation 分離的設計,分成了 log4j-api 和 log4j-core栈虚。


    jcl+slf4j+log4f2

=========================分割線========================

Gülcü 是個追求完美的人袖外,各種紛紛擾擾的歷史看在了ta眼里,他決定讓這些Log之間都能夠方便的互相替換节芥,所以做了各種 Adapter 和 Bridge 來連接:


adpater
adpater-jar

到這里在刺,日志演進總算有所停歇。

2.Spring Boot 日志使用

2.1. 依賴分析

歷史回顧不是我們的目的头镊,結合現(xiàn)在流行的開源框架Spring Boot蚣驼,我們再來談談具體項目該如何結合實際,使用日志相艇。
構建Spring Boot Web項目颖杏,版本:2.1.4。分析下Pom.xml的依賴:


logback maven

可以發(fā)現(xiàn)坛芽,Spring Boot采用了SLF4J+Logback的組合來完成日志的記錄留储。并且作者把Log4j和JUL的日志組件適配到了slf4j。的確咙轩,Spring Boot為Java coder做了太多的工作获讳。

2.2. 日志初始化過程

以上面構建的Spring Boot項目為例,添加簡單日志記錄代碼:

@SpringBootApplication
public class SpringBootLoggerDemoApplication {

    private static Logger logger = LoggerFactory.getLogger(SpringBootLoggerDemoApplication.class);

    public static void main(String[] args) {
        SpringApplication.run(SpringBootLoggerDemoApplication.class, args);
        logger.debug("hello logger");
    }

}

LoggerFactory.java

    /**
     * Return a logger named according to the name parameter using the
     * statically bound {@link ILoggerFactory} instance.
     * 
     * @param name
     *            The name of the logger.
     * @return logger
     */
    public static Logger getLogger(String name) {
        ILoggerFactory iLoggerFactory = getILoggerFactory();
        return iLoggerFactory.getLogger(name);
    }

通過getILoggerFactory獲取日志工廠:

    /**
     * Return the {@link ILoggerFactory} instance in use.
     * <p/>
     * <p/>
     * ILoggerFactory instance is bound with this class at compile time.
     * 
     * @return the ILoggerFactory instance in use
     */
    public static ILoggerFactory getILoggerFactory() {
        if (INITIALIZATION_STATE == UNINITIALIZED) {
            synchronized (LoggerFactory.class) {
                if (INITIALIZATION_STATE == UNINITIALIZED) {
                    INITIALIZATION_STATE = ONGOING_INITIALIZATION;
                    performInitialization();
                }
            }
        }
        ...省略...
    }

這里代碼定位到performInitialization:

    private final static void performInitialization() {
        bind();
        ...省略..
    }

bind()具體日志實現(xiàn):

 private final static void bind() {
        try {
            Set<URL> staticLoggerBinderPathSet = null;
            if (!isAndroid()) {
                staticLoggerBinderPathSet = findPossibleStaticLoggerBinderPathSet();
                reportMultipleBindingAmbiguity(staticLoggerBinderPathSet);
            }
            ...省略...
        } 
    }

關鍵代碼就在findPossibleStaticLoggerBinderPathSet:

 private static String STATIC_LOGGER_BINDER_PATH = "org/slf4j/impl/StaticLoggerBinder.class";

    static Set<URL> findPossibleStaticLoggerBinderPathSet() {
        // use Set instead of list in order to deal with bug #138
        // LinkedHashSet appropriate here because it preserves insertion order
        // during iteration
        Set<URL> staticLoggerBinderPathSet = new LinkedHashSet<URL>();
        try {
            ClassLoader loggerFactoryClassLoader = LoggerFactory.class.getClassLoader();
            Enumeration<URL> paths;
            if (loggerFactoryClassLoader == null) {
                paths = ClassLoader.getSystemResources(STATIC_LOGGER_BINDER_PATH);
            } else {
                paths = loggerFactoryClassLoader.getResources(STATIC_LOGGER_BINDER_PATH);
            }
            while (paths.hasMoreElements()) {
                URL path = paths.nextElement();
                staticLoggerBinderPathSet.add(path);
            }
        } catch (IOException ioe) {
            Util.report("Error getting resources from path", ioe);
        }
        return staticLoggerBinderPathSet;
    }

代碼最終到最后活喊,其實是通過ClassLoader在ClassPath里面加載指定實現(xiàn)類org/slf4j/impl/StaticLoggerBinder.class來實現(xiàn)日志組件的加載丐膝,核心就在:

 //org/slf4j/impl/StaticLoggerBinder.class           

if (loggerFactoryClassLoader == null) {    
    paths = ClassLoader.getSystemResources(STATIC_LOGGER_BINDER_PATH);
 } else {
    paths = loggerFactoryClassLoader.getResources(STATIC_LOGGER_BINDER_PATH);
}

類路徑下看看backLog代碼:

lackLog code

這里有個題外話,之前看過很多介紹Java SPI文章,老是把日志的加載機制歸類為SPI帅矗,其實通過上面的介紹偎肃,現(xiàn)在可以結論,其實不是浑此。
http://www.reibang.com/p/46b42f7f593c

高級開發(fā)必須理解的Java中SPI機制

3.日志切換方法及原理

行文到此累颂,主題介紹似乎差不多了,但是好像還有個問題凛俱,要是我要在Spring Boot換其他日志組件怎么辦吶紊馏。其實Spring Boot已經為我們考慮過這個問題了,為我們提供了一個自動配置的starter:spring-boot-starter-log4j2最冰。

Starter for using Log4j2 for logging. An alternative to spring-boot-starter-logging

修改方式也簡單:

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
            <exclusions>
                <exclusion>
                    <groupId>org.springframework.boot</groupId>
                    <artifactId>spring-boot-starter-logging</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-log4j2</artifactId>
        </dependency>
log4j2 maven

依賴切換成了最后的log4j-api+log4j-core瘦棋。

log4j

參考:

https://zhuanlan.zhihu.com/p/24272450

https://www.slf4j.org

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市暖哨,隨后出現(xiàn)的幾起案子赌朋,更是在濱河造成了極大的恐慌,老刑警劉巖篇裁,帶你破解...
    沈念sama閱讀 219,270評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件沛慢,死亡現(xiàn)場離奇詭異,居然都是意外死亡达布,警方通過查閱死者的電腦和手機团甲,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,489評論 3 395
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來黍聂,“玉大人士聪,你說我怎么就攤上這事张症∏蹋” “怎么了屯蹦?”我有些...
    開封第一講書人閱讀 165,630評論 0 356
  • 文/不壞的土叔 我叫張陵,是天一觀的道長脐区。 經常有香客問我愈诚,道長,這世上最難降的妖魔是什么牛隅? 我笑而不...
    開封第一講書人閱讀 58,906評論 1 295
  • 正文 為了忘掉前任炕柔,我火速辦了婚禮,結果婚禮上媒佣,老公的妹妹穿的比我還像新娘匕累。我一直安慰自己,他們只是感情好默伍,可當我...
    茶點故事閱讀 67,928評論 6 392
  • 文/花漫 我一把揭開白布哩罪。 她就那樣靜靜地躺著授霸,像睡著了一般。 火紅的嫁衣襯著肌膚如雪际插。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,718評論 1 305
  • 那天显设,我揣著相機與錄音框弛,去河邊找鬼。 笑死捕捂,一個胖子當著我的面吹牛瑟枫,可吹牛的內容都是我干的。 我是一名探鬼主播指攒,決...
    沈念sama閱讀 40,442評論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼慷妙,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了允悦?” 一聲冷哼從身側響起膝擂,我...
    開封第一講書人閱讀 39,345評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎隙弛,沒想到半個月后架馋,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經...
    沈念sama閱讀 45,802評論 1 317
  • 正文 獨居荒郊野嶺守林人離奇死亡全闷,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 37,984評論 3 337
  • 正文 我和宋清朗相戀三年叉寂,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片总珠。...
    茶點故事閱讀 40,117評論 1 351
  • 序言:一個原本活蹦亂跳的男人離奇死亡屏鳍,死狀恐怖,靈堂內的尸體忽然破棺而出局服,到底是詐尸還是另有隱情钓瞭,我是刑警寧澤,帶...
    沈念sama閱讀 35,810評論 5 346
  • 正文 年R本政府宣布腌逢,位于F島的核電站降淮,受9級特大地震影響,放射性物質發(fā)生泄漏搏讶。R本人自食惡果不足惜佳鳖,卻給世界環(huán)境...
    茶點故事閱讀 41,462評論 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望媒惕。 院中可真熱鬧系吩,春花似錦、人聲如沸妒蔚。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,011評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至科盛,卻和暖如春帽衙,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背贞绵。 一陣腳步聲響...
    開封第一講書人閱讀 33,139評論 1 272
  • 我被黑心中介騙來泰國打工厉萝, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人榨崩。 一個月前我還...
    沈念sama閱讀 48,377評論 3 373
  • 正文 我出身青樓谴垫,卻偏偏與公主長得像,于是被迫代替她去往敵國和親母蛛。 傳聞我的和親對象是個殘疾皇子翩剪,可洞房花燭夜當晚...
    茶點故事閱讀 45,060評論 2 355