1.Java日志歷史
Java 擁有功能和性能都非常強大的日志庫嗤无,但不幸的是,這樣的日志庫有不止一個——相信每個Java程序員都曾經迷失在JUL(Java Util Log), JCL(Commons Logging), Log4j, SLF4J, Logback怜庸,Log4j2 等等的迷宮中当犯。讓我們回顧下講講這段“腥風血雨”的歷史。
-
Java Util Log
來自官方JDK割疾,帶著標準和權威的光環(huán)嚎卫,小名:JUL。從JDK1.4 才開始加入(2002年)杈曲,當時各種第三方日志組件及其盛行驰凛,且JUL性能和使用的確又沒有其他組件方便。雖然JDK1.5對其進行了改進担扑,但還是不影響很多項目不選用該組件的命運恰响。
Log4j 1.x
在JUL推出的前一年,Gülcü 發(fā)布了Log4j 1.x涌献,雖然進不了JDK胚宦,但是Log4j 1.x進入了Apache 基金會頂級項目。Log4j 在設計上非常優(yōu)秀燕垃,對后續(xù)的 Java Log 框架有長久而深遠的影響枢劝,也產生了Log4c, Log4s, Log4perl 等到其他語言的移植。但是卜壕,后浪推前浪您旁,前浪的性能終究還是不斷被后期的日志框架趕超,比如后期的Logback
和Log4j2
轴捎。-
JCL
那么問題來了鹤盒,JDK官方自帶JUL,第三方有Log4J侦副,不同項目或者開源Jar侦锯,采用了不同的日志實現(xiàn)庫,那么是不是意味著要整合使用秦驯,得寫多個配置文件呢尺碰。正式這個問題,帶來了JCL的出現(xiàn)。JCL亲桥,大名:Commons Logging洛心,同樣也是Apache下的項目,但是JCL 是一個Log Facade(門面Api)两曼,只提供 Log API皂甘,不提供實現(xiàn)。在程序中日志創(chuàng)建和記錄都是用JCL中的接口悼凑,在真正運行時偿枕,然后有適配器Adapter 來使用 Log4j 或者 JUL 作為Log 實現(xiàn)。(當前ClassPath中有什么實現(xiàn)户辫,如果有Log4j 就是用 Log4j, 如果啥都沒有就是用 JDK 的 JUL)渐夸。是不是感覺從面向對象的高度,JCL有種很先進的感覺渔欢。這就是面向接口編程的體現(xiàn)墓塌。這樣,在你的項目中奥额,如果用Log4j, 就添加 Log4j 的jar包進去苫幢,然后寫一個 Log4j 的配置文件;如果喜歡用JUL垫挨,就只需要寫個 JUL 的配置文件韩肝。如果有其他的新的日志庫出現(xiàn),也只需要它提供一個Adapter九榔,運行的時候把這個日志庫的 jar 包加進去哀峻。
JCL
合久必分,分久必合哲泊。歷史就是這樣剩蟀,日志組件在接下去的歷史演進中,又出現(xiàn)了跌宕起伏切威。一種平衡替換另一種平衡育特。
-
SLF4J/Logback
Gülcü (對頭,又是ta)認為 JCL 的 API 設計得不好先朦,容易讓使用者寫出性能有問題的代碼且预,Gülcü ,不安于現(xiàn)狀烙无,不基于JCL添加實現(xiàn)類,而是創(chuàng)立了SLF4J 和 Logback項目遍尺,目的就是為了提高日志組件的性能截酷。SLF4J的全稱:Simple Logging Facade for Java,看其意思就是門面API乾戏,而Logback作為其實現(xiàn)類迂苛。當然 Logback 則是作為 Log4j 的繼承者來開發(fā)的三热,提供了性能更好的實現(xiàn),異步 logger三幻,F(xiàn)ilter等更多的特性【脱現(xiàn)在事情變復雜了。我們有了兩個流行的 Log Facade念搬,以及三個流行的 Log Implementation抑堡。
jcl+slf4j
當你感覺現(xiàn)在差不多了吧的時候,三國時期的故事朗徊,其實又開始上演了首妖。
-
Log4j2
維護 Log4j 的人似乎坐立不安,他們不想坐視用戶一點點被 SLF4J /Logback 蠶食爷恳,繼而搞出了 Log4j2有缆。
Log4j2 和 Log4j1.x 并不兼容,設計上很大程度上模仿了 SLF4J/Logback温亲,性能上也獲得了很大的提升棚壁。
Log4j2 也做了 Facade/Implementation 分離的設計,分成了 log4j-api 和 log4j-core栈虚。
jcl+slf4j+log4f2
=========================分割線========================
Gülcü 是個追求完美的人袖外,各種紛紛擾擾的歷史看在了ta眼里,他決定讓這些Log之間都能夠方便的互相替換节芥,所以做了各種 Adapter 和 Bridge 來連接:
到這里在刺,日志演進總算有所停歇。
2.Spring Boot 日志使用
2.1. 依賴分析
歷史回顧不是我們的目的头镊,結合現(xiàn)在流行的開源框架Spring Boot蚣驼,我們再來談談具體項目該如何結合實際,使用日志相艇。
構建Spring Boot Web項目颖杏,版本:2.1.4。分析下Pom.xml的依賴:
可以發(fā)現(xiàn)坛芽,Spring Boot采用了SLF4J+Logback的組合來完成日志的記錄留储。并且作者把Log4j和JUL的日志組件適配到了slf4j。的確咙轩,Spring Boot為Java coder做了太多的工作获讳。
2.2. 日志初始化過程
以上面構建的Spring Boot項目為例,添加簡單日志記錄代碼:
@SpringBootApplication
public class SpringBootLoggerDemoApplication {
private static Logger logger = LoggerFactory.getLogger(SpringBootLoggerDemoApplication.class);
public static void main(String[] args) {
SpringApplication.run(SpringBootLoggerDemoApplication.class, args);
logger.debug("hello logger");
}
}
LoggerFactory.java
/**
* Return a logger named according to the name parameter using the
* statically bound {@link ILoggerFactory} instance.
*
* @param name
* The name of the logger.
* @return logger
*/
public static Logger getLogger(String name) {
ILoggerFactory iLoggerFactory = getILoggerFactory();
return iLoggerFactory.getLogger(name);
}
通過getILoggerFactory獲取日志工廠:
/**
* Return the {@link ILoggerFactory} instance in use.
* <p/>
* <p/>
* ILoggerFactory instance is bound with this class at compile time.
*
* @return the ILoggerFactory instance in use
*/
public static ILoggerFactory getILoggerFactory() {
if (INITIALIZATION_STATE == UNINITIALIZED) {
synchronized (LoggerFactory.class) {
if (INITIALIZATION_STATE == UNINITIALIZED) {
INITIALIZATION_STATE = ONGOING_INITIALIZATION;
performInitialization();
}
}
}
...省略...
}
這里代碼定位到performInitialization:
private final static void performInitialization() {
bind();
...省略..
}
bind()具體日志實現(xiàn):
private final static void bind() {
try {
Set<URL> staticLoggerBinderPathSet = null;
if (!isAndroid()) {
staticLoggerBinderPathSet = findPossibleStaticLoggerBinderPathSet();
reportMultipleBindingAmbiguity(staticLoggerBinderPathSet);
}
...省略...
}
}
關鍵代碼就在findPossibleStaticLoggerBinderPathSet:
private static String STATIC_LOGGER_BINDER_PATH = "org/slf4j/impl/StaticLoggerBinder.class";
static Set<URL> findPossibleStaticLoggerBinderPathSet() {
// use Set instead of list in order to deal with bug #138
// LinkedHashSet appropriate here because it preserves insertion order
// during iteration
Set<URL> staticLoggerBinderPathSet = new LinkedHashSet<URL>();
try {
ClassLoader loggerFactoryClassLoader = LoggerFactory.class.getClassLoader();
Enumeration<URL> paths;
if (loggerFactoryClassLoader == null) {
paths = ClassLoader.getSystemResources(STATIC_LOGGER_BINDER_PATH);
} else {
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;
}
代碼最終到最后活喊,其實是通過ClassLoader在ClassPath里面加載指定實現(xiàn)類org/slf4j/impl/StaticLoggerBinder.class
來實現(xiàn)日志組件的加載丐膝,核心就在:
//org/slf4j/impl/StaticLoggerBinder.class
if (loggerFactoryClassLoader == null) {
paths = ClassLoader.getSystemResources(STATIC_LOGGER_BINDER_PATH);
} else {
paths = loggerFactoryClassLoader.getResources(STATIC_LOGGER_BINDER_PATH);
}
類路徑下看看backLog代碼:
這里有個題外話,之前看過很多介紹Java SPI文章,老是把日志的加載機制歸類為SPI帅矗,其實通過上面的介紹偎肃,現(xiàn)在可以結論,其實不是浑此。
http://www.reibang.com/p/46b42f7f593c
3.日志切換方法及原理
行文到此累颂,主題介紹似乎差不多了,但是好像還有個問題凛俱,要是我要在Spring Boot換其他日志組件怎么辦吶紊馏。其實Spring Boot已經為我們考慮過這個問題了,為我們提供了一個自動配置的starter:spring-boot-starter-log4j2
最冰。
Starter for using Log4j2 for logging. An alternative to
spring-boot-starter-logging
修改方式也簡單:
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<exclusions>
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-logging</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-log4j2</artifactId>
</dependency>
依賴切換成了最后的log4j-api+log4j-core瘦棋。
參考: