為什么要使用日志
剛開始接觸java時(shí)都使用過System.out來調(diào)試,通過它我們能打印出一些關(guān)注的信息到控制臺(tái)便于我們調(diào)試。這種方式只限于我們平常開發(fā)時(shí)簡單測試,但是生產(chǎn)環(huán)境這樣是不行的。首先這樣做會(huì)有性能上的問題,其次使用這種方式會(huì)很麻煩。例如我打印的結(jié)果需要顯示方法名稱、線程名稱、時(shí)間等等,你總不能每次都手動(dòng)拼接然后打印吧;還例如我需要將它輸入到不同的文件中。以上說的都是System.out所不能實(shí)現(xiàn)或者特別麻煩的地方。
混亂的日志體系
上面我們總結(jié)了System.out不足之處,所以我們正式項(xiàng)目一般都是使用的日志。但是java中的日志體系特別亂,特別對于新手來說簡直就是一臉悶逼。你肯定聽過Log4j、JUL、JCL、Slf4j、Logback、Log4j2這些里面的幾種,面對這么多日志相關(guān)的東西,你除此接觸簡直就是一臉悶逼。下面我們來簡單說說這些日志工具的關(guān)系和歷史。
Log4j:Java之前官方并沒有提供日志相關(guān)的工具,而Log4j就是最早提供日志功能的工具了,然后大家都開始使用這個(gè)工具,而Log4j也幾乎成為Java標(biāo)準(zhǔn)的日志庫了。
JUC:log4j很流行但是并沒有成為java標(biāo)準(zhǔn)的日志庫,Sun公司推出了JUL(java Util Logging)。因?yàn)樵谥按蠹乙呀?jīng)習(xí)慣使用Log4j了,所以JUL并沒有流行起來。
JCL:上面說了已經(jīng)存在Log4j和JUL,如果想從Log4j換成JUC或者從JUC換成Log4j很麻煩,需要修改所有日志調(diào)用的地方,所以Apache為了解決問題推出了JCL(Jakarta Commons Loggin)。JCL中只是定義了一套日志接口,支持在運(yùn)行時(shí)動(dòng)態(tài)的加載日志組件的實(shí)現(xiàn)。也就是說我們在代碼里使用JCL的API,底層我們可以使用Log4j或者JUC實(shí)現(xiàn),這樣我們在切換日志實(shí)現(xiàn)時(shí),不需要修改大量的代碼。
Slf4j和Logback:而之前開發(fā)Log4j的作者離開原來的公司之后,覺得之前的日志框架還不夠牛逼,于是他再次出手,掏出Slfj4和Logback(Sl4j的實(shí)現(xiàn))兩個(gè)項(xiàng)目。這下java日志領(lǐng)域基本上就分成兩個(gè)幫派了,Commons Loggin和Slf4j。而隨著時(shí)間推移,Slf4j搭配Logback慢慢的搶占了Log4j的用戶。
Log4j 2:上面說了Slf4j搭配Logback慢慢的搶占了Log4j的用戶,這個(gè)時(shí)候Log4j推出了Log4j 2.x與Logback對戰(zhàn)。
看了上面的java日志工具的發(fā)展,現(xiàn)在大概明白了日志工具的體系了吧。對于上面說的工具基本上可以分為兩類,一類是日志接口,也就是日志門面,它們不提供實(shí)現(xiàn)或者提供簡單的實(shí)現(xiàn)。而另外一類是日志實(shí)現(xiàn),也就是實(shí)際上實(shí)現(xiàn)日志功能的工具。
對于日志工具包門面工具主要就是Slf4j和JCL這個(gè)兩個(gè),而實(shí)際上因?yàn)樾蕟栴},目前日志門面大家都使用的是Slf4j,而JCL逐漸的退出了舞臺(tái)。
對于日志的實(shí)現(xiàn)現(xiàn)在基本上的選擇就是Log4j、Logback、Log4j 2。
Slf4j和Logback組合使用
這個(gè)組合應(yīng)該是目前使用最廣的,至少我個(gè)人使用比較多,而且在Spring Boot中也默認(rèn)使用的該組合,下面就說如何使用Slf4j和Logback組合使用。
jar包說明
logback-core:Logback核心功能包,如果只是想用Logback使用這個(gè)包就可以了。
logback-access:訪問模塊集成和Servlet容器集成,提供Http訪問日志的功能。
logback-classic:如果想與Slf4j集成就需要使用到這個(gè)包,這個(gè)包里面還依賴了Slf4j的包。
一般情況下我們使用Slf4j與Logback只需要在工程中添加下面的依賴即可,不需要我們手動(dòng)添加Slf4j包的依賴。
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.2.3</version>
</dependency>
Logback配置說明
如果不需要定制化的日志只需要添加相關(guān)的maven依賴就可以了,如果需要定制化需求就需要自己添加配置。Logback支持通過XML和groovy的方式來配置,但是使用較多的方式還是通過XML這種方式配置。配置時(shí)我們只需要在創(chuàng)建Logback.xml文件放在resource下即可。
<?xml version="1.0" encoding="UTF-8"?>
<configuration scan="true" scanPeriod="60 seconds" debug="true">
<!--設(shè)置變量-->
<property name="APP_NAME" value="logback-demo"/>
<!--上下文名稱,設(shè)置之后不能再修改-->
<contextName>${APP_NAME}</contextName>
<!--配置appender-->
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>%-4relative [%thread] %-5level %logger{35} - %msg %n</pattern>
</encoder>
</appender>
<!--配置logger-->
<logger name="com.buydeem.share.log.logback" level="debug" additivity="true"/>
<!--配置root-->
<root level="info">
<!--引用appender-->
<appender-ref ref="STDOUT"/>
</root>
</configuration>
configuration
上面就是一個(gè)最簡單的logback配置文件,首先就是configuration節(jié)點(diǎn)的配置,其中scan和scanPeriod是用來監(jiān)控配置文件變化的,scan設(shè)置為true時(shí)代表會(huì)掃描配置文件的變化,而scanPeriod是用來指定掃描頻率。而debug是用來打印Logback內(nèi)部的日志,它會(huì)打印如何查找配置文件和實(shí)際應(yīng)用的日志文件等信息。
contextName
設(shè)置應(yīng)用的名稱,該值設(shè)置之后是無法再次修改的。簡單的說就是應(yīng)用啟動(dòng)之后,再次修改該值是不生效的。
property
用來定義變量,在后續(xù)的配置中可是使用該變量。
appender
日志輸出組件,主要用來輸入和格式化日志,而且Logback中提供了多種appender組件,不同的組件有不同的效果。例如可以將日志輸出在控制臺(tái)中,還可以將日志輸出到文件中。例如我們上文中使用的ch.qos.logback.core.ConsoleAppender它就是將日志輸出到控制臺(tái)。
對于appender節(jié)點(diǎn)來說我們還可以配置其他東西,例如我們可以篩選日志的級別,讓該appender中只輸出某種級別的日志。利用這個(gè)設(shè)置,我們可以將不同級別的日志輸出到不同的文件中。
logger和root
logger用來設(shè)置某一個(gè)包或者具體某一個(gè)類的日志打印級別以及指定appender。你可以理解logger和root是同一種東西,不同的在于root是logger的最上級。對于logger節(jié)點(diǎn)中,我們可以設(shè)置的屬性有l(wèi)evel和additivity兩個(gè)屬性。
level代表該appender中輸出的最低日志級別,例如上面的配置文件中我們將其設(shè)置成debug代表低于該級別的日志將不會(huì)再logger中打印出來。如果我們沒有設(shè)置將應(yīng)用父級的level,如果logger沒有父級則會(huì)使用root中的level(root是所有l(wèi)evel最上級)。
additivity用來設(shè)置該logger是否向上級傳遞,如果設(shè)置為true,該logger下打印的日志還會(huì)傳遞到root中。在我們的配置中,我們并沒有在logger下配置appender,但是仍然可以打印出日志。這是因?yàn)槲覀冊趓oot節(jié)點(diǎn)下配置了appender,并且我們還把logger的additivity設(shè)置成了true。如果我們將該值改為false,你可以發(fā)現(xiàn)日志將不會(huì)再控制臺(tái)中輸出了。
配置不同級別的日志內(nèi)容輸出到不同的文件
對于實(shí)際開發(fā)中我們可能需要將不同的日志輸出到不同的文件,下面是一個(gè)示例配置。
<?xml version="1.0" encoding="UTF-8"?>
<configuration scan="true" scanPeriod="60 seconds">
<!--日志文件前綴,即應(yīng)用名稱 -->
<property name="logfile.prefix" value="logback-demo"/>
<!--日志路徑,可寫相對路徑,也可寫絕對路徑 -->
<property name="log.path" value="logs"/>
<!-- 日志輸出格式 -->
<property name="log.pattern"
value="%d{yyyy-MM-dd HH:mm:ss.SSS} %5level [%15thread] %40.40logger{40} [%10method,%line] : %msg%n"/>
<!-- 控制臺(tái)輸出日志 -->
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<!-- 設(shè)置日志輸出格式-->
<encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
<pattern>${log.pattern}</pattern>
<!-- 編碼 -->
<charset>UTF-8</charset>
</encoder>
</appender>
<!-- 文件輸出日志, 滾動(dòng)(時(shí)間/文件大?。┹敵霾呗?-->
<appender name="DEBUG_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<!-- 過濾器,只記錄debug級別的日志 -->
<filter class="ch.qos.logback.classic.filter.LevelFilter">
<level>DEBUG</level>
<OnMismatch>DENY</OnMismatch>
<OnMatch>ACCEPT</OnMatch>
</filter>
<!-- 日志文件路徑及文件名 -->
<File>${log.path}/${logfile.prefix}-debug.log</File>
<!-- 日志記錄器的滾動(dòng)策略,按日期記錄 -->
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<!-- 日志輸出格式 -->
<FileNamePattern>${log.path}/${logfile.prefix}-debug.%d{yyyy-MM-dd}.log</FileNamePattern>
<!-- 日志保留天數(shù) -->
<maxHistory>30</maxHistory>
</rollingPolicy>
<!-- 設(shè)置日志輸出格式-->
<encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
<pattern>${log.pattern}</pattern>
<!-- 編碼 -->
<charset>UTF-8</charset>
</encoder>
</appender>
<!-- info級別-->
<appender name="INFO_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<filter class="ch.qos.logback.classic.filter.LevelFilter">
<level>INFO</level>
<OnMismatch>DENY</OnMismatch>
<OnMatch>ACCEPT</OnMatch>
</filter>
<File>${log.path}/${logfile.prefix}-info.log</File>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<FileNamePattern>${log.path}/${logfile.prefix}-info.%d{yyyy-MM-dd}.log</FileNamePattern>
<maxHistory>30</maxHistory>
</rollingPolicy>
<!-- 設(shè)置日志輸出格式-->
<encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
<pattern>${log.pattern}</pattern>
<!-- 編碼 -->
<charset>UTF-8</charset>
</encoder>
</appender>
<!-- warn級別-->
<appender name="WARN_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<filter class="ch.qos.logback.classic.filter.LevelFilter">
<level>WARN</level>
<OnMismatch>DENY</OnMismatch>
<OnMatch>ACCEPT</OnMatch>
</filter>
<File>${log.path}/${logfile.prefix}-warn.log</File>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<FileNamePattern>${log.path}/${logfile.prefix}-warn.%d{yyyy-MM-dd}.log</FileNamePattern>
<maxHistory>30</maxHistory>
</rollingPolicy>
<!-- 設(shè)置日志輸出格式-->
<encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
<pattern>${log.pattern}</pattern>
<!-- 編碼 -->
<charset>UTF-8</charset>
</encoder>
</appender>
<!-- error級別-->
<appender name="ERROR_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<filter class="ch.qos.logback.classic.filter.LevelFilter">
<level>ERROR</level>
<OnMismatch>DENY</OnMismatch>
<OnMatch>ACCEPT</OnMatch>
</filter>
<File>${log.path}/${logfile.prefix}-error.log</File>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<FileNamePattern>${log.path}/${logfile.prefix}-error.%d{yyyy-MM-dd}.log</FileNamePattern>
<maxHistory>30</maxHistory>
</rollingPolicy>
<!-- 設(shè)置日志輸出格式-->
<encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
<pattern>${log.pattern}</pattern>
<!-- 編碼 -->
<charset>UTF-8</charset>
</encoder>
</appender>
<logger name="com.buydeem.share.log.logback" level="debug" additivity="true"/>
<!-- 日志輸出 -->
<root level="DEBUG">
<appender-ref ref="STDOUT"/>
<appender-ref ref="DEBUG_FILE"/>
<appender-ref ref="INFO_FILE"/>
<appender-ref ref="WARN_FILE"/>
<appender-ref ref="ERROR_FILE"/>
</root>
</configuration>