上善若水
          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 閱讀(13563) 評論(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的子類,不采取任何處理。不管事多個還是沒有具體的實現,都不會影響程序的正常運行。  回復  更多評論
            
          主站蜘蛛池模板: 禹州市| 灌南县| 沾化县| 沙坪坝区| 沁阳市| 闸北区| 河池市| 诸暨市| 营山县| 丹寨县| 米林县| 临城县| 江源县| 平泉县| 长春市| 环江| 房产| 修文县| 青川县| 太湖县| 井冈山市| 永修县| 香河县| 元江| 通江县| 怀宁县| 平利县| 崇仁县| 高雄县| 西峡县| 高安市| 灵台县| 南丰县| 崇州市| 苏尼特左旗| 循化| 浦东新区| 福建省| 嘉义县| 敖汉旗| 嵊泗县|