1、日誌實現工具和日誌接口簡介html
java日誌工具主要包含兩個方面:日誌接口工具和日誌實現工具java
Apache 開發了功能強大的log4j,並申請歸入jdkandroid
- sun不接受apache 的申請,而是在jdk1.4中本身開發了Java.util.logging
- logback出自log4j的做者之手,用於替代log4j,Logback的內核重寫了log4j,在一些關鍵執行路徑上性能提高10倍以上,並且logback不只性能提高了,初始化內存加載也更小了。 logback很是天然實現了slf4j, logback-classic實現了slf4j,做爲slf4j的實現時無需適配器。
- slf4j 簡單日誌門面(Simple Logging Facade for Java)。 slf4j一樣是日誌接口,使用靜態綁定的方式支持log4j、jdk、logback等做爲日誌實現,也支持靜態綁定common-logging做爲中間層,而後經過common-logging的動態加載機制選擇日誌實現。使用SLF4J時,若是須要使用某一種日誌實現,那麼必須選擇正確的適配器,好比:slf4j-log4j12.jar, slf4j- jdk14.jar等
- JCL Java通用日誌接口 Apche common-logging。 commons-logging支持log4j、jdk 的Java.util.logging做爲其日誌實現,使用時經過動態加載的方式尋找。 commons-logging啓動後在 classpath 若是能找到log4j,則默認使用log4j 做爲日誌實現,若是沒有則使用JDK14Logger 實現,若是JDK版本低於1.4,則使用其內部提供的 SimpleLog 實現。JCL的缺點:Apache Common-Logging使用了ClassLoader尋找和載入底層的日誌庫。而OSGI中,不一樣的插件使用本身的ClassLoader。一個線程的ClassLoader在執行不一樣的插件時,其執行能力是不一樣的。OSGI的這種機制保證了插件互相獨立,然而確使Apache Common-Logging沒法工做。
- JCL 只提供 log 接口,具體的實現則在運行時動態尋找。slf4j一樣也是日誌接口,使用靜態綁定的方式支持log4j、jdk、logback等做爲日誌實現,也支持靜態綁定common-logging做爲中間層,而後經過common-logging的動態加載機制選擇日誌實現。
2、日誌接口slf4j源碼解析apache
一、引言api
在閱讀本小節前,最好先了解下log4j,logback,jdk-log,slf4j的知識,編寫一個可以打印日誌的demo就能夠了。app
日誌接口不是具體的日誌解決方案,它只服務於各類各樣的日誌系統 。而是一個抽象層( abstraction layer),它容許你在後臺使用任意一個日誌類庫。若是是在編寫供內外部均可以使用的API或者通用類庫,那麼你真不會但願使用你類庫的客戶端必須使用你選擇的日誌類庫。maven
log4j,logback,jdk-log都爲slfj提供了實現,在引入這個三個工具的slf4j的後工具
<dependency> <groupId>ch.qos.logback</groupId> <artifactId>logback-classic</artifactId> <version>1.1.7</version> </dependency> <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-log4j12</artifactId> <version>1.7.16</version> </dependency> <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-jdk14</artifactId> <version>1.5.6</version> </dependency>
這三個jar中都有StaticLoggerBindder這個類。(當依賴的jar包版本出現衝突(groupId和artifactId相同,但version不一樣)時,優先級規則爲:在依賴樹中越靠近項目根節點的jar優先級越高,若兩個jar在依賴樹中所處層次相同則在pom.xml中越早出現的jar優先級越高。性能
若是兩個jar裏面的package名和class名都相同,可是groupId或artifactId不一樣,例如log4j-1.2.17與橋接器log4j-over-slf4j-1.7.21就符合這樣的情況,程序加載org.apache.log4j.LogManager類的順序適用於上述優先級規則。)ui
logback有些不同,這點留到咱們後面再講。
至於爲何,咱們一步一步看。咱們在項目中引入slf4j-api,slf4j-xxx,xxx三個jar包以後,下面代碼就能夠正確的執行
import org.slf4j.Logger; import org.slf4j.LoggerFactory; public class App { public static Logger logger = LoggerFactory.getLogger(App.class); public static void main(String[] args) { logger.info("Hello {}!", "world"); } }
在上述代碼中並無引入log4j,logback,jdk-log相關的類,而當咱們去掉slf4j-xxx這個jar包以後,就會提示如下錯誤:
SLF4J: Failed to load class "org.slf4j.impl.StaticLoggerBinder".
咱們能夠猜想,在LoggerFactory或者Loger中引用到了org.slf4j.impl.StaticLoggerBinder。
2.獲取LoggerFactory
2.1 首先看getLogger(class a),這個方法又調用了getLogger(String name)的方法
public static Logger getLogger(String name) { ILoggerFactory iLoggerFactory = getILoggerFactory(); return iLoggerFactory.getLogger(name); }
2.2 代碼第一行獲取了一個iLoggerFactory,繼續點進去看
/** * Return the {@link ILoggerFactory} instance in use. * <p/> * <p/> * ILoggerFactory instance is bound with this class at compile time. * * @return the ILoggerFactory instance in use */ public static ILoggerFactory getILoggerFactory() { if (INITIALIZATION_STATE == UNINITIALIZED) { synchronized (LoggerFactory.class) { 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://jira.qos.ch/browse/SLF4J-97 return SUBST_FACTORY; } throw new IllegalStateException("Unreachable code"); }
if語句表明,若是沒有初始化的話,就進入一個同步塊執行performInitialization()的方法,若是init成功則返回一個經過StaticLoggerBindder建立的LoggerFactory。(這裏就結合前面提到問題考慮,爲何slf4j-jdk,slf4j-log4j,logback-classic中都包含StaticLoggerBindder類,以及爲引入這三個jar時,slf4j提示的異常)。
2.3 performInitialization()方法裏面調用了bind()方法,而後進行進行版本檢查,這裏咱們重點關注bind()方法。
private final static void bind() { try { Set<URL> staticLoggerBinderPathSet = null; // skip check under android, see also // http://jira.qos.ch/browse/SLF4J-328 if (!isAndroid()) { //找到全部的StaticLoggerBinder staticLoggerBinderPathSet = findPossibleStaticLoggerBinderPathSet(); /*若是有多個,則提示載入了多個StaticLoggerBinder 實現,可能引入了slf4j-jdk,slf4j-log4j, logback-classic中的兩個或者三個*/ /*若是三個都引入了,會提示: SLF4J: Found binding in [jar:file:/E:/maven/repository/ ch/qos/logback/logback-classic/1.1.7/logback-classic-1.1.7.jar!/org/slf4j/impl/StaticLoggerBinder.class] SLF4J: Found binding in [jar:file:/E:/maven/repository/ org/slf4j/slf4j-log4j12/1.7.16/slf4j-log4j12-1.7.16.jar!/org/slf4j/impl/StaticLoggerBinder.class] SLF4J: Found binding in [jar:file:/E:/maven/repository/ org/slf4j/slf4j-jdk14/1.5.6/slf4j-jdk14-1.5.6.jar!/org/slf4j/impl/StaticLoggerBinder.class] */ //提示有多個地方有StaticLoggerBinder reportMultipleBindingAmbiguity(staticLoggerBinderPathSet); } // the next line does the binding //獲取StaticLoggerBinder的大力 StaticLoggerBinder.getSingleton(); //設置狀態,初始化成功 INITIALIZATION_STATE = SUCCESSFUL_INITIALIZATION; //report實際的loggerFactory的類,例如 SLF4J: Actual binding is of type //[ch.qos.logback.classic.util.ContextSelectorStaticBinder] reportActualBinding(staticLoggerBinderPathSet); //這句應該是處理在初始化過程當中阻塞的輸入日誌事件 replayEvents(); // release all resources in SUBST_FACTORY SUBST_FACTORY.clear(); } catch (.....) {... } }
代碼很是長,不過咱們暫時只關心try 塊的代碼,先理清最主要的脈絡。staticLoggerBinderPathSet用來存儲 StaticLoggerBindder的路徑集合。
若是有多個路徑就提示異常。
2.4 而後看找StaticLoggerBindder路徑的代碼,仍是比較好理解的
// We need to use the name of the StaticLoggerBinder class, but we can't reference the class itself. private static String STATIC_LOGGER_BINDER_PATH = "org/slf4j/impl/StaticLoggerBinder.class"; 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); } //將path加入集合中 while (paths.hasMoreElements()) { URL path = paths.nextElement(); staticLoggerBinderPathSet.add(path); } } catch (IOException ioe) { Util.report("Error getting resources from path", ioe); } return staticLoggerBinderPathSet; }
STATIC_LOGGER_BINDER_PATH是類的路徑。首先獲取到LoggerFactory的類加載器。若是找不到,就調用ClassLoader的類方法,查找全部的資源;若是找到了LoggerFactory的類加載器,就根據類加載器找對應的路徑。而後將找到的路徑存入staticLoggerBinderPathSet集合中返回。
2.5 回到2.2小節的代碼,init成功以後直接返回執行下列代碼
return StaticLoggerBinder.getSingleton().getLoggerFactory();
slf4j中是沒有StaticLogerBinder,得去slf4j-jdk,slf4j-log4j,logback-classic中看。
咱們選擇log4j,代碼比較簡單,簡單的單例模式。
public class StaticLoggerBinder implements LoggerFactoryBinder { private static final StaticLoggerBinder SINGLETON = new StaticLoggerBinder(); public static final StaticLoggerBinder getSingleton() { return SINGLETON; } public static String REQUESTED_API_VERSION = "1.6.99"; // !final private static final String loggerFactoryClassStr = Log4jLoggerFactory.class.getName(); private final ILoggerFactory loggerFactory; private StaticLoggerBinder() { loggerFactory = new Log4jLoggerFactory(); try { @SuppressWarnings("unused") Level level = Level.TRACE; } catch (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"); } } public ILoggerFactory getLoggerFactory() { return loggerFactory; } public String getLoggerFactoryClassStr() { return loggerFactoryClassStr; } }
2.6 小節
slf4j-jdk,slf4j-log4j都有slf4j的slf4j logger接口的實現分別是 JDK14LoggerAdapter和Log4jLoggerAdapter,而logback自己就實現了slf4j,不須要額外的適配器。
後面該寫日誌橋接方面的知識