為什么要使用日志
剛開始接觸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中的日志體系特別亂宪拥,特別對(duì)于新手來說簡直就是一臉悶逼。你肯定聽過Log4j铣减、JUL江解、JCL、Slf4j徙歼、Logback、Log4j2這些里面的幾種鳖枕,面對(duì)這么多日志相關(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)档玻。也就是說我們?cè)诖a里使用JCL的API,底層我們可以使用Log4j或者JUC實(shí)現(xiàn)茫藏,這樣我們?cè)谇袚Q日志實(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對(duì)戰(zhàn)像寒。
看了上面的java日志工具的發(fā)展烘豹,現(xiàn)在大概明白了日志工具的體系了吧。對(duì)于上面說的工具基本上可以分為兩類诺祸,一類是日志接口携悯,也就是日志門面,它們不提供實(shí)現(xiàn)或者提供簡單的實(shí)現(xiàn)筷笨。而另外一類是日志實(shí)現(xiàn)憔鬼,也就是實(shí)際上實(shí)現(xiàn)日志功能的工具。
對(duì)于日志工具包門面工具主要就是Slf4j和JCL這個(gè)兩個(gè)胃夏,而實(shí)際上因?yàn)樾蕟栴}轴或,目前日志門面大家都使用的是Slf4j,而JCL逐漸的退出了舞臺(tái)仰禀。
對(duì)于日志的實(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)。
對(duì)于appender節(jié)點(diǎn)來說我們還可以配置其他東西荚板,例如我們可以篩選日志的級(jí)別凤壁,讓該appender中只輸出某種級(jí)別的日志。利用這個(gè)設(shè)置跪另,我們可以將不同級(jí)別的日志輸出到不同的文件中拧抖。
logger和root
logger用來設(shè)置某一個(gè)包或者具體某一個(gè)類的日志打印級(jí)別以及指定appender。你可以理解logger和root是同一種東西免绿,不同的在于root是logger的最上級(jí)唧席。對(duì)于logger節(jié)點(diǎn)中,我們可以設(shè)置的屬性有l(wèi)evel和additivity兩個(gè)屬性。
level代表該appender中輸出的最低日志級(jí)別淌哟,例如上面的配置文件中我們將其設(shè)置成debug代表低于該級(jí)別的日志將不會(huì)再logger中打印出來迹卢。如果我們沒有設(shè)置將應(yīng)用父級(jí)的level,如果logger沒有父級(jí)則會(huì)使用root中的level(root是所有l(wèi)evel最上級(jí))徒仓。
additivity用來設(shè)置該logger是否向上級(jí)傳遞腐碱,如果設(shè)置為true,該logger下打印的日志還會(huì)傳遞到root中掉弛。在我們的配置中症见,我們并沒有在logger下配置appender,但是仍然可以打印出日志殃饿。這是因?yàn)槲覀冊(cè)趓oot節(jié)點(diǎn)下配置了appender谋作,并且我們還把logger的additivity設(shè)置成了true。如果我們將該值改為false乎芳,你可以發(fā)現(xiàn)日志將不會(huì)再控制臺(tái)中輸出了遵蚜。
配置不同級(jí)別的日志內(nèi)容輸出到不同的文件
對(duì)于實(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"/>
<!--日志路徑,可寫相對(duì)路徑携取,也可寫絕對(duì)路徑 -->
<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級(jí)別的日志 -->
<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級(jí)別-->
<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級(jí)別-->
<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級(jí)別-->
<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>