幾乎任何應用,必定是須要日誌的。html
那麼,面對種類繁多的日誌框架和配置,咱們該何去何從?java
1.前奏:我是在研究mybatis源碼的過程當中才意識到須要搞明白日誌原理這回事,由於mybatis(和一些其餘開源框架,好比rocketmq)都有本身的日誌系統,他們在框架內部都使用的是本身的日誌API,那麼,爲何他們不像咱們日常那樣配置一個log4j呢?根本緣由我也不太清楚,不過我猜想可能有這麼一些理由,這些框架比較老,當初尚未slf4j這種事實上的標準,另外一方面,有一些特殊的定製化的日誌。完全研究清楚mybatis的日誌系統以後,我的以爲這一塊設計得不太好,至少今天看來,不太優雅,由於原本一個slf4j就能搞定全部,非得在源碼中加入本身的org.apache.ibatis.logging這個包,裏面包含一些適配器,雖然代碼並不複雜,可是有點畫蛇添足。apache
2.原理:slf4j是標準,也是門面,他對用戶提供統一的API,而下方對接各個日誌框架。這有點相似JVM,咱們Java開發者使用統一的API,而JVM對接各個操做系統。嚴格意義上說slf4j自身並不提供日誌具體實現。圖片來自:http://www.javashuo.com/article/p-ujrqvegx-mo.htmlapi
3.slf4j採用的是SPI機制,指定一個標準的目錄結構:org.slf4j.impl.StaticLoggerBinder,然第三方的框架都必須存在一個這樣的類,用於和slf4j創建關係,好比slf4j-simple.jar,logback,這兩個直接實現了slf4j的接口,而對於log4j這種則須要一箇中間適配器slf4j-log4j12。因而乎,當調用slf4j的Logger logger = LoggerFactory.getLogger(XXX.class)的時候,雖然使用的是slf4j的api,可是真正輸出日誌的是具體的日誌框架,這樣子作的好處就是,當某一天你但願更換日誌框架了,只須要把具體日誌框架的jar包替換掉,不須要更改任何一行代碼,就能實現日誌框架的切換。mybatis
4.slf4j是如何發現具體日誌框架的,這就得意於spi機制,前面說每一個日誌框架都須要存在一個org.slf4j.impl.StaticLoggerBinder類,log4j則是經過中間適配器slf4j-log4j12。當調用LoggerFactory.getLogger的時候,就會去classpath中尋找StaticLoggerBinder這個類,若是不存在或者存在超過1個,那麼會報錯,classpath有且只能存在一個StaticLoggerBinder類。框架
5.分析mybatis的日誌框架:mybatis有一套屬於本身的日誌系統,日誌api是:Log log = LogFactory.getLog(xxx.class),於此同時,封裝了幾個主流的日誌框架適配器,包括:SLF4J | LOG4J | LOG4J2 | JDK_LOGGING | COMMONS_LOGGING | STDOUT_LOGGING | NO_LOGGING,當調用Log log = LogFactory.getLog(xxx.class)時,會初始化衆多適配器中的一個,能夠在mybatis的配置文件中經過logImpl指定具體的一個,若是不指定那麼默認使用SLF4J,由於這裏在LogFactory類中的靜態代碼快第一個就是SLF4J:spa
static { tryImplementation(LogFactory::useSlf4jLogging); tryImplementation(LogFactory::useCommonsLogging); tryImplementation(LogFactory::useLog4J2Logging); tryImplementation(LogFactory::useLog4JLogging); tryImplementation(LogFactory::useJdkLogging); tryImplementation(LogFactory::useNoLogging); }
假設使用默認配置,那麼就會初始化Slf4jImpl類,這個類內部有個代理log,這個代理log就是Logger logger = LoggerFactory.getLogger(clazz),這就回歸到slf4j的標準使用方式上面來了,mybatis打印日誌,其實就是代理對象在打印,而代理對象就是classpath中配置的具體日誌框架。操作系統
6.分析log4j是如何與slf4j整合的:前面說到,要使用log4j就必須引入slf4j-log4j12這個jar包,而這個jar包中一樣存在一個StaticLoggerBinder類,當咱們調用LoggerFactory.getLogger(clazz)的時候,一樣是初始化StaticLoggerBinder,而後調用利用ILoggerFactory建立一個log4j的Logger實例,代碼以下:debug
1 public class Log4jLoggerFactory implements ILoggerFactory { 2 3 // key: name (String), value: a Log4jLoggerAdapter; 4 ConcurrentMap<String, Logger> loggerMap; 5 6 7 public Log4jLoggerFactory() { 8 loggerMap = new ConcurrentHashMap<String, Logger>(); 9 } 10 11 /* 12 * (non-Javadoc) 13 * 14 * @see org.slf4j.ILoggerFactory#getLogger(java.lang.String) 15 */ 16 public Logger getLogger(String name) { 17 Logger slf4jLogger = loggerMap.get(name); 18 if (slf4jLogger != null) { 19 return slf4jLogger; 20 } else { 21 org.apache.log4j.Logger log4jLogger; 22 if(name.equalsIgnoreCase(Logger.ROOT_LOGGER_NAME)) 23 log4jLogger = LogManager.getRootLogger(); 24 else 25 log4jLogger = LogManager.getLogger(name); 26 27 Logger newInstance = new Log4jLoggerAdapter(log4jLogger); 28 Logger oldInstance = loggerMap.putIfAbsent(name, newInstance); 29 return oldInstance == null ? newInstance : oldInstance; 30 } 31 } 32 }
最關鍵的一行就是第27行Logger newInstance = new Log4jLoggerAdapter(log4jLogger),slf4j的Logger對象其實是一個log4j的適配器對象(也是代理對象),當slf4j調用好比debug方法的時候,其實是代理對象(也就是真實的log4j對象)在調用debug方法。設計