阿里Java開發(fā)手冊思考(三)

題圖:by pixel2013 From pixabay

上期我們分享了Java中if/else復雜邏輯的處理

本期我們將分享Java中日志的處理(上)

想必大家都用過日志,雖然日志看起來可有可無雾袱,但是等到出問題的時候旧巾,日志就派上了大用場祥山,所以說日志打得好不好,規(guī)范不規(guī)范剥扣,直接影響了解決生產(chǎn)環(huán)境故障的效率,日志打的不好铝穷,有可能影響環(huán)境的性能钠怯,也有可能影響排查問題的難易程度,有可能排查問題的時間比寫代碼的時間還有多曙聂。

那么我們就來分析下阿里Java開發(fā)手冊--日志規(guī)約第一條:
【強制】應用中不可直接使用日志系統(tǒng)(Log4j晦炊、Logback)中的 API,而應依賴使用日志框架 SLF4J 中的 API筹陵,使用門面模式的日志框架刽锤,有利于維護和各個類的日志處理方式統(tǒng)一。

import org.slf4j.Logger; 
import org.slf4j.LoggerFactory; 
private static final Logger logger = LoggerFactory.getLogger(Abc.class);

日志框架

Java中的日志框架分如下幾種:

  • Log4j Apache Log4j是一個基于Java的日志記錄工具朦佩。它是由Ceki Gülcü首創(chuàng)的并思,現(xiàn)在則是Apache軟件基金會的一個項目。

  • Log4j 2 Apache Log4j 2是apache開發(fā)的一款Log4j的升級產(chǎn)品语稠。

  • Commons Logging Apache基金會所屬的項目宋彼,是一套Java日志接口弄砍,之前叫Jakarta Commons Logging,后更名為Commons Logging输涕。

  • Slf4j 類似于Commons Logging音婶,是一套簡易Java日志門面,本身并無日志的實現(xiàn)莱坎。(Simple Logging Facade for Java衣式,縮寫Slf4j)。

  • Logback 一套日志組件的實現(xiàn)(slf4j陣營)檐什。

  • Jul (Java Util Logging),自Java1.4以來的官方日志實現(xiàn)碴卧。

使用示例

  • Jul
import java.util.logging.Logger;

private static final Logger logger = Logger.getLogger("name");
...
try {
...
} catch (Exception e) {
    logger.error(".....error");
}

if(logger.isDebugEnabled()) {
    logger.debug("....." + name);
}
  • Log4j
import org.apache.log4j.Logger;

private static final Logger logger = Logger.getLogger(Abc.class.getNeme());
...
try {
...
} catch (Exception e) {
    logger.error(".....error");
}

if(logger.isDebugEnabled()) {
    logger.debug("....." + name);
}
  • Commons Logging
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

private static final Log logger = LogFactory.getLogger(Abc.class);
...
try {
...
} catch (Exception e) {
    logger.error(".....error");
}

if(logger.isDebugEnabled()) {
    logger.debug("....." + name);
}
  • Slf4j
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

private static final Logger logger = LoggerFactory.getLogger(Abc.class);
...
try {
...
} catch (Exception e) {
    logger.error(".....error {}", e.getMessage(), e);
}

logger.debug(".....{}", name);
  • Jul
    • 不支持占位符
    • 具體日志實現(xiàn)
  • Log4j
    • 不支持占位符
    • 具體日志實現(xiàn)
  • Logback
    • 不支持占位符
    • 具體日志實現(xiàn)
  • Commons Logging
    • 不支持占位符
    • 日志門面
  • Slf4j
    • 支持占位符
    • 日志門面

Slf4j中有一個很重要的特性:占位符,{}可以拼接任意字符串乃正,相比如其他框架的優(yōu)點即不需要用+來拼接字符串住册,也就不會創(chuàng)建新的字符串對象,所以像log4j中需要加isDebugEnabled()的判斷就是這個道理瓮具,在slf4j中就不需要加判斷荧飞。

門面模式

門面(Facade)模式,又稱外觀模式名党,對外隱藏了系統(tǒng)的復雜性叹阔,并向客戶端提供了可以訪問的接口,門面模式的好處是將客戶端和子系統(tǒng)松耦合兑巾,方便子系統(tǒng)的擴展和維護条获。

正是門面模式這樣的特點,使用Slf4j門面蒋歌,不管日志組件使用的是log4j還是logback等等帅掘,對于調(diào)用者而言并不關(guān)心使用的是什么日志組件,而且對于日志組件的更換或者升級堂油,調(diào)用的地方也不要做任何修改修档。

