Any problem in computer science can be solved by anther layer of indirection.
計算機科學領域的任何問題都可以通過增加一個間接的中間層來解決
日志門面存在的必要性
在java生態(tài)中,開始是沒有日志的官方標準的,而是社區(qū)先出現(xiàn)了各種優(yōu)秀的日志框架(各種日志框架的出現(xiàn)順序如下圖),而且彼此之間還不兼容;這樣的話就會當在項目中引入的其他jar使用的日志框架和自己使用的日志框架不一致的時候,就會出現(xiàn)混亂,很難維護。所以,我們需要一個中間的抽象層,來隱藏各個日志框架的差異性,讓對外的使用者對真正的底層日志實現(xiàn)無感,這樣日志門面就出現(xiàn)了。

日志門面擔當--SLF4J
1、初識SLF4J
SLF4J的意思是酸辣粉?呸,當然不是,你個吃貨;它的英文全稱是:Simple Logging Facade for Java ,簡單的日志門面。我們先來看看SLF4J官網(wǎng)的一張經(jīng)典的圖:

這張圖可以說是十分的詳細了,不僅很好的說明了SLF4J是一個抽象層,而且還告訴了我們SLF4J如何和市面上各種各樣的具體日志實現(xiàn)搭配使用:我們可以看到slf4j處于第二層,是直接面向我們應用的,也就是直接面向我們API調用工程師的;但是,我們發(fā)現(xiàn)有的日志框架和slf4j搭配使用只需要三層(比如logback,slf4j-simple等),而有的卻需要四層(比如reload4,jul等),這又是為什么呢?
其實,這不就是我們前面說的原因,開始日志是沒有規(guī)范的,各個社區(qū)自己玩自己的,日志門面SLF4J是后面出現(xiàn)的,有的日志框架在你SLF4J出現(xiàn)之前就有的,怎么遵從你slf4j的規(guī)范呢?是的,采用適配器模式,添加一個適配層幫助是配到SLF4J規(guī)范;而那些開始就遵守SLF4J規(guī)范的框架就不需要適配層的存在了,所以他們只需要單層就可以了。
2、SLF4J的規(guī)范
好了,說明了日志門面的作用后,我們來具體看看SLF4J指定的規(guī)范是怎么樣的,了解了規(guī)范后,我們就可以在這個規(guī)范下實現(xiàn)自己的日志框架了,想想就很屌的樣子,一起來看看吧:)
想想我們一般獲取到logger實例是通過什么樣的方式呢?我猜大家肯定會說:使用注解@SLF4J,確實,Lombook提供的這個編譯期注解幫我們省去了自己編寫如下代碼來獲取logger實例了:
Logger log = LoggerFactory.getLogger(Class<?> clazz);
LoggerFactory是SLF4J提供的類,它可以幫助我們獲取到真正的日志打印對象Logger,那它到底提供了怎樣的規(guī)范,可以獲取到真正的Logger呢?我們一起來看看代碼吧
public static Logger getLogger(String name) {
ILoggerFactory iLoggerFactory = getILoggerFactory();
return iLoggerFactory.getLogger(name);
}
LoggerFactory.getLogger的內(nèi)部調用了上面靜態(tài)的getLogger方法,看下這個方法的實現(xiàn),這不就是一個抽象個工廠的模式嗎?工廠的規(guī)范由SLF4J提供,角色的規(guī)范也由SLF4J提供,那么我們自己的日志框架只要依據(jù)工廠規(guī)范來實現(xiàn)自己的具體工廠,并且依據(jù)角色的規(guī)范來實現(xiàn)自己的具體日志實現(xiàn)類就可以了,那么還剩下一個問題就是:因為對于應用來說直接面向的僅僅是SLF4J的API,那么SLF4J如何發(fā)現(xiàn)第三方實現(xiàn)的具體工廠和日志實現(xiàn)類呢?其實,對于這種可插拔的功能,我們很容易想到使用SPI機制來實現(xiàn),但是SLF4J究竟是如何來實現(xiàn)的呢,我們繼續(xù)往下追蹤代碼:
來看看getILoggerFactory是如何拿到具體工廠的:
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");
}
這段代碼的前半部分是進行初始化,去尋找具體的日志工廠,而后面的switch...case...是來根據(jù)初始化后的不同狀態(tài)來進行返回一些默認的日志工廠或者真正的日志工廠;先來看最重要的初始化方法,初始化方法里面主要的就是bind方法來進行綁定具體的日志工廠實現(xiàn)類:
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()) {
staticLoggerBinderPathSet = findPossibleStaticLoggerBinderPathSet();
reportMultipleBindingAmbiguity(staticLoggerBinderPathSet);
}
// the next line does the binding
StaticLoggerBinder.getSingleton();
INITIALIZATION_STATE = SUCCESSFUL_INITIALIZATION;
reportActualBinding(staticLoggerBinderPathSet);
} 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.contains("org.slf4j.impl.StaticLoggerBinder.getSingleton()")) {
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);
} finally {
postBindCleanUp();
}
}
SLF4J去探查具體的日志實現(xiàn)框架的邏輯就發(fā)生在findPossibleStaticLoggerBinderPathSet方法中,在方法中SLF4J會去加載org.slf4j.impl.StaticLoggerBinder.java這個類,并且會調用StaticLoggerBinder的靜態(tài)方法getSingleton來進行綁定,這就是SLF4J提供的綁定規(guī)范了。
所以當我們自己的日志框架選擇SLF4J作為日志門面,那么我們就必須要在org/slf4j/impl路徑下提供一個的StaticLoggerBinder類(建議實現(xiàn)SLF4J提供的LoggerFactoryBinder接口)并要包含靜態(tài)的getSingleton方法來實現(xiàn)綁定(其實就是返回單例的StaticLoggerBinder對象)。
3.特殊情況
當然,在這期間也會出現(xiàn)一些問題:比如如果我引入了多個第三方日志框架,探查到了多個org.slf4j.impl.StaticLoggerBinder.java類該怎么辦呢? 還有就是:如果我們一個日志實現(xiàn)框架也沒有引入,那么就不會探查到任何org.slf4j.impl.StaticLoggerBinder.java類,又會發(fā)生什么情況呢? 這些問題SLF4J都有考慮到,也都幫我們提供了解決方案如下:
如果我們在項目中引入了多個第三方的日志具體實現(xiàn),那么SLF4J就會探查到多個org/slf4j/impl/StaticLoggerBinder.class,這時reportMultipleBindingAmbiguity方法會打印出日志提示,而且reportActualBinding會打印出日志告知最終采用的的StaticLoggerBinder。
而當我們沒有引入任何的具體日志實現(xiàn)類的時候,也就是SLF4J沒有探查到org/slf4j/impl/StaticLoggerBinder.class時,初始化的狀態(tài)會被賦值為NOP_FALLBACK_INITIALIZATION(INITIALIZATION_STATE = NOP_FALLBACK_INITIALIZATION),這樣就會返回SLF4J默認實現(xiàn)的NOPLoggerFactory日志工廠,但是這個日志工廠提供的日志實現(xiàn)類打印日志時,不做任何操作,沒有任何輸出的。
寫在最后
以上只是簡單的介紹了下SLF4J作為日志門面的原理,其中SLF4J中還有一些有趣的點,比如說MDC(Mapped Diagnostic Context),還有為web應用提供的MDCInsertingServletFilter等等。所以還是希望大家有時間多看看SLF4J官網(wǎng)的內(nèi)容。
另外,我們知道logback是SLF4J的直接實現(xiàn),而且作者都是同一個人,在log4j2出來之前,SLF4J+logback可謂是日志的最佳配方,畢竟Springboot默認提供的就是它兩。那么如何在SLF4J的規(guī)范下編寫具體的日志實現(xiàn)框架,logback是我們不得不去學習的一個日志實現(xiàn),考慮篇幅和時間為問題,關于logback的研究我們后續(xù)再一起研究,后會有期。