#0 系列目錄#html
#1 Slf4j# ##1.1 介紹## SLF4J,即簡單日誌門面(Simple Logging Facade for Java)。從設計模式的角度考慮,它是用來在log和代碼層之間起到門面的做用
。對用戶來講只要使用slf4j提供的接口,便可隱藏日誌的具體實現。這與jdbc和類似。使用jdbc也就避免了不一樣的具體數據庫。使用了slf4j能夠對客戶端應用解耦。由於當咱們在代碼實現中引入log日誌的時候,用的是接口,因此能夠實時的更具狀況來調換具體的日誌實現類。這就是slf4j的做用。java
SLF4J所提供的核心API是一些接口以及一個LoggerFactory的工廠類
。SLF4J提供了統一的記錄日誌的接口,只要按照其提供的方法記錄便可,最終日誌的格式、記錄級別、輸出方式等經過具體日誌系統的配置來實現,所以能夠在應用中靈活切換日誌系統。數據庫
配置SLF4J是很是簡單的一件事,只要將與你打算使用的日誌系統對應的jar包加入到項目中,SLF4J就會自動選擇使用你加入的日誌系統。
apache
##1.2 簡單使用##設計模式
/** * Slf4j 日誌門面接口 Test * @author taomk * @version 1.0 * @since 15-10-15 下午3:39 */ public class Slf4jFacadeTest { private static Logger logger = LoggerFactory.getLogger(Slf4jFacadeTest.class); public static void main(String[] args){ if(logger.isDebugEnabled()){ logger.debug("slf4j-log4j debug message"); } if(logger.isInfoEnabled()){ logger.debug("slf4j-log4j info message"); } if(logger.isTraceEnabled()){ logger.debug("slf4j-log4j trace message"); } } }
上述Logger接口、LoggerFactory類都是slf4j本身定義的。那麼,SLF4J是怎麼實現日誌綁定的?api
##1.3 日誌綁定##性能優化
private static Logger logger = LoggerFactory.getLogger(Slf4jFacadeTest.class);
public static Logger getLogger(String name) { ILoggerFactory iLoggerFactory = getILoggerFactory(); return iLoggerFactory.getLogger(name); }
上述獲取Log的過程大體分紅2個階段:服務器
下面來詳細說明:架構
又能夠分紅3個過程
:org/slf4j/impl/StaticLoggerBinder.class
類:LoggerFactory.javaprivate static String STATIC_LOGGER_BINDER_PATH = "org/slf4j/impl/StaticLoggerBinder.class"; private static Set<URL> findPossibleStaticLoggerBinderPathSet() { // use Set instead of list in order to deal with bug #138 // LinkedHashSet appropriate here because it preserves insertion order during iteration Set<URL> staticLoggerBinderPathSet = new LinkedHashSet<URL>(); try { ClassLoader loggerFactoryClassLoader = LoggerFactory.class.getClassLoader(); Enumeration<URL> 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; }
若是找到多個,則輸出 Class path contains multiple SLF4J bindings,表示有多個日誌實現與slf4j進行了綁定
。下面看下當出現多個StaticLoggerBinder的時候的輸出日誌(簡化了一些內容):LoggerFactory.javaapp
SLF4J: Class path contains multiple SLF4J bindings. SLF4J: Found binding in [slf4j-log4j12-1.7.12.jar!/org/slf4j/impl/StaticLoggerBinder.class] SLF4J: Found binding in [logback-classic-1.1.3.jar!/org/slf4j/impl/StaticLoggerBinder.class] SLF4J: Found binding in [slf4j-jdk14-1.7.12.jar!/org/slf4j/impl/StaticLoggerBinder.class] SLF4J: See http://www.slf4j.org/codes.html#multiple_bindings for an explanation. SLF4J: Actual binding is of type [org.slf4j.impl.Log4jLoggerFactory]
「隨機選取"
一個StaticLoggerBinder.class來建立一個單例:private final static void bind() { try { Set<URL> staticLoggerBinderPathSet = findPossibleStaticLoggerBinderPathSet(); // 打印搜索到的全部StaticLoggerBinder日誌 reportMultipleBindingAmbiguity(staticLoggerBinderPathSet); // the next line does the binding 隨機選取綁定 StaticLoggerBinder.getSingleton(); INITIALIZATION_STATE = SUCCESSFUL_INITIALIZATION; // 打印最終實際綁定StaticLoggerBinder日誌 reportActualBinding(staticLoggerBinderPathSet); fixSubstitutedLoggers(); } 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 " + NO_STATICLOGGERBINDER_URL + " for further details."); } else { 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; 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); } }
返回一個ILoggerFactory實例
:LoggerFactory.javapublic 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"); }
因此slf4j與其餘實際的日誌框架的集成jar包中,都會含有這樣的一個org/slf4j/impl/StaticLoggerBinder.class類文件
,而且提供一個ILoggerFactory的實現。
這就要看具體的ILoggerFactory類型了
,下面與Log4j集成來詳細說明。#2 Log4j介紹# Apache的一個開放源代碼項目,經過使用Log4j,咱們能夠控制日誌信息輸送的目的地是控制檯、文件、GUI組件、甚至是套接口服務器、NT的事件記錄器、UNIX Syslog守護進程等
;用戶也能夠控制每一條日誌的輸出格式
;經過定義每一條日誌信息的級別,用戶可以更加細緻地控制日誌的生成過程
。這些能夠經過一個配置文件來靈活地進行配置,而不須要修改程序代碼。具體詳細介紹,參見Log4j架構分析與實戰。
#3 Slf4j與Log4j集成# ##3.1 Maven依賴##
<!-- slf4j --> <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-api</artifactId> <version>1.7.12</version> </dependency> <!-- slf4j-log4j --> <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-log4j12</artifactId> <version>1.7.12</version> </dependency> <!-- log4j --> <dependency> <groupId>log4j</groupId> <artifactId>log4j</artifactId> <version>1.2.17</version> </dependency>
##3.2 使用案例##
log4j.rootLogger = debug, console log4j.appender.console = org.apache.log4j.ConsoleAppender log4j.appender.console.layout = org.apache.log4j.PatternLayout log4j.appender.console.layout.ConversionPattern = %-d{yyyy-MM-dd HH:mm:ss} %m%n
配置文件的詳細內容參見Log4J配置文件詳解。
private static Logger logger=LoggerFactory.getLogger(Log4jSlf4JTest.class); public static void main(String[] args){ if(logger.isDebugEnabled()){ logger.debug("slf4j-log4j debug message"); } if(logger.isInfoEnabled()){ logger.info("slf4j-log4j info message"); } if(logger.isTraceEnabled()){ logger.trace("slf4j-log4j trace message"); } }
slf4j: Logger logger=LoggerFactory.getLogger(Log4jSlf4JTest.class); log4j: Logger logger=Logger.getLogger(Log4jTest.class);
slf4j的Logger是slf4j定義的接口,而log4j的Logger是類。LoggerFactory是slf4j本身的類。
##3.3 原理分析## 先來看下slf4j-log4j12包中的內容:
org/slf4j/impl/StaticLoggerBinder.class類。
Log4jLoggerFactory。
來看下具體過程:
「org/slf4j/impl/StaticLoggerBinder.class"
這樣的類的url,而後就找到了slf4j-log4j12包中的StaticLoggerBinder。並建立出ILoggerFactory
,源碼以下:StaticLoggerBinder.getSingleton().getLoggerFactory();
以slf4j-log4j12中的StaticLoggerBinder爲例,建立出的ILoggerFactory爲Log4jLoggerFactory。
org.apache.log4j.Logger log4jLogger; if (name.equalsIgnoreCase(Logger.ROOT_LOGGER_NAME)) log4jLogger = LogManager.getRootLogger(); else log4jLogger = LogManager.getLogger(name); Logger newInstance = new Log4jLoggerAdapter(log4jLogger);
引起log4j1的加載配置文件,而後初始化
,最後返回一個org.apache.log4j.Logger log4jLogger,參見log4j1原生開發。log4jLogger封裝成Log4jLoggerAdapter,而Log4jLoggerAdapter是實現了slf4j的接口
,因此咱們使用的slf4j的Logger接口實例(這裏即Log4jLoggerAdapter)都會委託給內部的org.apache.log4j.Logger實例。#4 Slf4j與Log4j源碼分析# ##4.1 Slf4j初始化##
當前類類加載時或者顯示得執行調用LoggerFactory.getLogger()方法時
,觸發Slf4j初始化,並綁定具體日誌:Slf4jFacadeTest.javaprivate static Logger logger = LoggerFactory.getLogger(Slf4jFacadeTest.class);
public static Logger getLogger(String name) { // 1. 初始化LoggerFactory,綁定具體日誌,得到具體日誌的LoggerFactory。 ILoggerFactory iLoggerFactory = getILoggerFactory(); // 2. 根據具體日誌的LoggerFactory,觸發具體日誌的初始化並得到具體日誌的Logger對象; return iLoggerFactory.getLogger(name); }
public static ILoggerFactory getILoggerFactory() { // 1. 是否已經初始化了,不然進行初始化 if (INITIALIZATION_STATE == UNINITIALIZED) { INITIALIZATION_STATE = ONGOING_INITIALIZATION; performInitialization(); } switch (INITIALIZATION_STATE) { case SUCCESSFUL_INITIALIZATION: // 2. 成功初始化,則直接得到具體日誌的LoggerFactory 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"); }
private final static void performInitialization() { // 1. 綁定具體的日誌實現 bind(); if (INITIALIZATION_STATE == SUCCESSFUL_INITIALIZATION) { versionSanityCheck(); } }
private final static void bind() { try { // 1. 掃描查找「org/slf4j/impl/StaticLoggerBinder.class」 Set<URL> staticLoggerBinderPathSet = findPossibleStaticLoggerBinderPathSet(); // 2. 打印找到多個「org/slf4j/impl/StaticLoggerBinder.class」的日誌 reportMultipleBindingAmbiguity(staticLoggerBinderPathSet); // the next line does the binding 隨機選擇綁定,類加載器隨機選擇。 StaticLoggerBinder.getSingleton(); INITIALIZATION_STATE = SUCCESSFUL_INITIALIZATION; // 3. 打印綁定具體org/slf4j/impl/StaticLoggerBinder.class的日誌 reportActualBinding(staticLoggerBinderPathSet); fixSubstitutedLoggers(); } 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 " + NO_STATICLOGGERBINDER_URL + " for further details."); } else { 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; 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); } }
private static Set<URL> findPossibleStaticLoggerBinderPathSet() { // use Set instead of list in order to deal with bug #138 // LinkedHashSet appropriate here because it preserves insertion order during iteration Set<URL> staticLoggerBinderPathSet = new LinkedHashSet<URL>(); try { ClassLoader loggerFactoryClassLoader = LoggerFactory.class.getClassLoader(); Enumeration<URL> paths; if (loggerFactoryClassLoader == null) { // 1. 掃包查找「org/slf4j/impl/StaticLoggerBinder.class」 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; }
private static final StaticLoggerBinder SINGLETON = new StaticLoggerBinder(); public static final StaticLoggerBinder getSingleton() { // 1. 返回StaticLoggerBinder實例 return SINGLETON; } private StaticLoggerBinder() { // 2. StaticLoggerBinder初始化 loggerFactory = new Log4jLoggerFactory(); try { Level level = Level.TRACE; }cache (NoSuchFieldError nsfe) { Util.report("This version of SLF4J requires log4j version 1.2.12 or later. See also http://www.slf4j.org/codes.html#log4j_version"); } }
以上就是Slf4j初始化過程的源代碼,其初始化過程就是綁定具體日誌實現
;
##4.2 Log4j初始化## 這裏只在源碼層級作分析,不想看源碼,可直接參考具體詳細流程,請參見Log4j初始化分析。
初始化時機:前一小節Slf4j已提到,在iLoggerFactory.getLogger(name)時觸發Log4j初始化
。iLoggerFactory具體類型爲:Log4jLoggerFactory。
初始化步驟:
public Logger getLogger(String name) { Logger slf4jLogger = null; // protect against concurrent access of loggerMap synchronized (this) { slf4jLogger = (Logger) loggerMap.get(name); if (slf4jLogger == null) { // 1. 獲取Logej具體的Logger對象。 org.apache.log4j.Logger log4jLogger; if(name.equalsIgnoreCase(Logger.ROOT_LOGGER_NAME)) { log4jLogger = LogManager.getRootLogger(); } else { log4jLogger = LogManager.getLogger(name); } slf4jLogger = new Log4jLoggerAdapter(log4jLogger); loggerMap.put(name, slf4jLogger); } } return slf4jLogger; }
static { // By default we use a DefaultRepositorySelector which always returns 'h'. // 1. 初始化Logger倉庫,並添加一個RootLogger實例,默認日誌級別爲DEBUG。 Hierarchy h = new Hierarchy(new RootLogger((Level) Level.DEBUG)); repositorySelector = new DefaultRepositorySelector(h); /** Search for the properties file log4j.properties in the CLASSPATH. */ String override =OptionConverter.getSystemProperty(DEFAULT_INIT_OVERRIDE_KEY, null); // 2. 檢查系統屬性log4j.defaultInitOverride,若是該屬性被設置爲false,則執行初始化;不然(只要不是false,不管是什麼值,甚至沒有值,都是不然),跳過初始化。 // if there is no default init override, then get the resource // specified by the user or the default config file. if(override == null || "false".equalsIgnoreCase(override)) { String configurationOptionStr = OptionConverter.getSystemProperty( DEFAULT_CONFIGURATION_KEY, null); String configuratorClassName = OptionConverter.getSystemProperty( CONFIGURATOR_CLASS_KEY, null); URL url = null; // 3. 把系統屬性log4j.configuration的值賦給變量resource。若是該系統變量沒有被定義,則把resource賦值爲"log4j.properties"。注意:在apache的log4j文檔中建議使用定義log4j.configuration系統屬性的方法來設置默認的初始化文件是一個好方法。 // if the user has not specified the log4j.configuration // property, we search first for the file "log4j.xml" and then // "log4j.properties" if(configurationOptionStr == null) { url = Loader.getResource(DEFAULT_XML_CONFIGURATION_FILE); if(url == null) { url = Loader.getResource(DEFAULT_CONFIGURATION_FILE); } } else { try { // 4. 試圖把resource變量轉化成爲一個URL對象url。若是通常的轉化方法行不通,就調用org.apache.log4j.helpers.Loader.getResource(resource, Logger.class)方法來完成轉化。 url = new URL(configurationOptionStr); } catch (MalformedURLException ex) { // so, resource is not a URL: // attempt to get the resource from the class path url = Loader.getResource(configurationOptionStr); } } // If we have a non-null url, then delegate the rest of the // configuration to the OptionConverter.selectAndConfigure // method. if(url != null) { LogLog.debug("Using URL ["+url+"] for automatic log4j configuration."); try { // 5. 若是url以".xml"結尾,則調用方法DOMConfigurator.configure(url)來完成初始化;不然,則調用方法PropertyConfigurator.configure(url)來完成初始化。若是url指定的資源不能被得到,則跳出初始化過程。 OptionConverter.selectAndConfigure(url, configuratorClassName, LogManager.getLoggerRepository()); } catch (NoClassDefFoundError e) { LogLog.warn("Error during default initialization", e); } } else { LogLog.debug("Could not find resource: ["+configurationOptionStr+"]."); } } else { LogLog.debug("Default initialization of overridden by " + DEFAULT_INIT_OVERRIDE_KEY + "property."); } }
public static void selectAndConfigure(URL url, String clazz, LoggerRepository hierarchy) { Configurator configurator = null; String filename = url.getFile(); // 1. 若是url以".xml"結尾,則調用方法DOMConfigurator.configure(url)來完成初始化;不然,則調用方法PropertyConfigurator.configure(url)來完成初始化。若是url指定的資源不能被得到,則跳出初始化過程。 if(clazz == null && filename != null && filename.endsWith(".xml")) { clazz = "org.apache.log4j.xml.DOMConfigurator"; } if(clazz != null) { LogLog.debug("Preferred configurator class: " + clazz); configurator = (Configurator) instantiateByClassName(clazz, Configurator.class, null); if(configurator == null) { LogLog.error("Could not instantiate configurator ["+clazz+"]."); return; } } else { configurator = new PropertyConfigurator(); } configurator.doConfigure(url, hierarchy); }
##4.3 Log4j輸出日誌## 這裏只在源碼層級作分析,不想看源碼,可直接參考具體詳細流程,請參見Log4j輸出日誌。
Logger繼承自Category.java
)public void info(Object message) { // 1. 根據全局日誌等級threshold進行判斷,若是日誌等級低於threshold,不輸出日誌。 if(repository.isDisabled(Level.INFO_INT)) return; // 2. 根據當前logger配置的日誌等級level進行判斷,若是日誌等級低於當前logger配置的日誌等級,不輸出日誌。 if(Level.INFO.isGreaterOrEqual(this.getEffectiveLevel())) // 3. 將日誌信息封裝成LoggingEvent對象。 forcedLog(FQCN, Level.INFO, message, null); }
protected void forcedLog(String fqcn, Priority level, Object message, Throwable t) { // 1. 將LoggingEvent對象分發給全部的Appender。 callAppenders(new LoggingEvent(fqcn, this, level, message, t)); } public void callAppenders(LoggingEvent event) { int writes = 0; for(Category c = this; c != null; c=c.parent) { // Protected against simultaneous call to addAppender, removeAppender,... synchronized(c) { if(c.aai != null) { // 2. 將LoggingEvent對象分發給全部的Appender。 writes += c.aai.appendLoopOnAppenders(event); } if(!c.additive) { break; } } } if(writes == 0) { repository.emitNoAppenderWarning(this); } } public int appendLoopOnAppenders(LoggingEvent event) { int size = 0; Appender appender; if(appenderList != null) { size = appenderList.size(); for(int i = 0; i < size; i++) { appender = (Appender) appenderList.elementAt(i); appender.doAppend(event); } } return size; }
Filter處理和日誌信息格式化
。public synchronized void doAppend(LoggingEvent event) { if (closed) { LogLog.error("Attempted to append to closed appender named [" + name + "]."); return; } if (!isAsSevereAsThreshold(event.getLevel())) { return; } // Filter處理 Filter f = this.headFilter; FILTER_LOOP: while (f != null) { // 1. 有三種返回值 DENY、ACCEPT、NEUTRAL,DENY表示丟棄當前日誌信息,ACCEPT表示輸出當前日誌信息,NEUTRAL表示繼續下一個Filter。Filter只能在XML配置文件中使用,Properties文件中不支持。 switch (f.decide(event)) { case Filter.DENY: return; case Filter.ACCEPT: break FILTER_LOOP; case Filter.NEUTRAL: f = f.getNext(); } } this.append(event); } public void append(LoggingEvent event) { // Reminder: the nesting of calls is: // // doAppend() // - check threshold // - filter // - append(); // - checkEntryConditions(); // - subAppend(); if(!checkEntryConditions()) { return; } subAppend(event); } protected void subAppend(LoggingEvent event) { // 2. 日誌信息格式化:對日誌進行格式化處理。 this.qw.write(this.layout.format(event)); if (layout.ignoresThrowable()) { String[] s = event.getThrowableStrRep(); if (s != null) { int len = s.length; for (int i = 0; i < len; i++) { this.qw.write(s[i]); this.qw.write(Layout.LINE_SEP); } } } if (shouldFlush(event)) { // 3. 將日誌信息輸出至目的地(文件、數據庫或網格) this.qw.flush(); } }