源碼分析

此處應有代(zhang)碼(sheng):

首先使用靜態(tài)工廠來獲取Logger對象,傳入的class府框,最終會轉(zhuǎn)化為name吱窝,每個類的日志處理可能不同,所以根據(jù)傳入類的名字來判斷類的實現(xiàn)方式

public static Logger getLogger(Class clazz) {
    return getLogger(clazz.getName());
}

public static Logger getLogger(String name) {
    ILoggerFactory iLoggerFactory = getILoggerFactory();
    return iLoggerFactory.getLogger(name);
}

真正核心的在getILoggerFactory()中迫靖,首先判斷初始化的狀態(tài)INITIALIZATION_STATE院峡,如果沒有初始化UNINITIALIZED,那么會更改狀態(tài)為正在初始化ONGOING_INITIALIZATION系宜,并執(zhí)行初始化performInitialization()照激,初始化完成之后,判斷初始化的狀態(tài)盹牧,如果初始化成功SUCCESSFUL_INITIALIZATION俩垃,那么會通過StaticLoggerBinder獲取日志工廠getLoggerFactory()励幼,這里又涉及到了單例模式

public static ILoggerFactory getILoggerFactory() {
    if (INITIALIZATION_STATE == UNINITIALIZED) {
        INITIALIZATION_STATE = ONGOING_INITIALIZATION;
        performInitialization();
    }
    switch (INITIALIZATION_STATE) {
        case SUCCESSFUL_INITIALIZATION:
            return StaticLoggerBinder.getSingleton().getLoggerFactory();
        case NOP_FALLBACK_INITIALIZATION:
            return NOP_FALLBACK_FACTORY;
        case FAILED_INITIALIZATION:
            throw new IllegalStateException("org.slf4j.LoggerFactory could not be successfully initialized. See also http://www.slf4j.org/codes.html#unsuccessfulInit");
        case ONGOING_INITIALIZATION:
            return TEMP_FACTORY;
    }
    throw new IllegalStateException("Unreachable code");
}

接著我們分析performInitialization是如何初始化的口柳,首先是執(zhí)行bind()方法苹粟,然后判斷如果狀態(tài)為初始化成功SUCCESSFUL_INITIALIZATION,執(zhí)行版本檢查跃闹,主要是檢查jdk版本與slf4j的版本嵌削,看是否匹配。

private static final void performInitialization() {
    bind();
    if (INITIALIZATION_STATE == 3) {
        versionSanityCheck();
    }
}

bind()方法辣卒,首先獲取實現(xiàn)日志的加載路徑掷贾,檢查路徑是否合法,然后初始化StaticLoggerBinder的對象荣茫,尋找合適的實現(xiàn)方式使用。

private static final void bind() {
    try {
        Set staticLoggerBinderPathSet = findPossibleStaticLoggerBinderPathSet();
        reportMultipleBindingAmbiguity(staticLoggerBinderPathSet);

        StaticLoggerBinder.getSingleton();
        INITIALIZATION_STATE = SUCCESSFUL_INITIALIZATION;
        reportActualBinding(staticLoggerBinderPathSet);
        emitSubstituteLoggerWarning();
    } catch (NoClassDefFoundError ncde) {
        String msg = ncde.getMessage();
        if (messageContainsOrgSlf4jImplStaticLoggerBinder(msg)) {
            INITIALIZATION_STATE = NOP_FALLBACK_INITIALIZATION;
            Util.report("Failed to load class \"org.slf4j.impl.StaticLoggerBinder\".");
            Util.report("Defaulting to no-operation (NOP) logger implementation");
            Util.report("See http://www.slf4j.org/codes.html#StaticLoggerBinder for further details.");
        } else {
            failedBinding(ncde);
            throw ncde;
        }
    } catch (NoSuchMethodError nsme) {
        String msg = nsme.getMessage();
        if ((msg != null) && (msg.indexOf("org.slf4j.impl.StaticLoggerBinder.getSingleton()") != -1)) {
            INITIALIZATION_STATE = FAILED_INITIALIZATION;
            Util.report("slf4j-api 1.6.x (or later) is incompatible with this binding.");
            Util.report("Your binding is version 1.5.5 or earlier.");
            Util.report("Upgrade your binding to version 1.6.x.");
        }
        throw nsme;
    } catch (Exception e) {
        failedBinding(e);
        throw new IllegalStateException("Unexpected initialization failure", e);
    }
}

