1 日志系統(tǒng)
在應(yīng)用程序的開發(fā)中再来,通常會(huì)使用日志記錄監(jiān)視代碼中變量的變化情況蒙兰,輸出到控制臺(tái),文件系統(tǒng)或者網(wǎng)絡(luò)芒篷。記錄日志目的在于線上問題追蹤搜变,作為其他系統(tǒng)進(jìn)行統(tǒng)計(jì)分析的"數(shù)據(jù)".
2 Java常用日志框架
Java 的日志框架有很多,比如:JUL(Java Util Logging)针炉、Log4j挠他、Logback、Log4j2糊识、Tinylog 等绩社。除此之外,還有 JCL(Apache Commons Logging)和 SLF4J 這樣的“門面日志”
2.1 門面日志
什么是“門面日志”赂苗∮浒遥“門面日志”利用了設(shè)計(jì)模式中的門面模式思想,對(duì)外提供一套通用的日志記錄的 API拌滋,而不提供具體的日志輸出服務(wù)朴沿,如果要實(shí)現(xiàn)日志輸出,需要集成其他的日志框架,比如 Log4j赌渣、Logback魏铅、Log4j2 等。
這種門面模式的好處在于坚芜,記錄日志的 API 和日志輸出的服務(wù)分離開览芳,代碼里面只需要關(guān)注記錄日志的 API,通過 SLF4J 指定的接口記錄日志鸿竖;而日志輸出通過引入 JAR 包的方式即可指定其他的日志框架沧竟。當(dāng)我們需要改變系統(tǒng)的日志輸出服務(wù)時(shí),不用修改代碼缚忧,只需要改變引入日志輸出框架 JAR 包悟泵。
2.2 日志框架的歷史
- 1996年早期,歐洲安全電子市場項(xiàng)目組決定編寫它自己的程序跟蹤API(Tracing API)闪水。經(jīng)過不斷的完善糕非,這個(gè)API終于成為一個(gè)十分受歡迎的Java日志軟件包,即Log4j(作者Ceki Gülcü)球榆。后來Log4j成為Apache基金會(huì)項(xiàng)目中的一員朽肥。
- 期間Log4j近乎成了Java社區(qū)的日志標(biāo)準(zhǔn)。據(jù)說Apache基金會(huì)還曾經(jīng)建議Sun引入Log4j到j(luò)ava的標(biāo)準(zhǔn)庫中持钉,但Sun拒絕了.
- 2002年Java1.4發(fā)布鞠呈,Sun推出了自己的日志庫JUL(Java Util Logging),其實(shí)現(xiàn)基本模仿了Log4j的實(shí)現(xiàn)。但是如果有人想換成其他日志組件右钾,如log4j換成JUL,因?yàn)閍pi完全不同旱爆,就需要改動(dòng)代碼舀射。
- Apache見此,開發(fā)了JCL(Jakarta Commons Logging)怀伦,即commons-logging-xx.jar脆烟。它只提供一套通用的日志接口api,并不提供日志的實(shí)現(xiàn)房待。很好的設(shè)計(jì)原則嘛邢羔,依賴抽象而非實(shí)現(xiàn)。這樣應(yīng)用程序可以在運(yùn)行時(shí)選擇自己想要的日志實(shí)現(xiàn)組件桑孩。
- 這樣看上去也挺美好的拜鹤,但是log4j的作者覺得JCL不好用,自己開發(fā)出slf4j流椒,它跟JCL類似敏簿,本身不替供日志具體實(shí)現(xiàn),只對(duì)外提供接口或門面。目的就是為了替代JCL惯裕。同時(shí)温数,還開發(fā)出logback,一個(gè)比log4j擁有更高性能的組件蜻势,目的是為了替代log4j撑刺。
- Apache參考了logback,并做了一系列優(yōu)化,推出了log4j2
3 commons-logging
3.1 概述
Apache Commons Logging是一個(gè)門面日握玛。代碼里面只需要關(guān)注記錄日志的 API够傍,通過JCL指定的接口記錄日志;而日志輸出通過引入 JAR 包的方式實(shí)現(xiàn)败许。如果沒有引入日志框架王带,Apache Commons Logging會(huì)使用內(nèi)部默認(rèn)實(shí)現(xiàn) SimpleLog
JUL最后更新于2014年7月,現(xiàn)以停止更新市殷。
3.2 包結(jié)構(gòu)
- Log:日志對(duì)象接口,封裝了操作日志的方法,定義了日志操作的5個(gè)級(jí)別:trace < debug < info < warn < error
- LogFactory:抽象類愕撰,日志工廠,獲取日志類醋寝;
- LogFactoryImpl:LogFactory的實(shí)現(xiàn)類搞挣,真正獲取日志對(duì)象的地方;
- Log4JLogger:對(duì)log4j的日志對(duì)象封裝音羞;
- Jdk14Logger:對(duì)JDK1.4的日志對(duì)象封裝囱桨;
- Jdk13LumberjackLogger:對(duì)JDK1.3以及以前版本的日志對(duì)象封裝;
- SimpleLog:commons-logging自帶日志對(duì)象嗅绰;
3.2 日志框架加載
jcl可以通過在ClassPath下創(chuàng)建commons-logging.properties配置文件指定加載日志實(shí)現(xiàn)框架舍肠。
#指定日志對(duì)象:
org.apache.commons.logging.Log = org.apache.commons.logging.impl.Jdk14Logger
#指定日志工廠:
org.apache.commons.logging.LogFactory = org.apache.commons.logging.impl.LogFactoryImpl
jcl如果沒有指定日志實(shí)現(xiàn)框架則默認(rèn)加載時(shí)按照順序log4j>jul> simpleLog依次加載,如果查找到log4j包則使用log4j,如果沒有依次類推窘面。
3.3 JCL使用
引用JCL依賴包
<dependency>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
<version>1.2</version>
</dependency>
測試代碼
此時(shí)由于項(xiàng)目也沒有放入其他日志框架依賴包翠语,會(huì)按照順序log4j>jul> simpleLog加載,因此此時(shí)會(huì)使用jdk自帶jul實(shí)現(xiàn)日志打印财边。
public class commons_loggingDemo {
Log log= LogFactory.getLog(commons_loggingDemo.class);
@Test
public void test() throws IOException {
log.debug("Debug info.");
log.info("Info info");
log.warn("Warn info");
log.error("Error info");
log.fatal("Fatal info");
}
}
引用log4j依賴包
在不修改代碼的前提下肌括,引入log4j依賴包。在次調(diào)用測試代碼酣难,此時(shí)會(huì)使用log4j實(shí)現(xiàn)日志打印谍夭。
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.16</version>
</dependency>
4 SLF4J
4.1 概述
SLF4J同樣是一個(gè)門面日志。代碼里面只需要關(guān)注記錄日志的 API憨募,通過SLF4J指定的接口記錄日志紧索;而日志輸出通過引入 JAR 包的方式實(shí)現(xiàn)。
slf4j-api.jar 表示SLF4J提供的日志接口相關(guān)的jar包
natvie implementation of slf4j-api: 表示SLF4J本地底層日志框架馋嗜,其中包括logback-classic.jar + logback-core.jar作為logback日志框架底層實(shí)現(xiàn)齐板,SLF4J-Simple作為SLF4J簡單日志框架實(shí)現(xiàn),slf4j-nop.jar表示空實(shí)現(xiàn)。
none-natvie implementation of slf4j-api :表示非本地甘磨,外部日志框架橡羞。其中包括lo4j.jar表示log4j日志框架,JUL表示JDK自帶日志框架济舆,log4j-core.jar表示log4j2日志框架(圖中沒畫)
Adaptation layer:表示外部日志框架的適配器
組合如下
- 使用logback作為日志框架
- slf4j-api.jar + logback-classic.jar + logback-core.jar
- 使用log4j作為日志框架
- slf4j-api.jar + slf4j-log4j12.jar + log4j.jar
- 使用jul作為日志框架
- slf4j-api.jar + slf4j-jdk14.jar
- 使用log4j2作為日志框架
- slf4j-api.jar + +log4j-slf4j-impl+log4j-core
- 只用slf4j無日志實(shí)現(xiàn)
- slf4j-api.jar + slf4j-nop.jar
4.2 橋接器
在實(shí)際開發(fā)過程中通常會(huì)遇到下面的問題卿泽,項(xiàng)目中舊模塊使用的是log4j來打印日志的話,而新模塊想用logback來統(tǒng)一打印日志的話滋觉,由于兩者并不兼容签夭,如果直接修改業(yè)務(wù)代碼會(huì)存在一定風(fēng)險(xiǎn)。那么該如何解決呢椎侠?
SLFJ提供了各種日志框架的橋接器第租,我們只需要引用對(duì)應(yīng)適配器的實(shí)現(xiàn)包,并去掉原始的依賴包我纪,SLFJ會(huì)自動(dòng)幫我們完成日志框架的統(tǒng)一慎宾。上面案例中可以去掉log4j包log4j.jar引入log4j-over-slf4j包(這個(gè)包使之前代碼使用log4j不會(huì)因?yàn)檎也坏蕉鴪?bào)錯(cuò),并會(huì)將打印日志橋接給slf4j-api.jar)浅悉,并同時(shí)引入slf4j-api.jar + logback-classic.jar + logback-core.jar趟据。
SLFJ提供的橋接器
- JCL橋接器
- 使用jcl-over-slf4j.jar
- log4j橋接器
- 使用log4j-over-slf4j.jar
- JUL橋接器
- 使用jul-to-slf4j.jar
4.3 小結(jié)
4.4 使用
springBoot默認(rèn)使用logback作為日志框架,使用slf4j作為門面日志术健。
使用springBoot+slf4j+logback
<dependencyManagement>
<dependencies>
<dependency>
<!-- Import dependency management from Spring Boot -->
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>1.5.18.RELEASE</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</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-logging</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
</dependency>
</dependencies>
這里已經(jīng)幫助我們添加各種類型日志橋接器汹碱,如果我們我們向?qū)⒃瓉眄?xiàng)目中日志框架去掉,并使用Logback框架時(shí)荞估。只需要去掉原先依賴咳促。
使用springBoot+slf4j+log4j
這里需要添加spring-boot-starter-log4j依賴,并將spring-boot-starter-logging從依賴中排除勘伺。
<dependencyManagement>
<dependencies>
<dependency>
<!-- Import dependency management from Spring Boot -->
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>1.5.18.RELEASE</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
<exclusions>
<exclusion>
<artifactId>spring-boot-starter-logging</artifactId>
<groupId>org.springframework.boot</groupId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-log4j</artifactId>
<version>1.3.8.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
</dependency>
</dependencies>
同樣這里已經(jīng)幫助我們添加各種類型日志橋接器等缀,如果我們我們向?qū)⒃瓉眄?xiàng)目中日志框架去掉,并使用logb4j框架時(shí)娇昙。只需要去掉原先依賴。
使用springBoot+slf4j+log4j2
這里需要添加spring-boot-starter-log4j2依賴笤妙,并將spring-boot-starter-logging從依賴中排除冒掌。
<dependencyManagement>
<dependencies>
<dependency>
<!-- Import dependency management from Spring Boot -->
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>1.5.18.RELEASE</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
<exclusions>
<exclusion>
<artifactId>spring-boot-starter-logging</artifactId>
<groupId>org.springframework.boot</groupId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-log4j2</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
</dependency>
</dependencies>
同樣這里已經(jīng)幫助我們添加各種類型日志橋接器,如果我們我們向?qū)⒃瓉眄?xiàng)目中日志框架去掉蹲盘,并使用logb4j框架時(shí)股毫。只需要去掉原先依賴。
關(guān)于其他使用日志框架可以排除spring-boot-starter-logging后自行添加就不再詳訴召衔。
測試代碼
import org.junit.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
public class Sj4jTest {
Logger logger = LoggerFactory.getLogger("Sj4jTest");
@Test
public void test() throws IOException {
logger.debug("Debug info.");
logger.info("Info info");
logger.warn("Warn info");
logger.error("Error info");
}
}