上期我們分享了Java中if/else復雜邏輯的處理
本期我們將分享Java中日志的處理(上)
想必大家都用過日志,雖然日志看起來可有可無雾袱,但是等到出問題的時候旧巾,日志就派上了大用場祥山,所以說日志打得好不好,規(guī)范不規(guī)范剥扣,直接影響了解決生產(chǎn)環(huán)境故障的效率,日志打的不好铝穷,有可能影響環(huán)境的性能钠怯,也有可能影響排查問題的難易程度,有可能排查問題的時間比寫代碼的時間還有多曙聂。
那么我們就來分析下阿里Java開發(fā)手冊--日志規(guī)約第一條:
【強制】應用中不可直接使用日志系統(tǒng)(Log4j晦炊、Logback)中的 API,而應依賴使用日志框架 SLF4J 中的 API筹陵,使用門面模式的日志框架刽锤,有利于維護和各個類的日志處理方式統(tǒng)一。
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
private static final Logger logger = LoggerFactory.getLogger(Abc.class);
日志框架
Java中的日志框架分如下幾種:
Log4j
Apache Log4j是一個基于Java的日志記錄工具朦佩。它是由Ceki Gülcü首創(chuàng)的并思,現(xiàn)在則是Apache軟件基金會的一個項目。Log4j 2
Apache Log4j 2是apache開發(fā)的一款Log4j的升級產(chǎn)品语稠。Commons Logging
Apache基金會所屬的項目宋彼,是一套Java日志接口弄砍,之前叫Jakarta Commons Logging,后更名為Commons Logging输涕。Slf4j
類似于Commons Logging音婶,是一套簡易Java日志門面,本身并無日志的實現(xiàn)莱坎。(Simple Logging Facade for Java衣式,縮寫Slf4j)。Logback
一套日志組件的實現(xiàn)(slf4j陣營)檐什。Jul
(Java Util Logging),自Java1.4以來的官方日志實現(xiàn)碴卧。
使用示例
- Jul
import java.util.logging.Logger;
private static final Logger logger = Logger.getLogger("name");
...
try {
...
} catch (Exception e) {
logger.error(".....error");
}
if(logger.isDebugEnabled()) {
logger.debug("....." + name);
}
- Log4j
import org.apache.log4j.Logger;
private static final Logger logger = Logger.getLogger(Abc.class.getNeme());
...
try {
...
} catch (Exception e) {
logger.error(".....error");
}
if(logger.isDebugEnabled()) {
logger.debug("....." + name);
}
- Commons Logging
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
private static final Log logger = LogFactory.getLogger(Abc.class);
...
try {
...
} catch (Exception e) {
logger.error(".....error");
}
if(logger.isDebugEnabled()) {
logger.debug("....." + name);
}
- Slf4j
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
private static final Logger logger = LoggerFactory.getLogger(Abc.class);
...
try {
...
} catch (Exception e) {
logger.error(".....error {}", e.getMessage(), e);
}
logger.debug(".....{}", name);
-
Jul
- 不支持占位符
- 具體日志實現(xiàn)
-
Log4j
- 不支持占位符
- 具體日志實現(xiàn)
-
Logback
- 不支持占位符
- 具體日志實現(xiàn)
-
Commons Logging
- 不支持占位符
- 日志門面
-
Slf4j
- 支持占位符
- 日志門面
Slf4j中有一個很重要的特性:
占位符
,{}可以拼接任意字符串
乃正,相比如其他框架的優(yōu)點即不需要用+
來拼接字符串住册,也就不會創(chuàng)建新的字符串對象,所以像log4j中需要加isDebugEnabled()
的判斷就是這個道理瓮具,在slf4j中就不需要加判斷荧飞。
門面模式
門面(Facade)模式,又稱外觀模式名党,對外隱藏了系統(tǒng)的復雜性叹阔,并向客戶端提供了可以訪問的接口,門面模式的好處是將客戶端和子系統(tǒng)松耦合兑巾,方便子系統(tǒng)的擴展和維護条获。
正是門面模式這樣的特點,使用Slf4j門面蒋歌,不管日志組件使用的是log4j還是logback等等帅掘,對于調(diào)用者而言并不關(guān)心使用的是什么日志組件,而且對于日志組件的更換或者升級堂油,調(diào)用的地方也不要做任何修改修档。
源碼分析
此處應有代(zhang)碼(sheng):
首先使用靜態(tài)工廠來獲取Logger對象,傳入的class府框,最終會轉(zhuǎn)化為name吱窝,每個類的日志處理可能不同,所以根據(jù)傳入類的名字來判斷類的實現(xiàn)方式
public static Logger getLogger(Class clazz) {
return getLogger(clazz.getName());
}
public static Logger getLogger(String name) {
ILoggerFactory iLoggerFactory = getILoggerFactory();
return iLoggerFactory.getLogger(name);
}
真正核心的在getILoggerFactory()中迫靖,首先判斷初始化的狀態(tài)
INITIALIZATION_STATE
院峡,如果沒有初始化UNINITIALIZED
,那么會更改狀態(tài)為正在初始化ONGOING_INITIALIZATION
系宜,并執(zhí)行初始化performInitialization()
照激,初始化完成之后,判斷初始化的狀態(tài)盹牧,如果初始化成功SUCCESSFUL_INITIALIZATION
俩垃,那么會通過StaticLoggerBinder
獲取日志工廠getLoggerFactory()
励幼,這里又涉及到了單例模式
。
public static ILoggerFactory getILoggerFactory() {
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("org.slf4j.LoggerFactory could not be successfully initialized. See also http://www.slf4j.org/codes.html#unsuccessfulInit");
case ONGOING_INITIALIZATION:
return TEMP_FACTORY;
}
throw new IllegalStateException("Unreachable code");
}
接著我們分析
performInitialization
是如何初始化的口柳,首先是執(zhí)行bind()
方法苹粟,然后判斷如果狀態(tài)為初始化成功SUCCESSFUL_INITIALIZATION
,執(zhí)行版本檢查跃闹,主要是檢查jdk版本與slf4j的版本嵌削,看是否匹配。
private static final void performInitialization() {
bind();
if (INITIALIZATION_STATE == 3) {
versionSanityCheck();
}
}
bind()方法辣卒,首先獲取實現(xiàn)日志的加載路徑掷贾,檢查路徑是否合法,然后初始化StaticLoggerBinder的對象荣茫,尋找合適的實現(xiàn)方式使用。
private static final void bind() {
try {
Set staticLoggerBinderPathSet = findPossibleStaticLoggerBinderPathSet();
reportMultipleBindingAmbiguity(staticLoggerBinderPathSet);
StaticLoggerBinder.getSingleton();
INITIALIZATION_STATE = SUCCESSFUL_INITIALIZATION;
reportActualBinding(staticLoggerBinderPathSet);
emitSubstituteLoggerWarning();
} 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 http://www.slf4j.org/codes.html#StaticLoggerBinder for further details.");
} else {
failedBinding(ncde);
throw ncde;
}
} catch (NoSuchMethodError nsme) {
String msg = nsme.getMessage();
if ((msg != null) && (msg.indexOf("org.slf4j.impl.StaticLoggerBinder.getSingleton()") != -1)) {
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);
}
}
可以看出场靴,bind()方法中最重要的方法就是尋找實現(xiàn)方式
findPossibleStaticLoggerBinderPathSet
啡莉,具體方法實現(xiàn)如下:
private static Set findPossibleStaticLoggerBinderPathSet() {
Set staticLoggerBinderPathSet = new LinkedHashSet();
try {
ClassLoader loggerFactoryClassLoader = LoggerFactory.class.getClassLoader();
Enumeration paths;
Enumeration paths;
if (loggerFactoryClassLoader == null) {
paths = ClassLoader.getSystemResources(STATIC_LOGGER_BINDER_PATH);
} else {
paths = loggerFactoryClassLoader.getResources(STATIC_LOGGER_BINDER_PATH);
}
while (paths.hasMoreElements()) {
URL path = (URL)paths.nextElement();
staticLoggerBinderPathSet.add(path);
}
} catch (IOException ioe) {
Util.report("Error getting resources from path", ioe);
}
return staticLoggerBinderPathSet;
}
注意!旨剥!前方高能_中馈!
Slf4j的絕妙之處就在于此轨帜,類加載器加載類魄咕,也就是說尋找StaticLoggerBinder.class文件,然后只要實現(xiàn)了這個類的日志組件蚌父,都可以作為一種實現(xiàn)哮兰,如果有多個實現(xiàn),那么誰先加載就使用誰苟弛,這個地方涉及
JVM的類加載機制
橋接
- Slf4j與其他日志組件的橋接(Bridge)
-
slf4j-log4j12-1.7.13.jar
- log4j1.2版本的橋接器
-
slf4j-jdk14-1.7.13.jar
- java.util.logging的橋接器
-
slf4j-nop-1.7.13.jar
- NOP橋接器
-
slf4j-simple-1.7.13.jar
- 一個簡單實現(xiàn)的橋接器
-
slf4j-jcl-1.7.13.jar
- Jakarta Commons Logging 的橋接器. 這個橋接器將SLF4j所有日志委派給JCL
-
logback-classic-1.0.13.jar(requires logback-core-1.0.13.jar)
- slf4j的原生實現(xiàn)喝滞,logback直接實現(xiàn)了slf4j的接口,因此使用slf4j與logback的結(jié)合使用也意味更小的內(nèi)存與計算開銷
Slf4j Manual中有一張圖清晰的展示了接入方式膏秫,如下:
- Bridging legacy APIs(橋接遺留的api)
- log4j-over-slf4j-version.jar
* 將log4j重定向到slf4j
-
jcl-over-slf4j-version.jar
- 將commos logging里的Simple Logger重定向到slf4j
-
jul-to-slf4j-version.jar
- 將Java Util Logging重定向到slf4j
- 橋接注意事項
在使用slf4j橋接時要注意避免形成死循環(huán)右遭,在項目依賴的jar包中不要存在以下情況
- log4j-over-slf4j.jar和slf4j-log4j12.jar同時存在
- 從名字上就能看出,前者重定向給后者缤削,后者又委派給前者窘哈,會形成死循環(huán)
-
jul-to-slf4j.jar和slf4j-jdk14.jar同時存在
- 從名字上就能看出,前者重定向給后者亭敢,后者又委派給前者滚婉,會形成死循環(huán)
總結(jié)
-
為了更好的了解Slf4j,你需要了解:
- JVM類加載機制
- 設計模式:門面模式吨拗、橋接模式
-
簡單總結(jié)Slf4j的原理:
- 通過工廠類满哪,提供一個的接口婿斥,用戶可以通過這個門面,直接使用API實現(xiàn)日志的記錄哨鸭。
- 而具體實現(xiàn)由Slf4j來尋找加載民宿,尋找的過程,就是通過類加載加載org/slf4j/impl/StaticLoggerBinder.class的文件像鸡,只要實現(xiàn)了這個文件的日志實現(xiàn)系統(tǒng)活鹰,都可以作為一種實現(xiàn)方式。
- 如果找到很多種方式只估,那么就尋找一種默認的方式志群。
- 這就是日志接口的工作方式,簡單高效蛔钙,關(guān)鍵是完全解耦锌云,不需要日志實現(xiàn)部分提供任何的修改配置,只需要符合接口的標準就可以加載進來吁脱,有利于維護和各個類的日志處理方式統(tǒng)一桑涎。