可以看出场靴,bind()方法中最重要的方法就是尋找實現(xiàn)方式findPossibleStaticLoggerBinderPathSet啡莉,具體方法實現(xiàn)如下:

private static Set findPossibleStaticLoggerBinderPathSet() {
    Set staticLoggerBinderPathSet = new LinkedHashSet();
    try {
        ClassLoader loggerFactoryClassLoader = LoggerFactory.class.getClassLoader();
        Enumeration paths;
        Enumeration paths;
        if (loggerFactoryClassLoader == null) {
            paths = ClassLoader.getSystemResources(STATIC_LOGGER_BINDER_PATH);
        } else {
            paths = loggerFactoryClassLoader.getResources(STATIC_LOGGER_BINDER_PATH);
        }

        while (paths.hasMoreElements()) {
            URL path = (URL)paths.nextElement();
            staticLoggerBinderPathSet.add(path);
        }
    } catch (IOException ioe) {
        Util.report("Error getting resources from path", ioe);
    }
    return staticLoggerBinderPathSet;
}

注意!旨剥!前方高能_中馈!

Slf4j的絕妙之處就在于此轨帜,類加載器加載類魄咕,也就是說尋找StaticLoggerBinder.class文件,然后只要實現(xiàn)了這個類的日志組件蚌父,都可以作為一種實現(xiàn)哮兰,如果有多個實現(xiàn),那么誰先加載就使用誰苟弛,這個地方涉及JVM的類加載機制

橋接

  • Slf4j與其他日志組件的橋接(Bridge)
  • slf4j-log4j12-1.7.13.jar
    • log4j1.2版本的橋接器
  • slf4j-jdk14-1.7.13.jar
    • java.util.logging的橋接器
  • slf4j-nop-1.7.13.jar
    • NOP橋接器
  • slf4j-simple-1.7.13.jar
    • 一個簡單實現(xiàn)的橋接器
  • slf4j-jcl-1.7.13.jar
    • Jakarta Commons Logging 的橋接器. 這個橋接器將SLF4j所有日志委派給JCL
  • logback-classic-1.0.13.jar(requires logback-core-1.0.13.jar)
    • slf4j的原生實現(xiàn)喝滞,logback直接實現(xiàn)了slf4j的接口,因此使用slf4j與logback的結(jié)合使用也意味更小的內(nèi)存與計算開銷

Slf4j Manual中有一張圖清晰的展示了接入方式膏秫,如下:

橋接
  • Bridging legacy APIs(橋接遺留的api)
  • log4j-over-slf4j-version.jar
* 將log4j重定向到slf4j
  • jcl-over-slf4j-version.jar
    • 將commos logging里的Simple Logger重定向到slf4j
  • jul-to-slf4j-version.jar
    • 將Java Util Logging重定向到slf4j
橋接遺留api
  • 橋接注意事項

在使用slf4j橋接時要注意避免形成死循環(huán)右遭,在項目依賴的jar包中不要存在以下情況

  • log4j-over-slf4j.jar和slf4j-log4j12.jar同時存在
  • 從名字上就能看出,前者重定向給后者缤削,后者又委派給前者窘哈,會形成死循環(huán)
  • jul-to-slf4j.jar和slf4j-jdk14.jar同時存在
    • 從名字上就能看出,前者重定向給后者亭敢,后者又委派給前者滚婉,會形成死循環(huán)

