Slf4j 日志框架適配原理
概要
看了?Java 日志體系詳解?后,相信大家對slf4j以及其他日志組件的關系有了一定理解。slf4j只是為日志的輸出提供了統(tǒng)一接口,并沒有具體的實現(xiàn),就好像JDBC一樣。那么,大家會不會好奇slf4j是怎么綁定/適配/橋接到log4j或者logback其他日志實現(xiàn)組件的呢?這篇文章為大家詳細講述。
適配過程原理
統(tǒng)計API接口,說明slf4j使用的是門面模式(Facade),然后我們就很容易猜測到大致的調用過程是,slf4j是通過自己的api去調用實現(xiàn)組件的api,這樣來完成適配的。我們重點看看是怎么做到適配的。
源碼基于slf4j-api.1.7.25
slf4j通用門面的實現(xiàn)
調用slf4j時我們都是使用它的api,首先我們需要獲取它的logger
一般大家使用slf4j都是這樣子的
import?org.slf4j.Logger;
import?org.slf4j.LoggerFactory;
private?Logger?logger?=?LoggerFactory.getLogger(LogTest.class);
getLogger
我們對getLogger()方法源碼跟蹤下去
public?static?Logger?getLogger(Class>?clazz)?{
??????Logger?logger?=?getLogger(clazz.getName());
??????if?(DETECT_LOGGER_NAME_MISMATCH)?{
??????????Class>?autoComputedCallingClass?=?Util.getCallingClass();
??????????if?(autoComputedCallingClass?!=?null?&&?nonMatchingClasses(clazz,?autoComputedCallingClass))?{
??????????????Util.report(String.format("Detected?logger?name?mismatch.?Given?name:?\"%s\";?computed?name:?\"%s\".",?logger.getName(),
??????????????????????????????autoComputedCallingClass.getName()));
??????????????Util.report("See?"?+?LOGGER_NAME_MISMATCH_URL?+?"?for?an?explanation");
??????????}
??????}
??????return?logger;
??}
??public?static?Logger?getLogger(String?name)?{
??????//獲取logger的工廠來生成logger
??????ILoggerFactory?iLoggerFactory?=?getILoggerFactory();
??????return?iLoggerFactory.getLogger(name);
??}
從ILoggerFactory的名字上來看,這是一個接口,而它又可以生成到具體實際的logger,那我們應該猜測到這個ILoggerFactory會跟其他日志實現(xiàn)相關,但是例如log4j,自己的實現(xiàn)肯定不會關心slf4j的呀,所以應該由適配jar包,即slf4j-log4j12.jar來實現(xiàn)。
繼續(xù)看代碼
public?static?ILoggerFactory?getILoggerFactory()?{
????//從ILoggerFactory的狀態(tài)可以看出,ILoggerFactory只會一次初始化
????if?(INITIALIZATION_STATE?==?UNINITIALIZED)?{
????????synchronized?(LoggerFactory.class)?{
????????????//同步語句?+?雙重判斷,防止多次初始化
????????????//如果還沒初始化,則進行初始化
????????????if?(INITIALIZATION_STATE?==?UNINITIALIZED)?{
????????????????INITIALIZATION_STATE?=?ONGOING_INITIALIZATION;
????????????????performInitialization();
????????????}
????????}
????}
????switch?(INITIALIZATION_STATE)?{
????//初始化成功,即綁定成功,則從StaticLoggerBinder獲取ILoggerFactory并返回
????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:
????????return?SUBST_FACTORY;
????}
????throw?new?IllegalStateException("Unreachable?code");
}
//對ILoggerFactory的狀態(tài)做說明
static?final?int?UNINITIALIZED?=?0;?//沒初始化
static?final?int?ONGOING_INITIALIZATION?=?1;?//正在初始化
static?final?int?FAILED_INITIALIZATION?=?2;?//初始化失敗
static?final?int?SUCCESSFUL_INITIALIZATION?=?3;?//初始化成功
static?final?int?NOP_FALLBACK_INITIALIZATION?=?4;?//無日志實現(xiàn)
bind
performInitialization()方法看來是重點
private?final?static?void?performInitialization()?{
????bind();
????if?(INITIALIZATION_STATE?==?SUCCESSFUL_INITIALIZATION)?{
????????versionSanityCheck();
????}
}
bind()方法
private?final?static?void?bind()?{
????try?{
????????Set?staticLoggerBinderPathSet?=?null;
????????if?(!isAndroid())?{
????????????//找出可能綁定的日志的path,其實即StaticLoggerBinder.class文件
????????????staticLoggerBinderPathSet?=?findPossibleStaticLoggerBinderPathSet();
????????????//如果找出多個的話則打印錯誤信息。(等下會演示)
????????????reportMultipleBindingAmbiguity(staticLoggerBinderPathSet);
????????}
????????//通過獲取單例來做初始化??????????
????????StaticLoggerBinder.getSingleton();
????????INITIALIZATION_STATE?=?SUCCESSFUL_INITIALIZATION;
????????//打印實際綁定的那個日志實現(xiàn)。(等下會演示)
????????reportActualBinding(staticLoggerBinderPathSet);
????????fixSubstituteLoggers();
????????replayEvents();
????????//?release?all?resources?in?SUBST_FACTORY
????????SUBST_FACTORY.clear();
????}?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);
????}
}
StaticLoggerBinder類
findPossibleStaticLoggerBinderPathSet()方法
從hard code看重要性,org/slf4j/impl/StaticLoggerBinder.class就是slf4j日志適配的關鍵
//hard?code
private?static?String?STATIC_LOGGER_BINDER_PATH?=?"org/slf4j/impl/StaticLoggerBinder.class";
static?Set?findPossibleStaticLoggerBinderPathSet()?{
????Set?staticLoggerBinderPathSet?=?new?LinkedHashSet();
????try?{
????????//獲取LoggerFactory,即slf4j-apoi的類加載器
????????ClassLoader?loggerFactoryClassLoader?=?LoggerFactory.class.getClassLoader();
????????Enumeration?paths;
????????//為null說明是由Bootstrap?Classloader加載的,則轉為App?Classloader去加載
????????if?(loggerFactoryClassLoader?==?null)?{
????????????paths?=?ClassLoader.getSystemResources(STATIC_LOGGER_BINDER_PATH);
????????}?else?{
????????????//用跟slf4j一樣的Classloader去加載
????????????paths?=?loggerFactoryClassLoader.getResources(STATIC_LOGGER_BINDER_PATH);
????????}
????????while?(paths.hasMoreElements())?{
????????????URL?path?=?paths.nextElement();
????????????staticLoggerBinderPathSet.add(path);
????????}
????}?catch?(IOException?ioe)?{
????????Util.report("Error?getting?resources?from?path",?ioe);
????}
????return?staticLoggerBinderPathSet;
}
從類加載器的用法說明,org/slf4j/impl/StaticLoggerBinder.class要跟slf4j-api.jar包在同一個類加載器中,一般來說即要求放在同一路徑下比較穩(wěn)妥,當然也可以通過-classpath來指定。
前面我們已經(jīng)猜測org/slf4j/impl/StaticLoggerBinder應該是由各種適配器來實現(xiàn)的,我們來看看

