<rt id="bn8ez"></rt>
<label id="bn8ez"></label>

  • <span id="bn8ez"></span>

    <label id="bn8ez"><meter id="bn8ez"></meter></label>

    上善若水
    In general the OO style is to use a lot of little objects with a lot of little methods that give us a lot of plug points for overriding and variation. To do is to be -Nietzsche, To bei is to do -Kant, Do be do be do -Sinatra
    posts - 146,comments - 147,trackbacks - 0

    Commons Logging+Log4J一直是Java日志的經(jīng)典組合,以至于很多服務(wù)器都使用了類似的配置,像WebSphere、以前的Tomcat都使用Commons Logging作為日志輸出框架,而據(jù)說JBoss則直接Commons LoggingLog4J一起使用了(這個估計(jì)是為了解決Commons Logging中經(jīng)常在這類服務(wù)器上遇到的ClassLoader問題)。然而Log4J的開發(fā)團(tuán)隊(duì)對Commons Logging貌似不滿意(可以從Log4J Manual中看出一些端倪),因而Log4J團(tuán)隊(duì)開發(fā)了自己的日志門面框架SLF4JSimple Logging Façade For Java),貌似他們對自己開發(fā)的Log4J的性能也不滿意,然后又弄出了個LogBack,關(guān)鍵執(zhí)行語句的性能要比Log4J10倍以上(官網(wǎng)資料,我本人還沒有仔細(xì)看過LogBack的代碼,更沒有測試過,不知道具體性能能提高多少),這是后話,等過幾年看LogBack代碼后再仔細(xì)討論。

    以我個人理解,SLF4J的出現(xiàn)是為了解決Commons Logging存在的兩個問題:

    1.    Commons Logging存在的ClassLoader問題,即當(dāng)服務(wù)器本身引入Commons Logging時(shí),如果在服務(wù)器中載入Commons Logging包,則該包中的類由服務(wù)器的ClassLoader加載,從而在加載Log4J中的Logger類時(shí)會出現(xiàn)ClassNotFoundException,因?yàn)榉?wù)器中的ClassLoader沒法找到Web App下的jar包。對于父ClassLoader優(yōu)先的類加載機(jī)制來說,目前的一個解決方案是使用commons-logging-api-1.1.1.jar包,該包的Log實(shí)現(xiàn)類只包含Jdk14LoggerSimpleLogNoOpLog三個類,因而在加載這幾個類時(shí)會使用Web App中的Commons Logging包,從而解決ClassNotFoundException的問題,然而這種方式對那些實(shí)現(xiàn)Child ClassLoader First的服務(wù)器來說,由WebClassLoaderClassLoader加載的類在使用日志時(shí)會存在問題,因?yàn)樗鼈兊?/span>Log接口由加載自身類的ClassLoader加載,而Log4JLogger類卻由WebClassLoader加載。具體關(guān)于Commons Logging中存在的問題我會在另外一篇文章中詳細(xì)說明。

    2.    在使用Commons Logging時(shí),我們經(jīng)常會看到以下方法的寫法:

    if (logger.isDebugEnabled()) {

        logger.info("Loading XML bean definitions from " + encodedResource.getResource());

    }

    存在isDebugEnabled()的判斷邏輯是為了在避免多余的字符串拼接,即如果不存在isDebugEnabled()判斷,即使當(dāng)前日志級別為ERROR時(shí),在遇到logger.info()調(diào)用時(shí),它還會先拼接日志消息的字符串,然后進(jìn)入該方法內(nèi),才發(fā)現(xiàn)這個日志語句不用打印。而這種多余的拼接不僅浪費(fèi)了多余的CPU操作,而且會增加GC的負(fù)擔(dān)。SLF4J則提供以下的方式來解決這個問題:

    logger.info("Loading XML bean definitions from {}", encodedResource.getResource());

     

    SLF4J綜述

    類似Commons LoggingSLF4J在使用時(shí)通過LoggerFactory得到命名的Logger實(shí)例,然后通過該Logger實(shí)例調(diào)用相應(yīng)的方法打印日志:

    final Logger logger = LoggerFactory.getLogger("levin.logging.slf4j");

    logger.info("Using slf4j, current time is {}"new Date());

    然而不同于Commons Logging的動態(tài)綁定機(jī)制,SLF4J則采用了一種靜態(tài)綁定的機(jī)制,即每個支持SLF4JLogging框架必須存在一個繼承自LoggerFactoryBinder接口的StaticLoggerBinder類:

    public interface LoggerFactoryBinder {

     public ILoggerFactory getLoggerFactory();

       public String getLoggerFactoryClassStr();

    }

    LoggerFactory調(diào)用StaticLoggerBinder類中的getLoggerFactory()方法返回相應(yīng)的ILoggerFactory實(shí)例:

    public interface ILoggerFactory {

     public Logger getLogger(String name);

    }

    最后通過ILoggerFactory實(shí)例獲取Logger實(shí)例:

    public interface Logger {

       public String getName();

       ...(trace)

       public boolean isDebugEnabled();

       public void debug(String msg);

       public void debug(String format, Object arg);

       public void debug(String format, Object arg1, Object arg2);

       public void debug(String format, Object... arguments);

       public void debug(String msg, Throwable t);

       public boolean isDebugEnabled(Marker marker);

       public void debug(Marker marker, String msg);

       public void debug(Marker marker, String format, Object arg);

       public void debug(Marker marker, String format, Object arg1, Object arg2);

       public void debug(Marker marker, String format, Object... arguments);

       public void debug(Marker marker, String msg, Throwable t);

       ...(info)

       ...(warn)

       ...(error)

    }

    也正是因?yàn)檫@個設(shè)計(jì),SLF4Jclasspath下只支持一個橋接包(slf4j-simple-<version>.jarslf4j-log4j12-<version>.jarslf4j-jdk14-<version>.jarlogback-classic-<version>.jar等)。如果在classpath下存在多個橋接包,則具體用哪個就要看這幾個橋接包的加載順序了,實(shí)際中會使用先加載的橋接包。同時(shí)SLF4J會打印使用哪個橋接包,哪些橋接包沒有使用。這種靜態(tài)綁定的設(shè)計(jì)比Commons Logging在可擴(kuò)展性上具有更加靈活的機(jī)制,對“可插拔”的支持也更加高效。如果要支持一個新的Logging框架,Commons Logging需要通過在屬性配置文件、或虛擬機(jī)屬性中配置支持這個新的Logging框架的實(shí)現(xiàn)類(實(shí)現(xiàn)Log接口);而SLF4J則只需要編寫一個五個相應(yīng)的類:

    1.    實(shí)現(xiàn)Logger接口的類

    2.    實(shí)現(xiàn)ILoggerFactory接口的類

    3.    實(shí)現(xiàn)LoggerFactoryBinder接口的類StaticLoggerBinder類(必須使用StaticLoggerBinder類名),并且存在一個靜態(tài)的getSingleton()方法。

    4.    實(shí)現(xiàn)MarkerFactoryBinder類的StaticMarkerBinder類(必須使用StaticMarkerBinder類名),可選。一般也會存在一個靜態(tài)的SINGLETON字段,不過也是可選的。

    5.    實(shí)現(xiàn)StaticMDCBinder類,可選。一般也會存在一個靜態(tài)的SINGLETON字段,也可選。

    SLF4J的類設(shè)計(jì)也相對比較簡單(也感覺有點(diǎn)零散):


    SLF4J實(shí)現(xiàn)實(shí)例,SLF4J APISLF4J Simple

    由于采用了靜態(tài)綁定的方式,而不是像Commons Logging中的動態(tài)綁定,SLF4JLoggerFactory的實(shí)現(xiàn)要比Commons LoggingLogFactory的實(shí)現(xiàn)要簡單的多。即LoggerFactory調(diào)用getILoggerFactory()方法,該方法會初始化LoggerFactory,即通過在bind()方法中加載classpath中的StaticLoggerBinder類,并根據(jù)加載結(jié)果設(shè)置當(dāng)前LoggerFactory的初始化狀態(tài),從而在getILoggerFactory()方法中通過當(dāng)前LoggerFactory的狀態(tài)判斷返回的ILoggerFactory實(shí)例。簡單的示意圖如下:


    bind()方法的主要源碼如下:

     private final static void bind() {

        try {

          ...

          //實(shí)現(xiàn)綁定

          StaticLoggerBinder.getSingleton();

          INITIALIZATION_STATE = SUCCESSFUL_INITIALIZATION;

          ...

        } catch (NoClassDefFoundError ncde) {

          String msg = ncde.getMessage();

          //判斷是否是因?yàn)闆]有找到StaticLoggerBinder類引起的異常

          //此時(shí),使用NOPLoggerFactory類返回給getILoggerFactory(),不打印任何日志

          if (messageContainsOrgSlf4jImplStaticLoggerBinder(msg)) {

            INITIALIZATION_STATE = NOP_FALLBACK_INITIALIZATION;

            ...

          } else {

            // INITIALIZATION_STATE = FAILED_INITIALIZATION

            failedBinding(ncde);

            throw ncde;

          }

        } catch (java.lang.NoSuchMethodError nsme) {

          String msg = nsme.getMessage();

          if (msg != null && msg.indexOf("org.slf4j.impl.StaticLoggerBinder.getSingleton()") != -1) {

            INITIALIZATION_STATE = FAILED_INITIALIZATION;

            ...

          }

          throw nsme;

        } catch (Exception e) {

          //INITIALIZATION_STATE = FAILED_INITIALIZATION;

          failedBinding(e);

          throw new IllegalStateException("Unexpected initialization failure", e);

        }

     }

    bind()方法使用調(diào)用StaticLoggerBinder.getSingleton()方法來實(shí)現(xiàn)綁定,如果該方法調(diào)用成功,則將初始化狀態(tài)設(shè)置為SUCCESSFUL_INITIALIZATION,如果因?yàn)闆]有找到StaticLoggerBinder類而引起的異常,則將狀態(tài)設(shè)置為NOP_FALLBACK_INITIALIZATION,否則將狀態(tài)設(shè)置為FAILED_INITIALIZATION,并拋出異常。如果在當(dāng)前classpath下存在多個橋接jar包,在實(shí)現(xiàn)綁定前后會記錄存在哪些可使用的橋接jar包,綁定了那個ILoggerFactory類。

    bind()返回后,performInitialization()方法會再做一些版本檢查,即StaticLoggerBinder可以定義一個靜態(tài)的REQUESTED_API_VERSION字段,表示該StaticLoggerBinder支持的SLF4J版本,如果該版本不在LoggerFactory定義的兼容版本列表中(API_COMPATIBILITY_LIST),SLF4J會打印警告信息,并列出當(dāng)前LoggerFactory兼容的版本列表。而后在getILoggerFactory()方法中會根據(jù)當(dāng)前LoggerFactory的初始化狀態(tài)來決定返回的ILoggerFactory實(shí)例:

     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(UNSUCCESSFUL_INIT_MSG);

          case ONGOING_INITIALIZATION:

            // support re-entrant behavior.

            // See also http://bugzilla.slf4j.org/show_bug.cgi?id=106

            return TEMP_FACTORY;

        }

        throw new IllegalStateException("Unreachable code");

     }

    當(dāng)LoggerFactory成功初始化,則返回綁定的StaticLoggerBinder中的ILoggerFactory實(shí)例;如果為NOP_FALLBACK_INITIALIZATION(沒有找到橋接jar),則返回NOPLoggerFactory,它返回一個單例的NOPLogger實(shí)例,該類不會打印任何日志;如果初始化狀態(tài)為FAILED_INITIALIZATION,拋出IllegalStateException異常;如果初始化狀態(tài)為ONGOING_INITIALIZATION,則返回SubstituteLoggerFactory類實(shí)例,該狀態(tài)發(fā)生在一個線程正在初始化LoggerFactory,而另一個線程已經(jīng)開始請求獲取ILoggerFactory實(shí)例,SubstituteLoggerFactory會記錄當(dāng)前請求的Logger名稱,然后返回NOPLogger實(shí)例。所有這些在LoggerFactory初始化時(shí)被忽略的Logger Name會在LoggerFactory初始化成功以后被report出來(在System.err流中打印出來)。

          SLF4J實(shí)現(xiàn)了一個簡單的日志系統(tǒng):slf4j-simple-<version>.jar。要實(shí)現(xiàn)一個兼容SLF4J的日志系統(tǒng),基本的需要三個類:

    1.    StaticLoggerBinder類,實(shí)現(xiàn)LoggerFactoryBinder接口。它實(shí)現(xiàn)單例模式,存在getSingleton()靜態(tài)方法,存在REQUESTED_API_VERION靜態(tài)字段,不用final避免編譯器的優(yōu)化(將值直接寫入源碼中,而不使用該字段)。返回的ILoggerFactory實(shí)例也一直使用同一個實(shí)例(SimpleLoggerFactory)。

    public class StaticLoggerBinder implements LoggerFactoryBinder {

     private static final StaticLoggerBinder SINGLETON = new StaticLoggerBinder();

     public static final StaticLoggerBinder getSingleton() {

        return SINGLETON;

     }

     // to avoid constant folding by the compiler, this field must *not* be final

     public static String REQUESTED_API_VERSION = "1.6.99"// !final

     private static final String loggerFactoryClassStr = SimpleLoggerFactory.class.getName();

     private final ILoggerFactory loggerFactory;

     private StaticLoggerBinder() {

        loggerFactory = new SimpleLoggerFactory();

     }

     public ILoggerFactory getLoggerFactory() {

        return loggerFactory;

     }

     public String getLoggerFactoryClassStr() {

        return loggerFactoryClassStr;

     }  

    }

    2.    實(shí)現(xiàn)ILoggerFactory接口的SimpleLoggerFactory。它有一個loggerMap字段緩存所有之前創(chuàng)建的SimpleLogger實(shí)例,以Logger Namekey,實(shí)現(xiàn)每個相同名字的Logger實(shí)例只需要創(chuàng)建一次。

    public class SimpleLoggerFactory implements ILoggerFactory {

     final static SimpleLoggerFactory INSTANCE = new SimpleLoggerFactory();

     Map loggerMap;

     public SimpleLoggerFactory() {

        loggerMap = new HashMap();

     }

     public Logger getLogger(String name) {

        Logger slogger = null;

        // protect against concurrent access of the loggerMap

        synchronized (this) {

          slogger = (Logger) loggerMap.get(name);

          if (slogger == null) {

            slogger = new SimpleLogger(name);

            loggerMap.put(name, slogger);

          }

        }

        return slogger;

     }

    }

    3.    SimpleLogger類,實(shí)現(xiàn)Logger接口。SimpleLogger繼承自MarkerIgnoringBase類,該基類忽略所有存在Marker參數(shù)的日志打印方法。SimpleLogger將日志級別分成五個級別:TRACEDEBUGINFOWARNERROR,這些級別對應(yīng)的INT值一次增大。SimpleLogger還支持對simplelogger.properties配置文件的解析,它支持的key值有:

    org.slf4j.simpleLogger.defaultLogLevel

    org.slf4j.simpleLogger.showDateTime

    org.slf4j.simpleLogger.dateTimeFormat

    org.slf4j.simpleLogger.showThreadName

    org.slf4j.simpleLogger.showLogName

    org.slf4j.simpleLogger.showShortLogName

    org.slf4j.simpleLogger.logFile

    org.slf4j.simpleLogger.levelInBrackets

    org.slf4j.simpleLogger.warnLevelStringwarn提示字符,默認(rèn)“WARN”)

    同時(shí)SimpleLogger還支持為特定的Logger Name前綴(以”.”作為分隔符)指定level

    org.slf4j.simpleLogger.log.<logNamePrefix>

    并且所有這些key都可以定義在系統(tǒng)屬性中。

    SimpleLogger類的實(shí)現(xiàn)主要分成兩步:初始化和打印日志:

    a.    初始化

    加載配置文件,使用加載的配置文件初始化類字段,即對應(yīng)以上simplelogger.properties支持的key;保存當(dāng)前Logger Name;計(jì)算當(dāng)前Logger實(shí)際的Level,即如果沒有為該Logger Name(或其以“.”分隔的前綴)配置特定的Level,則使用默認(rèn)配置的Level,否則,使用具體的日志,并保存計(jì)算出的Level值,如果沒有找到Level配置,使用默認(rèn)值INFO

    b.    打印日志

    對使用format字符串的日志打印方法,調(diào)用formatAndLog()方法,其內(nèi)部先調(diào)用MessageFormatter.arrayFormat()方法,然后調(diào)用log()方法實(shí)現(xiàn)打印信息。log()方法的實(shí)現(xiàn)只是根據(jù)解析出來的配置信息,判斷哪些信息需要打印,則打印這些信息,實(shí)現(xiàn)比較簡單,不再贅述。

    MarkerMarkerFactoryStaticMarkerBinder以及MDCStaticMDCBinder

    并不是所有Logging系統(tǒng)支持這些功能,對它們支持最全面的當(dāng)屬LogBack框架了,因而這些類將會在介紹LogBack框架時(shí)一起討論。在slf4j-simple-<version>.jarStaticMarkerBinder返回BasicMarkerFactory實(shí)例,而StaticMDCBinder返回NOPMDCAdapter實(shí)例。

    其他橋接包

    slf4j-log4j12-<version>.jarslf4j-jdk14-<version>.jarslf4j-jcl-<version>.jar等,它們的實(shí)現(xiàn)類似slf4j-simple-<version>.jar的實(shí)現(xiàn),并且更加簡單,因而它們對Logger的實(shí)現(xiàn)將大部分的邏輯代理給了底層實(shí)現(xiàn)框架,因而這里不再贅述。

    SLF4JCommons LoggingLog4J之間的相互轉(zhuǎn)化

    SLF4J支持上層是SLF4J框架,底層還是通過Commons Logging的動態(tài)查找機(jī)制,只要將slf4j-jcl-<version>.jar包加入classpath中即可(當(dāng)然slf4j-api-<version>.jar也要存在)。

    另外SLF4J還支持上層是Commons Logging,而底層交給SLF4J提供的靜態(tài)綁定機(jī)制查找真正的日志實(shí)現(xiàn)框架,只需要將jcl-over-slf4j-<version>.jar包加入到classpath中,此時(shí)不需要引入commons-logging-<version>.jar包。它的實(shí)現(xiàn)只是重寫了Commons Logging框架,并在LogFactory中只使用SLF4JLogSLF4JLocationAwareLog類。

    不過需要注意,slf4j-jcl-<version>.jar包和jcl-over-slf4j-<version>.jar兩個包不能同時(shí)出現(xiàn)在classpath中,不然會引起循環(huán)調(diào)用而導(dǎo)致棧溢出的問題,因而slf4j-jcl-<version>.jar在初始化時(shí)就會檢測這個限制,并拋出異常。

    最后SLF4J還支持Log4J作為上層,而底層交給SLF4J靜態(tài)綁定要真正實(shí)現(xiàn)日志打印的框架,可以將log4j-over-slf4j-<version>.jar包加入到classpath中。其實(shí)現(xiàn)也類似jcl-over-slf4j-<version>.jar的實(shí)現(xiàn),重寫大部分的Log4J的內(nèi)部邏輯,而在Logger類實(shí)現(xiàn)中,將真正的日志打印邏輯代理給SLF4JLoggerFactory

    最后給我現(xiàn)在在公司開發(fā)的這個系統(tǒng)吐個槽,我們隊(duì)日志并沒有統(tǒng)一的管理,有人用Commons Logging,也有人直接用Log4J,其實(shí)所有代碼都沒有統(tǒng)一管理,很是換亂,不過SLF4J竟然可以滿足這種情況的遷移,即可以將log4j-over-slf4j-<version>.jarjcl-over-slf4j-<version>.jar包同時(shí)放到classpath下。而到這個時(shí)候我才意識到為什么SLF4J為什么會慢慢的使用那么廣泛了。

    posted on 2012-11-08 00:44 DLevin 閱讀(13566) 評論(5)  編輯  收藏 所屬分類: Logging

    FeedBack:
    # re: 深入源碼之SLF4J
    2014-08-14 17:26 | 發(fā)
    發(fā)送  回復(fù)  更多評論
      
    # re: 深入源碼之SLF4J
    2014-10-09 16:57 | zhanjindong
    你好,請教個問題,如果我現(xiàn)在的classpath下有兩個橋接包,比如log4j和logback的,有什么辦法指定加載某一個嗎?我現(xiàn)在每次都是使用log4j。也就是說怎么指定StaticLoggerBinder這個類的加載呢?  回復(fù)  更多評論
      
    # re: 深入源碼之SLF4J
    2014-11-01 08:52 | DLevin
    能想到的一種方法,控制兩個橋接包在classpath中的順序~@zhanjindong
      回復(fù)  更多評論
      
    # re: 深入源碼之SLF4J
    2014-11-01 08:53 | DLevin
    不過,你可能要問你自己一個問題,為什么會存在兩個橋接包?貌似木有神馬意義啊~@zhanjindong
      回復(fù)  更多評論
      
    # re: 深入源碼之SLF4J
    2016-08-14 13:40 | Rookie
    @zhanjindong
    不能指定的,因?yàn)樵诩虞d的時(shí)候他是把所有符合的StaticLoggerBinder都放在一個set中,然后直接就選擇了set集合中第一個加載的類,我想作者考慮的是既然有多個實(shí)現(xiàn)都可以滿足,就默認(rèn)使用第一個加載的,因?yàn)椴豢赡芡瑫r(shí)運(yùn)行多個實(shí)現(xiàn),當(dāng)然如果你沒有實(shí)現(xiàn),程序也是可以運(yùn)行的,會返回NOPLogger這個Logger的子類,不采取任何處理。不管事多個還是沒有具體的實(shí)現(xiàn),都不會影響程序的正常運(yùn)行。  回復(fù)  更多評論
      
    主站蜘蛛池模板: 无码国产精品一区二区免费模式| 涩涩色中文综合亚洲| 亚洲一区二区三区影院 | 99精品免费视品| 黄色短视频免费看| 国产大片免费天天看| 男女拍拍拍免费视频网站| 中文在线日本免费永久18近| 91精品全国免费观看青青| 免费在线观影网站| 一级特黄aa毛片免费观看| 一区二区三区在线免费看| 热re99久久6国产精品免费| 久久免费看黄a级毛片| 免费视频爱爱太爽了| 青青草免费在线视频| 天天摸夜夜摸成人免费视频| 永久黄网站色视频免费观看| 国产禁女女网站免费看| 亚洲人成色7777在线观看不卡 | 午夜免费福利网站| 男人的天堂亚洲一区二区三区| 国产成人精品日本亚洲专区| 国产成人亚洲精品青草天美| 亚洲精选在线观看| 亚洲精品天堂在线观看| 美女露100%胸无遮挡免费观看| 国产精品免费αv视频| 日韩人妻一区二区三区免费| 100000免费啪啪18免进| 国产成人免费a在线资源| 国产亚洲午夜高清国产拍精品| 亚洲欧洲第一a在线观看| 香蕉大伊亚洲人在线观看| 免费无码国产在线观国内自拍中文字幕 | 婷婷亚洲综合五月天小说| 亚洲国产情侣一区二区三区| 亚洲a∨国产av综合av下载 | 国产精品成人亚洲| 国产一区二区三区免费| 可以免费看黄的网站|