幾乎任何應(yīng)用,一定是需要日志的。
那么,面對(duì)種類(lèi)繁多的日志框架和配置,我們?cè)摵稳ズ螐模?br>
1.前奏:我是在研究mybatis源碼的過(guò)程中才意識(shí)到需要搞明白日志原理這回事,因?yàn)閙ybatis(和一些其他開(kāi)源框架,比如rocketmq)都有自己的日志系統(tǒng),他們?cè)诳蚣軆?nèi)部都使用的是自己的日志API,那么,為什么他們不像我們平常那樣配置一個(gè)log4j呢?根本原因我也不太清楚,不過(guò)我猜測(cè)可能有這么一些理由,這些框架比較老,當(dāng)初還沒(méi)有slf4j這種事實(shí)上的標(biāo)準(zhǔn),另一方面,有一些特殊的定制化的日志。徹底研究清楚mybatis的日志系統(tǒng)之后,個(gè)人覺(jué)得這一塊設(shè)計(jì)得不太好,至少今天看來(lái),不太優(yōu)雅,因?yàn)楸緛?lái)一個(gè)slf4j就能搞定所有,非得在源碼中加入自己的org.apache.ibatis.logging這個(gè)包,里面包含一些適配器,雖然代碼并不復(fù)雜,但是有點(diǎn)多此一舉。
2.原理:slf4j是標(biāo)準(zhǔn),也是門(mén)面,他對(duì)用戶提供統(tǒng)一的API,而下方對(duì)接各個(gè)日志框架。這有點(diǎn)類(lèi)似JVM,我們Java開(kāi)發(fā)者使用統(tǒng)一的API,而JVM對(duì)接各個(gè)操作系統(tǒng)。嚴(yán)格意義上說(shuō)slf4j自身并不提供日志具體實(shí)現(xiàn)。圖片來(lái)自:
https://www.cnblogs.com/hanszhao/p/9754419.html
<https://www.cnblogs.com/hanszhao/p/9754419.html>
?
3.slf4j采用的是SPI機(jī)制,指定一個(gè)標(biāo)準(zhǔn)的目錄結(jié)構(gòu):org.slf4j.impl.StaticLoggerBinder,然第三方的框架都必須存在一個(gè)這樣的類(lèi),用于和slf4j建立關(guān)系,比如slf4j-simple.jar,logback,這兩個(gè)直接實(shí)現(xiàn)了slf4j的接口,而對(duì)于log4j這種則需要一個(gè)中間適配器slf4j-log4j12。于是乎,當(dāng)調(diào)用slf4j的Logger
logger =
LoggerFactory.getLogger(XXX.class)的時(shí)候,雖然使用的是slf4j的api,但是真正輸出日志的是具體的日志框架,這樣子做的好處就是,當(dāng)某一天你希望更換日志框架了,只需要把具體日志框架的jar包替換掉,不需要更改任何一行代碼,就能實(shí)現(xiàn)日志框架的切換。
4.slf4j是如何發(fā)現(xiàn)具體日志框架的,這就得意于spi機(jī)制,前面說(shuō)每個(gè)日志框架都需要存在一個(gè)org.slf4j.impl.StaticLoggerBinder類(lèi),log4j則是通過(guò)中間適配器slf4j-log4j12。當(dāng)調(diào)用LoggerFactory.getLogger的時(shí)候,就會(huì)去classpath中尋找StaticLoggerBinder這個(gè)類(lèi),如果不存在或者存在超過(guò)1個(gè),那么會(huì)報(bào)錯(cuò),classpath有且只能存在一個(gè)StaticLoggerBinder類(lèi)。
5.分析mybatis的日志框架:mybatis有一套屬于自己的日志系統(tǒng),日志api是:Log log =
LogFactory.getLog(xxx.class),于此同時(shí),封裝了幾個(gè)主流的日志框架適配器,包括:SLF4J | LOG4J | LOG4J2 |
JDK_LOGGING | COMMONS_LOGGING | STDOUT_LOGGING | NO_LOGGING,當(dāng)調(diào)用Log log =
LogFactory.getLog(xxx.class)時(shí),會(huì)初始化眾多適配器中的一個(gè),可以在mybatis的配置文件中通過(guò)logImpl指定具體的一個(gè),如果不指定那么默認(rèn)使用SLF4J,因?yàn)檫@里在LogFactory類(lèi)中的靜態(tài)代碼快第一個(gè)就是SLF4J:
static { tryImplementation(LogFactory::useSlf4jLogging);
tryImplementation(LogFactory::useCommonsLogging);
tryImplementation(LogFactory::useLog4J2Logging);
tryImplementation(LogFactory::useLog4JLogging);
tryImplementation(LogFactory::useJdkLogging);
tryImplementation(LogFactory::useNoLogging); }
假設(shè)使用默認(rèn)配置,那么就會(huì)初始化Slf4jImpl類(lèi),這個(gè)類(lèi)內(nèi)部有個(gè)代理log,這個(gè)代理log就是Logger logger =
LoggerFactory.getLogger(clazz),這就回歸到slf4j的標(biāo)準(zhǔn)使用方式上面來(lái)了,mybatis打印日志,其實(shí)就是代理對(duì)象在打印,而代理對(duì)象就是classpath中配置的具體日志框架。
6.分析log4j是如何與slf4j整合的:前面說(shuō)到,要使用log4j就必須引入slf4j-log4j12這個(gè)jar包,而這個(gè)jar包中同樣存在一個(gè)StaticLoggerBinder類(lèi),當(dāng)我們調(diào)用LoggerFactory.getLogger(clazz)的時(shí)候,同樣是初始化StaticLoggerBinder,然后調(diào)用利用ILoggerFactory創(chuàng)建一個(gè)log4j的Logger實(shí)例,代碼如下:
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 }
最關(guān)鍵的一行就是第27行Logger newInstance = new
Log4jLoggerAdapter(log4jLogger),slf4j的Logger對(duì)象實(shí)際上是一個(gè)log4j的適配器對(duì)象(也是代理對(duì)象),當(dāng)slf4j調(diào)用比如debug方法的時(shí)候,實(shí)際上是代理對(duì)象(也就是真實(shí)的log4j對(duì)象)在調(diào)用debug方法。
熱門(mén)工具 換一換