在IDE的類搜索,可以找到兩個StaticLoggerBinder

那是因為我本機依賴了
<dependency>
????<groupId>ch.qos.logbackgroupId>
????<artifactId>logback-classicartifactId>
????<version>${logback.version}version>
dependency>
<dependency>
????<groupId>org.slf4jgroupId>
????<artifactId>slf4j-log4j12artifactId>
????<version>${slf4j.version}version>
dependency>
所以只是看到logback和log4j的適配器包。slf4j是對每一種日志實現(xiàn)都有對應的一個適配實現(xiàn)。適配器包的具體內容我們等下再看。(PS:這不是一個好的依賴配置,等下會說)
到這里我們已經(jīng)找到了StaticLoggerBinder類了,StaticLoggerBinder是由各自的slf4j適配器包提供的。
這里有個trick,既然StaticLoggerBinder在slf4j-api有,也在其他logback-classic或slf4j-log4j12有,那么怎么確保JVM只加載到適配器包中的StaticLoggerBinder?其實看看slf4j代碼的pom.xml就發(fā)現(xiàn),答案是打包時是沒有StaticLoggerBinder打進去的,這樣slf4j-api.jar包是沒有StaticLoggerBinder類的,JVM在找類時只會找到其他jar包的StaticLoggerBinder。
我們剛剛的源碼到bind()方法的這一句
StaticLoggerBinder.getSingleton();
這一句其實已經(jīng)是調用適配包的代碼,我們將會看看logback和log4j對應StaticLoggerBinder類的代碼。
對logback適配實現(xiàn)
從上面的依賴我們看出,為什么slf4j對logback的適配是在logback-classic.jar包呢?logback-classic應該是logback的核心包才對,不應該關心slf4j的。那是因為slf4j和logback是同一個作者,所以才說logback是天然集成slf4j的。
我們看看logback-classic.jar中的StaticLoggerBinder
static?{
????SINGLETON.init();
}
public?static?StaticLoggerBinder?getSingleton()?{
????return?SINGLETON;
}
void?init()?{
????try?{
????????try?{
????????????new?ContextInitializer(defaultLoggerContext).autoConfig();
????????}?catch?(JoranException?je)?{
????????????Util.report("Failed?to?auto?configure?default?logger?context",?je);
????????}
????????//?logback-292
????????if?(!StatusUtil.contextHasStatusListener(defaultLoggerContext))?{
????????????StatusPrinter.printInCaseOfErrorsOrWarnings(defaultLoggerContext);
????????}
????????contextSelectorBinder.init(defaultLoggerContext,?KEY);
????????initialized?=?true;
????}?catch?(Exception?t)?{?//?see?LOGBACK-1159
????????Util.report("Failed?to?instantiate?["?+?LoggerContext.class.getName()?+?"]",?t);
????}
}
上面的就是logback的初始化了。
public?ILoggerFactory?getLoggerFactory()?{
????if?(!initialized)?{
????????return?defaultLoggerContext;
????}
????if?(contextSelectorBinder.getContextSelector()?==?null)?{
????????throw?new?IllegalStateException("contextSelector?cannot?be?null.?See?also?"?+?NULL_CS_URL);
????}
????return?contextSelectorBinder.getContextSelector().getLoggerContext();
}
getLoggerFactory()方法會返回logback的LoggerContext,而LoggerContext是繼承slf4j的ILoggerFactory的,這樣就適配到slf4j。
Logger是從LoggerFactory取出的。
看看LoggerContext的getLogger()方法
public?final?Logger?getLogger(final?Class>?clazz)?{
????return?getLogger(clazz.getName());
}
@Override
public?final?Logger?getLogger(final?String?name)?{
????if?(name?==?null)?{
????????throw?new?IllegalArgumentException("name?argument?cannot?be?null");
????}
????//?if?we?are?asking?for?the?root?logger,?then?let?us?return?it?without
????//?wasting?time
????if?(Logger.ROOT_LOGGER_NAME.equalsIgnoreCase(name))?{
????????return?root;
????}
????int?i?=?0;
????Logger?logger?=?root;
????//?check?if?the?desired?logger?exists,?if?it?does,?return?it
????//?without?further?ado.
????Logger?childLogger?=?(Logger)?loggerCache.get(name);
????//?if?we?have?the?child,?then?let?us?return?it?without?wasting?time
????if?(childLogger?!=?null)?{
????????return?childLogger;
????}
????//?if?the?desired?logger?does?not?exist,?them?create?all?the?loggers
????//?in?between?as?well?(if?they?don't?already?exist)
????String?childName;
????while?(true)?{
????????int?h?=?LoggerNameUtil.getSeparatorIndexOf(name,?i);
????????if?(h?==?-1)?{
????????????childName?=?name;
????????}?else?{
????????????childName?=?name.substring(0,?h);
????????}
????????//?move?i?left?of?the?last?point
????????i?=?h?+?1;
????????synchronized?(logger)?{
????????????childLogger?=?logger.getChildByName(childName);
????????????if?(childLogger?==?null)?{
????????????????childLogger?=?logger.createChildByName(childName);
????????????????loggerCache.put(childName,?childLogger);
????????????????incSize();
????????????}
????????}
????????logger?=?childLogger;
????????if?(h?==?-1)?{
????????????return?childLogger;
????????}
????}
}
這里涉及了logback很多邏輯,我們不太需要理會。這里主要看logback的Logger其實是繼承了slf4j的Logger,這樣就適配到slf4j。
對log4j配置實現(xiàn)
看了logback的適配,就猜到log4j的也差不多
slf4j-log4j12的StaticLoggerBinder
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;
??}
Log4jLoggerFactory()是繼承了slf4j的ILoggerFactory。繼續(xù)看getLogger方法。
public?Logger?getLogger(String?name)?{
????Logger?slf4jLogger?=?loggerMap.get(name);
????if?(slf4jLogger?!=?null)?{
????????return?slf4jLogger;
????}?else?{
????????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);
????????Logger?oldInstance?=?loggerMap.putIfAbsent(name,?newInstance);
????????return?oldInstance?==?null???newInstance?:?oldInstance;
????}
}
這里又是把log4j的Logger包裝成slf4j的Logger,適配到slf4j。
圖解

畫了個圖總結一下上面代碼說的類關系,大家感受一下。
總結
slf4j的適配原理是通過適配包的org/slf4j/impl/StaticLoggerBinder來做轉承,適配包通過繼承和使用slf4j-api的ILoggerFactory和Logger來完成適配。
在最新的版本(我看的是1.8.0)已經(jīng)改為使用Java的SPI機制來實現(xiàn),StaticLoggerBinder類已經(jīng)不用了,改為SLF4JServiceProvider,這樣就真正的面向接口編程了,不用打包時忽略StaticLoggerBinder。
source:?//albenw.github.io/posts/e31dfd0e/

喜歡,在看
