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

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

    1.    Commons Logging存在的ClassLoader問題,即當服務器本身引入Commons Logging時,如果在服務器中載入Commons Logging包,則該包中的類由服務器的ClassLoader加載,從而在加載Log4J中的Logger類時會出現ClassNotFoundException,因為服務器中的ClassLoader沒法找到Web App下的jar包。對于父ClassLoader優先的類加載機制來說,目前的一個解決方案是使用commons-logging-api-1.1.1.jar包,該包的Log實現類只包含Jdk14LoggerSimpleLogNoOpLog三個類,因而在加載這幾個類時會使用Web App中的Commons Logging包,從而解決ClassNotFoundException的問題,然而這種方式對那些實現Child ClassLoader First的服務器來說,由WebClassLoaderClassLoader加載的類在使用日志時會存在問題,因為它們的Log接口由加載自身類的ClassLoader加載,而Log4JLogger類卻由WebClassLoader加載。具體關于Commons Logging中存在的問題我會在另外一篇文章中詳細說明。

    2.    在使用Commons Logging時,我們經常會看到以下方法的寫法:

    if (logger.isDebugEnabled()) {

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

    }

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

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

     

    SLF4J綜述

    類似Commons LoggingSLF4J在使用時通過LoggerFactory得到命名的Logger實例,然后通過該Logger實例調用相應的方法打印日志:

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

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

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

    public interface LoggerFactoryBinder {

     public ILoggerFactory getLoggerFactory();

       public String getLoggerFactoryClassStr();

    }

    LoggerFactory調用StaticLoggerBinder類中的getLoggerFactory()方法返回相應的ILoggerFactory實例:

    public interface ILoggerFactory {

     public Logger getLogger(String name);

    }

    最后通過ILoggerFactory實例獲取Logger實例:

    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)

    }

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

    1.    實現Logger接口的類

    2.    實現ILoggerFactory接口的類

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

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

    5.    實現StaticMDCBinder類,可選。一般也會存在一個靜態的SINGLETON字段,也可選。

    SLF4J的類設計也相對比較簡單(也感覺有點零散):


    SLF4J實現實例,SLF4J APISLF4J Simple

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


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

     private final static void bind() {

        try {

          ...

          //實現綁定

          StaticLoggerBinder.getSingleton();

          INITIALIZATION_STATE = SUCCESSFUL_INITIALIZATION;

          ...

        } catch (NoClassDefFoundError ncde) {

          String msg = ncde.getMessage();

          //判斷是否是因為沒有找到StaticLoggerBinder類引起的異常

          //此時,使用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()方法使用調用StaticLoggerBinder.getSingleton()方法來實現綁定,如果該方法調用成功,則將初始化狀態設置為SUCCESSFUL_INITIALIZATION,如果因為沒有找到StaticLoggerBinder類而引起的異常,則將狀態設置為NOP_FALLBACK_INITIALIZATION,否則將狀態設置為FAILED_INITIALIZATION,并拋出異常。如果在當前classpath下存在多個橋接jar包,在實現綁定前后會記錄存在哪些可使用的橋接jar包,綁定了那個ILoggerFactory類。

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

     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");

     }

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

          SLF4J實現了一個簡單的日志系統:slf4j-simple-<version>.jar。要實現一個兼容SLF4J的日志系統,基本的需要三個類:

    1.    StaticLoggerBinder類,實現LoggerFactoryBinder接口。它實現單例模式,存在getSingleton()靜態方法,存在REQUESTED_API_VERION靜態字段,不用final避免編譯器的優化(將值直接寫入源碼中,而不使用該字段)。返回的ILoggerFactory實例也一直使用同一個實例(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.    實現ILoggerFactory接口的SimpleLoggerFactory。它有一個loggerMap字段緩存所有之前創建的SimpleLogger實例,以Logger Namekey,實現每個相同名字的Logger實例只需要創建一次。

    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類,實現Logger接口。SimpleLogger繼承自MarkerIgnoringBase類,該基類忽略所有存在Marker參數的日志打印方法。SimpleLogger將日志級別分成五個級別:TRACEDEBUGINFOWARNERROR,這些級別對應的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提示字符,默認“WARN”)

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

    org.slf4j.simpleLogger.log.<logNamePrefix>

    并且所有這些key都可以定義在系統屬性中。

    SimpleLogger類的實現主要分成兩步:初始化和打印日志:

    a.    初始化

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

    b.    打印日志

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

    MarkerMarkerFactoryStaticMarkerBinder以及MDCStaticMDCBinder

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

    其他橋接包

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

    SLF4JCommons LoggingLog4J之間的相互轉化

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

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

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

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

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

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

    FeedBack:
    # re: 深入源碼之SLF4J
    2014-08-14 17:26 |
    發送  回復  更多評論
      
    # re: 深入源碼之SLF4J
    2014-10-09 16:57 | zhanjindong
    你好,請教個問題,如果我現在的classpath下有兩個橋接包,比如log4j和logback的,有什么辦法指定加載某一個嗎?我現在每次都是使用log4j。也就是說怎么指定StaticLoggerBinder這個類的加載呢?  回復  更多評論
      
    # re: 深入源碼之SLF4J
    2014-11-01 08:52 | DLevin
    能想到的一種方法,控制兩個橋接包在classpath中的順序~@zhanjindong
      回復  更多評論
      
    # re: 深入源碼之SLF4J
    2014-11-01 08:53 | DLevin
    不過,你可能要問你自己一個問題,為什么會存在兩個橋接包?貌似木有神馬意義啊~@zhanjindong
      回復  更多評論
      
    # re: 深入源碼之SLF4J
    2016-08-14 13:40 | Rookie
    @zhanjindong
    不能指定的,因為在加載的時候他是把所有符合的StaticLoggerBinder都放在一個set中,然后直接就選擇了set集合中第一個加載的類,我想作者考慮的是既然有多個實現都可以滿足,就默認使用第一個加載的,因為不可能同時運行多個實現,當然如果你沒有實現,程序也是可以運行的,會返回NOPLogger這個Logger的子類,不采取任何處理。不管事多個還是沒有具體的實現,都不會影響程序的正常運行。  回復  更多評論
      
    主站蜘蛛池模板: 亚洲一区二区三区精品视频 | 内射干少妇亚洲69XXX| 24小时日本在线www免费的| 一个人免费视频在线观看www| 亚洲日韩国产欧美一区二区三区 | 国产亚洲一区二区手机在线观看| AV无码免费永久在线观看| 一级做a爱片特黄在线观看免费看| 亚洲熟妇自偷自拍另欧美| 久久亚洲国产视频| 午夜老司机免费视频| 中文字幕的电影免费网站| 亚洲中文无码永久免| 久久被窝电影亚洲爽爽爽| 国产又长又粗又爽免费视频| 69式国产真人免费视频| 131美女爱做免费毛片| 在线观看黄片免费入口不卡| 亚洲国产高清国产拍精品| 亚洲综合激情另类小说区| 成人浮力影院免费看| 91禁漫免费进入| 99re8这里有精品热视频免费| 亚洲第一二三四区| 日产亚洲一区二区三区| 亚洲va无码手机在线电影| 亚洲精品成人无限看| 国产精品免费小视频| 99在线精品视频观看免费| 国产精品亚洲专区无码牛牛| 91亚洲视频在线观看| 亚洲人成网站18禁止久久影院| 亚洲综合激情九月婷婷| 亚洲成a人片在线观| 亚洲人成小说网站色| 亚洲精品无码成人片久久不卡| 亚洲精品久久无码| 免费手机在线看片| 精品国产呦系列在线观看免费| 成人免费乱码大片A毛片| 嫩草成人永久免费观看|