jcl與jul?log4j1?log4j2?logback日志系統(tǒng)機制及集成原理
系列文章已完成,目錄如下:
jdk-logging log4j logback日志系統(tǒng)實現(xiàn)機制原理介紹
slf4j與jul、log4j1、log4j2、logback的集成原理
slf4j、jcl、jul、log4j1、log4j2、logback大總結(jié)
前面介紹了jdk自帶的logging、log4j1、log4j2、logback等實際的日志框架
對于開發(fā)者而言,每種日志都有不同的寫法。如果我們以實際的日志框架來進行編寫,代碼就限制死了,之后就很難再更換日志系統(tǒng),很難做到無縫切換。
java web開發(fā)就經(jīng)常提到一項原則:面向接口編程,而不是面向?qū)崿F(xiàn)編程
所以我們應(yīng)該是按照一套統(tǒng)一的API來進行日志編程,實際的日志框架來實現(xiàn)這套API,這樣的話,即使更換日志框架,也可以做到無縫切換。
這就是commons-logging與slf4j的初衷。
下面就來介紹下commons-logging與slf4j這兩個門面如何與上述四個實際的日志框架進行集成的呢
介紹之前先說明下日志簡稱:
jdk自帶的logging->簡稱 jul (java-util-logging)
apache commons-logging->簡稱 jcl
apache commons-logging
先從一個簡單的使用案例來說明
1 簡單的使用案例
private static Log logger=LogFactory.getLog(JulJclTest.class); public static void main(String[] args){ if(logger.isTraceEnabled()){ logger.trace("commons-logging-jcl trace message"); } if(logger.isDebugEnabled()){ logger.debug("commons-logging-jcl debug message"); } if(logger.isInfoEnabled()){ logger.info("commons-logging-jcl info message"); } }
上述Log、LogFactory都是commons-logging自己的接口和類
2 使用原理
LogFactory.getLog(JulJclTest.class)的源碼如下:
public static Log getLog(Class clazz) throws LogConfigurationException { return getFactory().getInstance(clazz); }
上述獲取Log的過程大致分成2個階段
- 獲取LogFactory的過程 (從字面上理解就是生產(chǎn)Log的工廠)
- 根據(jù)LogFactory獲取Log的過程
commons-logging默認提供的LogFactory實現(xiàn):LogFactoryImpl commons-logging默認提供的Log實現(xiàn):Jdk14Logger、Log4JLogger、SimpleLog。
來看下commons-logging包中的大概內(nèi)容:
下面來詳細說明:
1 獲取LogFactory的過程
從下面幾種途徑來獲取LogFactory
1.1 系統(tǒng)屬性中獲取,即如下形式
System.getProperty("org.apache.commons.logging.LogFactory")
1.2 使用java的SPI機制,來搜尋對應(yīng)的實現(xiàn)
對于java的SPI機制,詳細內(nèi)容可以自行搜索,這里不再說明。搜尋路徑如下:
META-INF/services/org.apache.commons.logging.LogFactory
簡單來說就是搜尋哪些jar包中含有搜尋含有上述文件,該文件中指明了對應(yīng)的LogFactory實現(xiàn)
1.3 從commons-logging的配置文件中尋找
commons-logging也是可以擁有自己的配置文件的,名字為commons-logging.properties,只不過目前大多數(shù)情況下,我們都沒有去使用它。如果使用了該配置文件,嘗試從配置文件中讀取屬性"org.apache.commons.logging.LogFactory"對應(yīng)的值
1.4 最后還沒找到的話,使用默認的org.apache.commons.logging.impl.LogFactoryImpl
LogFactoryImpl是commons-logging提供的默認實現(xiàn)
2 根據(jù)LogFactory獲取Log的過程
這時候就需要尋找底層是選用哪種類型的日志
就以commons-logging提供的默認實現(xiàn)為例,來詳細看下這個過程:
2.1 從commons-logging的配置文件中尋找Log實現(xiàn)類的類名
從commons-logging.properties配置文件中尋找屬性為"org.apache.commons.logging.Log"對應(yīng)的Log類名
2.2 從系統(tǒng)屬性中尋找Log實現(xiàn)類的類名
即如下方式獲?。?/p>
System.getProperty("org.apache.commons.logging.Log")
2.3 如果上述方式?jīng)]找到,則從classesToDiscover屬性中尋找
classesToDiscover屬性值如下:
private static final String[] classesToDiscover = { "org.apache.commons.logging.impl.Log4JLogger", "org.apache.commons.logging.impl.Jdk14Logger", "org.apache.commons.logging.impl.Jdk13LumberjackLogger", "org.apache.commons.logging.impl.SimpleLog" };
它會嘗試根據(jù)上述類名,依次進行創(chuàng)建,如果能創(chuàng)建成功,則使用該Log,然后返回給用戶。
下面針對具體的日志框架,看看commons-logging是如何集成的
commons-logging與jul集成
1 需要的jar包
commons-logging
對應(yīng)的maven依賴是:
<dependency> <groupId>commons-logging</groupId> <artifactId>commons-logging</artifactId> <version>1.2</version> </dependency>
2 使用案例
private static Log logger=LogFactory.getLog(JulJclTest.class); public static void main(String[] args){ if(logger.isTraceEnabled()){ logger.trace("commons-logging-jcl trace message"); } if(logger.isDebugEnabled()){ logger.debug("commons-logging-jcl debug message"); } if(logger.isInfoEnabled()){ logger.info("commons-logging-jcl info message"); } }
結(jié)果輸出如下:
四月 27, 2015 11:13:33 下午 com.demo.log4j.JulJclTest main
信息: commons-logging-jcl info message
3 使用案例分析
案例過程分析,就是看看上述commons-logging的在執(zhí)行原理的過程中是如何來走的
1 獲取獲取LogFactory的過程
所以commons-logging會使用默認的LogFactoryImpl作為LogFactory
- 1.1 我們沒有配置系統(tǒng)屬性"org.apache.commons.logging.LogFactory"
- 1.2 我們沒有配置commons-logging的commons-logging.properties配置文件
- 1.3 也沒有含有"META-INF/services/org.apache.commons.logging.LogFactory"路徑的jar包
2 根據(jù)LogFactory獲取Log的過程
所以就需要依次根據(jù)classesToDiscover中的類名稱進行創(chuàng)建。
2.1 我們沒有配置commons-logging的commons-logging.properties配置文件
2.2 我們沒有配置系統(tǒng)屬性"org.apache.commons.logging.Log"
2.3 先是創(chuàng)建org.apache.commons.logging.impl.Log4JLogger
創(chuàng)建失敗,因為該類是依賴org.apache.log4j包中的類的
2.4 接著創(chuàng)建org.apache.commons.logging.impl.Jdk14Logger
創(chuàng)建成功,所以我們返回的就是Jdk14Logger,看下它是如何與jul集成的
它內(nèi)部有一個java.util.logging.Logger logger屬性,所以Jdk14Logger的info("commons-logging-jcl info message")操作都會轉(zhuǎn)化成由java.util.logging.Logger來實現(xiàn):
上述logger的來歷:
logger = java.util.logging.Logger.getLogger(name);
就是使用jul原生的方式創(chuàng)建的一個java.util.logging.Logger,參見jdk-logging的原生寫法
是如何打印info信息的呢?
使用jul原生的方式:
logger.log(Level.WARNING,"commons-logging-jcl info message");
由于jul默認的級別是INFO級別(見上一篇文章的說明中的配置文件jdk自帶的logging),所以只打出了如下信息:
四月 27, 2015 11:41:24 下午 com.demo.log4j.JulJclTest main
信息: commons-logging-jcl info message
原生的jdk的logging的日志級別是FINEST、FINE、INFO、WARNING、SEVERE分別對應(yīng)我們常見的trace、debug、info、warn、error。
commons-logging與log4j1集成
1 需要的jar包
commons-logging
log4j
對應(yīng)的maven依賴是:
<dependency> <groupId>commons-logging</groupId> <artifactId>commons-logging</artifactId> <version>1.2</version> </dependency> <dependency> <groupId>log4j</groupId> <artifactId>log4j</artifactId> <version>1.2.17</version> </dependency>
4.2 使用案例
在類路徑下加入log4j的配置文件log4j.properties
log4j.rootLogger = trace, console log4j.appender.console = org.apache.log4j.ConsoleAppender log4j.appender.console.layout = org.apache.log4j.PatternLayout log4j.appender.console.layout.ConversionPattern = %-d{yyyy-MM-dd HH:mm:ss} %m%n
使用方式如下:
private static Log logger=LogFactory.getLog(Log4jJclTest.class); public static void main(String[] args){ if(logger.isTraceEnabled()){ logger.trace("commons-logging-log4j trace message"); } if(logger.isDebugEnabled()){ logger.debug("commons-logging-log4j debug message"); } if(logger.isInfoEnabled()){ logger.info("commons-logging-log4j info message"); } }
代碼沒變,還是使用commons-logging的接口和類來編程,沒有l(wèi)og4j的任何影子。這樣,commons-logging就與log4j集成了起來,我們可以通過log4j的配置文件來控制日志的顯示級別
上述是trace級別(小于debug),所以trace、debug、info的都會顯示出來
3 使用案例分析
案例過程分析,就是看看上述commons-logging的在執(zhí)行原理的過程中是如何來走的:
1 獲取獲取LogFactory的過程
同上述jcl的過程一樣,使用默認的LogFactoryImpl作為LogFactory
2 根據(jù)LogFactory獲取Log的過程
同上述jcl的過程一樣,最終會依次根據(jù)classesToDiscover中的類名稱進行創(chuàng)建:
先是創(chuàng)建org.apache.commons.logging.impl.Log4JLogger
創(chuàng)建成功,因為此時含有l(wèi)og4j的jar包,所以返回的是Log4JLogger,我們看下它與commons-logging是如何集成的:
它內(nèi)部有一個org.apache.log4j.Logger logger屬性,這個是log4j的原生Logger。所以Log4JLogger都是委托這個logger來完成的
2.1 org.apache.log4j.Logger logger來歷
org.apache.log4j.Logger.getLogger(name)
使用原生的log4j1的寫法來生成,參見之前l(fā)og4j原生的寫法log4j1原生的寫法,我們知道上述過程會引發(fā)log4j1的配置文件的加載,之后就進入log4j1的世界了
2.2 輸出日志
測試案例中我們使用commons-logging輸出的日志的形式如下(這里的logger是org.apache.commons.logging.impl.Log4JLogger類型):
logger.debug("commons-logging-log4j debug message");
其實就會轉(zhuǎn)換成log4j原生的org.apache.log4j.Logger對象(就是上述獲取的org.apache.log4j.Logger類型的logger對象)的如下輸出:
logger.debug("log4j debug message");
上述過程最好與log4j1的原生方式對比著看,見log4j1的原生方式
commons-logging與log4j2集成
1 需要的jar包
commons-logging
log4j-api (log4j2的API包)
log4j-core (log4j2的API實現(xiàn)包)
log4j-jcl (log4j2與commons-logging的集成包)
對應(yīng)的maven依賴是:
<dependency> <groupId>commons-logging</groupId> <artifactId>commons-logging</artifactId> <version>1.2</version> </dependency> <dependency> <groupId>org.apache.logging.log4j</groupId> <artifactId>log4j-api</artifactId> <version>2.2</version> </dependency> <dependency> <groupId>org.apache.logging.log4j</groupId> <artifactId>log4j-core</artifactId> <version>2.2</version> </dependency> <dependency> <groupId>org.apache.logging.log4j</groupId> <artifactId>log4j-jcl</artifactId> <version>2.2</version> </dependency>
2 使用案例
編寫log4j2的配置文件log4j2.xml,簡單如下:
<?xml version="1.0" encoding="UTF-8"?> <Configuration status="WARN"> <Appenders> <Console name="Console" target="SYSTEM_OUT"> <PatternLayout pattern="%d{HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n"/> </Console> </Appenders> <Loggers> <Root level="debug"> <AppenderRef ref="Console"/> </Root> </Loggers> </Configuration>
使用案例如下:
private static Log logger=LogFactory.getLog(Log4j2JclTest.class); public static void main(String[] args){ if(logger.isTraceEnabled()){ logger.trace("commons-logging-log4j trace message"); } if(logger.isDebugEnabled()){ logger.debug("commons-logging-log4j debug message"); } if(logger.isInfoEnabled()){ logger.info("commons-logging-log4j info message"); } }
仍然是使用commons-logging的Log接口和LogFactory來進行編寫,看不到log4j2的影子。但是這時候含有上述幾個jar包,log4j2就與commons-logging集成了起來。
3 使用案例分析
案例過程分析,就是看看上述commons-logging的在執(zhí)行原理的過程中是如何來走的:
1 先來看下上述 log4j-jcl(log4j2與commons-logging的集成包)的來歷:
我們知道,commons-logging原始的jar包中使用了默認的LogFactoryImpl作為LogFactory,該默認的LogFactoryImpl中的classesToDiscover(到上面查看它的內(nèi)容)并沒有l(wèi)og4j2對應(yīng)的Log實現(xiàn)類。所以我們就不能使用這個原始包中默認的LogFactoryImpl了,需要重新指定一個,并且需要給出一個apache的Log實現(xiàn)(該Log實現(xiàn)是用于log4j2的),所以就產(chǎn)生了log4j-jcl這個jar包,來看下這個jar包的大致內(nèi)容:
這里面的LogFactoryImpl就是要準備替換commons-logging中默認的LogFactoryImpl(其中META-INF/services/下的那個文件起到重要的替換作用,下面詳細說)
這里面的Log4jLog便是針對log4j2的,而commons-logging中的原始的Log4JLogger則是針對log4j1的。它們都是commons-logging的Log接口的實現(xiàn)
2 獲取獲取LogFactory的過程
這個過程就和jul、log4j1的集成過程不太一樣了。通過java的SPI機制,找到了org.apache.commons.logging.LogFactory對應(yīng)的實現(xiàn),即在log4j-jcl包中找到的,其中META-INF/services/org.apache.commons.logging.LogFactory中的內(nèi)容是:
org.apache.logging.log4j.jcl.LogFactoryImpl
即指明了使用log4j-jcl中的LogFactoryImpl作為LogFactory
3 根據(jù)LogFactory獲取Log的過程
就來看下log4j-jcl中的LogFactoryImpl是怎么實現(xiàn)的
public class LogFactoryImpl extends LogFactory { private final LoggerAdapter<Log> adapter = new LogAdapter(); //略 }
這個LoggerAdapter是lo4j2中的一個適配器接口類,根據(jù)log4j2生產(chǎn)的原生的org.apache.logging.log4j.Logger實例,將它包裝成你指定的泛型類。
這里使用的LoggerAdapter實現(xiàn)是LogAdapter,它的內(nèi)容如下:
public class LogAdapter extends AbstractLoggerAdapter<Log> { @Override protected Log newLogger(final String name, final LoggerContext context) { return new Log4jLog(context.getLogger(name)); } @Override protected LoggerContext getContext() { return getContext(ReflectionUtil.getCallerClass(LogFactory.class)); } }
我們可以看到,它其實就是將原生的log4j2的Logger封裝成Log4jLog。這里就可以看明白了,下面來詳細的走下流程,看看是什么時候來初始化log4j2的:
至此,我們通過Log4jLog實例打印的日志都是委托給了它內(nèi)部包含的log4j2的原生Logger對象了。
3.1 首先獲取log4j2中的重要配置對象LoggerContext,LogAdapter的實現(xiàn)如上面的源碼(使用父類的getContext方法),父類方法的內(nèi)容如下:
LogManager.getContext(cl, false);
我們可以看到這其實就是使用log4j2的LogManager進行初始化的,至此就進入log4j2的初始化的世界了。
3.2 log4j2的LoggerContext初始化完成后,該生產(chǎn)一個log4j2原生的Logger對象
使用log4j2原生的方式:
context.getLogger(name)
3.3 將上述方式產(chǎn)生的Log4j原生的Logger實例進行包裝,包裝成Log4jLog
new Log4jLog(context.getLogger(name));
上述過程最好與log4j2的原生方式對比著看,見log4j2的原生方式
commons-logging與logback集成
需要的jar包
jcl-over-slf4j (替代了commons-logging,下面詳細說明)
slf4j-api
logback-core
logback-classic
對應(yīng)的maven依賴是:
<dependency> <groupId>org.slf4j</groupId> <artifactId>jcl-over-slf4j</artifactId> <version>1.7.12</version> </dependency> <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-api</artifactId> <version>1.7.12</version> </dependency> <dependency> <groupId>ch.qos.logback</groupId> <artifactId>logback-core</artifactId> <version>1.1.3</version> </dependency> <dependency> <groupId>ch.qos.logback</groupId> <artifactId>logback-classic</artifactId> <version>1.1.3</version> </dependency>
2 使用案例
首先在類路徑下編寫logback的配置文件logback.xml,簡單如下:
<?xml version="1.0" encoding="UTF-8"?> <configuration> <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender"> <encoder> <pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern> </encoder> </appender> <root level="DEBUG"> <appender-ref ref="STDOUT" /> </root> </configuration>
使用方式:
private static Log logger=LogFactory.getLog(LogbackTest.class); public static void main(String[] args){ if(logger.isTraceEnabled()){ logger.trace("commons-logging-jcl trace message"); } if(logger.isDebugEnabled()){ logger.debug("commons-logging-jcl debug message"); } if(logger.isInfoEnabled()){ logger.info("commons-logging-jcl info message"); } }
完全是用commons-logging的API來完成日志編寫
3 使用案例分析
logback本身的使用其實就和slf4j綁定了起來,現(xiàn)在要想指定commons-logging的底層log實現(xiàn)是logback,則需要2步走
- 第一步: 先將commons-logging底層的log實現(xiàn)轉(zhuǎn)向slf4j (jcl-over-slf4j干的事)
- 第二步: 再根據(jù)slf4j的選擇底層日志原理,我們使之選擇上logback
這樣就可以完成commons-logging與logback的集成。即寫著commons-logging的API,底層卻是logback來進行輸出
然后來具體分析下整個過程的源碼實現(xiàn):
1 先看下jcl-over-slf4j都有哪些內(nèi)容(它可以替代了commons-logging)
如下圖
這就是jcl-over-slf4j的大致內(nèi)容
這里可以與commons-logging原生包中的內(nèi)容進行下對比。原生包中的內(nèi)容如下:
1.1 commons-logging中的Log接口和LogFactory類等
這是我們使用commons-logging編寫需要的接口和類
1.2 去掉了commons-logging原生包中的一些Log實現(xiàn)和默認的LogFactoryImpl
只有SLF4JLog實現(xiàn)和SLF4JLogFactory
2 獲取獲取LogFactory的過程
jcl-over-slf4j包中的LogFactory和commons-logging中原生的LogFactory不一樣,jcl-over-slf4j中的LogFactory直接限制死,是SLF4JLogFactory,源碼如下:
public abstract class LogFactory { static LogFactory logFactory = new SLF4JLogFactory(); //略 }
3 根據(jù)LogFactory獲取Log的過程
這就需要看下jcl-over-slf4j包中的SLF4JLogFactory的源碼內(nèi)容:
Log newInstance; Logger slf4jLogger = LoggerFactory.getLogger(name); if (slf4jLogger instanceof LocationAwareLogger) { newInstance = new SLF4JLocationAwareLog((LocationAwareLogger) slf4jLogger); } else { newInstance = new SLF4JLog(slf4jLogger); }
可以看到其實是用slf4j的LoggerFactory先創(chuàng)建一個slf4j的Logger實例(這其實就是單獨使用logback的使用方式,見logback原生案例)。
然后再將這個Logger實例封裝成common-logging定義的Log接口實現(xiàn),即SLF4JLog或者SLF4JLocationAwareLog實例。
所以我們使用的commons-logging的Log接口實例都是委托給slf4j創(chuàng)建的Logger實例(slf4j的這個實例又是選擇logbakc后產(chǎn)生的,即slf4j產(chǎn)生的Logger實例最終還是委托給logback中的Logger的)
結(jié)尾
這篇講解commons-logging與jul、log4j1、log4j2、logback的集成原理,內(nèi)容很長了,就把slf4j與上述四者的集成放到下一篇文章,更多關(guān)于jcl與jul log4j1 log4j2 logback日志系統(tǒng)集成的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Java的MyBatis框架中實現(xiàn)多表連接查詢和查詢結(jié)果分頁
這篇文章主要介紹了Java的MyBatis框架中實現(xiàn)多表連接查詢和查詢結(jié)果分頁,借助MyBatis框架中帶有的動態(tài)SQL查詢功能可以比普通SQL查詢做到更多,需要的朋友可以參考下2016-04-04SSH框架網(wǎng)上商城項目第13戰(zhàn)之Struts2文件上傳功能
這篇文章主要為大家詳細介紹了SSH框架網(wǎng)上商城項目第13戰(zhàn)之Struts2文件上傳功能的相關(guān)資料,感興趣的小伙伴們可以參考一下2016-06-06Java微服務(wù)分布式調(diào)度Elastic-job環(huán)境搭建及配置
Elastic-Job在配置中提供了JobEventConfiguration,支持數(shù)據(jù)庫方式配置,會在數(shù)據(jù)庫中自動創(chuàng)建JOB_EXECUTION_LOG和JOB_STATUS_TRACE_LOG兩張表以及若干索引,來記錄作業(yè)的相關(guān)信息2023-02-02