上善若水
          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一起使用了(這個(gè)估計(jì)是為了解決Commons Logging中經(jīng)常在這類服務(wù)器上遇到的ClassLoader問題)。然而Log4J的開發(fā)團(tuán)隊(duì)對(duì)Commons Logging貌似不滿意(可以從Log4J Manual中看出一些端倪),因而Log4J團(tuán)隊(duì)開發(fā)了自己的日志門面框架SLF4JSimple Logging Façade For Java),貌似他們對(duì)自己開發(fā)的Log4J的性能也不滿意,然后又弄出了個(gè)LogBack,關(guān)鍵執(zhí)行語句的性能要比Log4J10倍以上(官網(wǎng)資料,我本人還沒有仔細(xì)看過LogBack的代碼,更沒有測(cè)試過,不知道具體性能能提高多少),這是后話,等過幾年看LogBack代碼后再仔細(xì)討論。

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

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

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

          if (logger.isDebugEnabled()) {

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

          }

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

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

           

          SLF4J綜述

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

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

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

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

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

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

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


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

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


          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下存在多個(gè)橋接jar包,在實(shí)現(xiàn)綁定前后會(huì)記錄存在哪些可使用的橋接jar包,綁定了那個(gè)ILoggerFactory類。

          bind()返回后,performInitialization()方法會(huì)再做一些版本檢查,即StaticLoggerBinder可以定義一個(gè)靜態(tài)的REQUESTED_API_VERSION字段,表示該StaticLoggerBinder支持的SLF4J版本,如果該版本不在LoggerFactory定義的兼容版本列表中(API_COMPATIBILITY_LIST),SLF4J會(huì)打印警告信息,并列出當(dāng)前LoggerFactory兼容的版本列表。而后在getILoggerFactory()方法中會(huì)根據(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,它返回一個(gè)單例的NOPLogger實(shí)例,該類不會(huì)打印任何日志;如果初始化狀態(tài)為FAILED_INITIALIZATION,拋出IllegalStateException異常;如果初始化狀態(tài)為ONGOING_INITIALIZATION,則返回SubstituteLoggerFactory類實(shí)例,該狀態(tài)發(fā)生在一個(gè)線程正在初始化LoggerFactory,而另一個(gè)線程已經(jīng)開始請(qǐng)求獲取ILoggerFactory實(shí)例,SubstituteLoggerFactory會(huì)記錄當(dāng)前請(qǐng)求的Logger名稱,然后返回NOPLogger實(shí)例。所有這些在LoggerFactory初始化時(shí)被忽略的Logger Name會(huì)在LoggerFactory初始化成功以后被report出來(在System.err流中打印出來)。

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

          1.    StaticLoggerBinder類,實(shí)現(xiàn)LoggerFactoryBinder接口。它實(shí)現(xiàn)單例模式,存在getSingleton()靜態(tài)方法,存在REQUESTED_API_VERION靜態(tài)字段,不用final避免編譯器的優(yōu)化(將值直接寫入源碼中,而不使用該字段)。返回的ILoggerFactory實(shí)例也一直使用同一個(gè)實(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。它有一個(gè)loggerMap字段緩存所有之前創(chuàng)建的SimpleLogger實(shí)例,以Logger Namekey,實(shí)現(xiàn)每個(gè)相同名字的Logger實(shí)例只需要?jiǎng)?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將日志級(jí)別分成五個(gè)級(jí)別:TRACEDEBUG、INFO、WARN、ERROR,這些級(jí)別對(duì)應(yīng)的INT值一次增大。SimpleLogger還支持對(duì)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.    初始化

          加載配置文件,使用加載的配置文件初始化類字段,即對(duì)應(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.    打印日志

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

          Marker、MarkerFactory、StaticMarkerBinder以及MDC、StaticMDCBinder

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

          其他橋接包

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

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

          SLF4J支持上層是SLF4J框架,底層還是通過Commons Logging的動(dòng)態(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兩個(gè)包不能同時(shí)出現(xiàn)在classpath中,不然會(huì)引起循環(huán)調(diào)用而導(dǎo)致棧溢出的問題,因而slf4j-jcl-<version>.jar在初始化時(shí)就會(huì)檢測(cè)這個(gè)限制,并拋出異常。

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

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

          FeedBack:
          # re: 深入源碼之SLF4J
          2014-08-14 17:26 | 發(fā)
          發(fā)送  回復(fù)  更多評(píng)論
            
          # re: 深入源碼之SLF4J
          2014-10-09 16:57 | zhanjindong
          你好,請(qǐng)教個(gè)問題,如果我現(xiàn)在的classpath下有兩個(gè)橋接包,比如log4j和logback的,有什么辦法指定加載某一個(gè)嗎?我現(xiàn)在每次都是使用log4j。也就是說怎么指定StaticLoggerBinder這個(gè)類的加載呢?  回復(fù)  更多評(píng)論
            
          # re: 深入源碼之SLF4J
          2014-11-01 08:52 | DLevin
          能想到的一種方法,控制兩個(gè)橋接包在classpath中的順序~@zhanjindong
            回復(fù)  更多評(píng)論
            
          # re: 深入源碼之SLF4J
          2014-11-01 08:53 | DLevin
          不過,你可能要問你自己一個(gè)問題,為什么會(huì)存在兩個(gè)橋接包?貌似木有神馬意義啊~@zhanjindong
            回復(fù)  更多評(píng)論
            
          # re: 深入源碼之SLF4J
          2016-08-14 13:40 | Rookie
          @zhanjindong
          不能指定的,因?yàn)樵诩虞d的時(shí)候他是把所有符合的StaticLoggerBinder都放在一個(gè)set中,然后直接就選擇了set集合中第一個(gè)加載的類,我想作者考慮的是既然有多個(gè)實(shí)現(xiàn)都可以滿足,就默認(rèn)使用第一個(gè)加載的,因?yàn)椴豢赡芡瑫r(shí)運(yùn)行多個(gè)實(shí)現(xiàn),當(dāng)然如果你沒有實(shí)現(xiàn),程序也是可以運(yùn)行的,會(huì)返回NOPLogger這個(gè)Logger的子類,不采取任何處理。不管事多個(gè)還是沒有具體的實(shí)現(xiàn),都不會(huì)影響程序的正常運(yùn)行。  回復(fù)  更多評(píng)論
            
          主站蜘蛛池模板: 武冈市| 蒙阴县| 邢台县| 维西| 五台县| 岗巴县| 周至县| 富裕县| 措勤县| 阜平县| 林周县| 玛曲县| 财经| 西乌珠穆沁旗| 项城市| 东兰县| 兰坪| 乌兰县| 开江县| 上栗县| 霍林郭勒市| 新邵县| 二连浩特市| 嘉善县| 滨海县| 延吉市| 讷河市| 邻水| 西青区| 东莞市| 涟水县| 牟定县| 迁西县| 林甸县| 铜梁县| 禄丰县| 宾川县| 文山县| 万山特区| 肃北| 阜南县|