總結(jié)

  • 為了更好的了解Slf4j,你需要了解:

    • JVM類加載機制
    • 設計模式:門面模式吨拗、橋接模式
  • 簡單總結(jié)Slf4j的原理:

    • 通過工廠類满哪,提供一個的接口婿斥,用戶可以通過這個門面,直接使用API實現(xiàn)日志的記錄哨鸭。
    • 而具體實現(xiàn)由Slf4j來尋找加載民宿,尋找的過程,就是通過類加載加載org/slf4j/impl/StaticLoggerBinder.class的文件像鸡,只要實現(xiàn)了這個文件的日志實現(xiàn)系統(tǒng)活鹰,都可以作為一種實現(xiàn)方式。
    • 如果找到很多種方式只估,那么就尋找一種默認的方式志群。
    • 這就是日志接口的工作方式,簡單高效蛔钙,關(guān)鍵是完全解耦锌云,不需要日志實現(xiàn)部分提供任何的修改配置,只需要符合接口的標準就可以加載進來吁脱,有利于維護和各個類的日志處理方式統(tǒng)一桑涎。
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市兼贡,隨后出現(xiàn)的幾起案子攻冷,更是在濱河造成了極大的恐慌,老刑警劉巖遍希,帶你破解...
    沈念sama閱讀 221,635評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件等曼,死亡現(xiàn)場離奇詭異,居然都是意外死亡凿蒜,警方通過查閱死者的電腦和手機禁谦,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,543評論 3 399
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來篙程,“玉大人枷畏,你說我怎么就攤上這事∈觯” “怎么了拥诡?”我有些...
    開封第一講書人閱讀 168,083評論 0 360
  • 文/不壞的土叔 我叫張陵,是天一觀的道長氮发。 經(jīng)常有香客問我渴肉,道長,這世上最難降的妖魔是什么爽冕? 我笑而不...
    開封第一講書人閱讀 59,640評論 1 296
  • 正文 為了忘掉前任仇祭,我火速辦了婚禮,結(jié)果婚禮上颈畸,老公的妹妹穿的比我還像新娘乌奇。我一直安慰自己没讲,他們只是感情好,可當我...
    茶點故事閱讀 68,640評論 6 397
  • 文/花漫 我一把揭開白布礁苗。 她就那樣靜靜地躺著爬凑,像睡著了一般。 火紅的嫁衣襯著肌膚如雪试伙。 梳的紋絲不亂的頭發(fā)上嘁信,一...
    開封第一講書人閱讀 52,262評論 1 308
  • 那天,我揣著相機與錄音疏叨,去河邊找鬼潘靖。 笑死,一個胖子當著我的面吹牛蚤蔓,可吹牛的內(nèi)容都是我干的卦溢。 我是一名探鬼主播,決...
    沈念sama閱讀 40,833評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼秀又,長吁一口氣:“原來是場噩夢啊……” “哼既绕!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起涮坐,我...
    開封第一講書人閱讀 39,736評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎誓军,沒想到半個月后袱讹,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,280評論 1 319
  • 正文 獨居荒郊野嶺守林人離奇死亡昵时,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,369評論 3 340
  • 正文 我和宋清朗相戀三年捷雕,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片壹甥。...
    茶點故事閱讀 40,503評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡救巷,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出句柠,到底是詐尸還是另有隱情浦译,我是刑警寧澤,帶...
    沈念sama閱讀 36,185評論 5 350
  • 正文 年R本政府宣布溯职,位于F島的核電站精盅,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏谜酒。R本人自食惡果不足惜叹俏,卻給世界環(huán)境...
    茶點故事閱讀 41,870評論 3 333
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望僻族。 院中可真熱鬧粘驰,春花似錦屡谐、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,340評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至籽前,卻和暖如春亭珍,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背枝哄。 一陣腳步聲響...
    開封第一講書人閱讀 33,460評論 1 272
  • 我被黑心中介騙來泰國打工肄梨, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人挠锥。 一個月前我還...
    沈念sama閱讀 48,909評論 3 376
  • 正文 我出身青樓众羡,卻偏偏與公主長得像,于是被迫代替她去往敵國和親蓖租。 傳聞我的和親對象是個殘疾皇子粱侣,可洞房花燭夜當晚...
    茶點故事閱讀 45,512評論 2 359

推薦閱讀更多精彩內(nèi)容

  • 歷史 log4j可以當之無愧地說是Java日志框架的元老,1999年發(fā)布首個版本蓖宦,2012年發(fā)布最后一個版本齐婴,20...
    kelgon閱讀 10,165評論 3 53
  • 問題 在項目啟動時,發(fā)現(xiàn)打印了大量的debug日志稠茂,但是src/main/resources下明明有l(wèi)og4j.x...
    Mr胡桃閱讀 22,167評論 2 11
  • 概述 在項目開發(fā)中柠偶,為了跟蹤代碼的運行情況,常常要使用日志來記錄信息睬关。在Java世界诱担,有很多的日志工具庫來實現(xiàn)日志...
    靜默虛空閱讀 1,863評論 1 9
  • 前言 最近學習開java web服務器開發(fā),開始學習java电爹,處理業(yè)務邏輯蔫仙,但對其中的日志比較好奇,之前沒怎么接觸...
    九風萍舟閱讀 3,299評論 1 6
  • 在項目開發(fā)過程中丐箩,我們可以通過 debug 查找問題摇邦。而在線上環(huán)境我們查找問題只能通過打印日志的方式查找問題。因此...
    Java架構(gòu)閱讀 3,480評論 2 41