幾種日志的區(qū)別
-
commons-logging
apache
最早提供的日志的門面接口甥捺。避免和具體的日志方案直接耦合瘟仿。類似于JDBC
的api
接口,具體的的JDBC driver
實(shí)現(xiàn)由各數(shù)據(jù)庫(kù)提供商實(shí)現(xiàn)。通過統(tǒng)一接口解耦荤胁,不過其內(nèi)部也實(shí)現(xiàn)了一些簡(jiǎn)單日志方案。 -
Log4j
Logging for Java
屎债,經(jīng)典的一種日志解決方案仅政。內(nèi)部把日志系統(tǒng)抽象封裝成Logger
、appender
盆驹、pattern
等實(shí)現(xiàn)圆丹。我們可以通過配置文件輕松的實(shí)現(xiàn)日志系統(tǒng)的管理和多樣化配置。 -
slf4j
全稱為Simple Logging Facade for Java
躯喇。 是對(duì)不同日志框架提供的一個(gè)門面封裝辫封。可以在部署的時(shí)候不修改任何配置即可接入一種日志實(shí)現(xiàn)方案廉丽。和commons-loging
類似倦微。個(gè)人感覺設(shè)從計(jì)上更好一些,沒有commons
那么多潛規(guī)則正压。同時(shí)有兩個(gè)額外特點(diǎn):①能支持多個(gè)參數(shù)欣福,并通過{}
占位符進(jìn)行替換,避免老寫logger.isXXXEnabled
這種無奈的判斷焦履,帶來性能提升見拓劝;②OSGI
機(jī)制更好兼容支持雏逾。 -
logback
作為一個(gè)通用可靠、快速靈活的日志框架郑临,將作為Log4j
的替代和slf4j
組成新的日志系統(tǒng)的完整實(shí)現(xiàn)校套。具有極佳的性能,在關(guān)鍵路徑上執(zhí)行速度是log4j
的10 倍牧抵,且內(nèi)存消耗更少笛匙。 -
Log4j2
Log4j2
是Log4j
的升級(jí)版,與之前的版本Log4j 1.x
相比犀变、有重大的改進(jìn)妹孙,在修正了Logback
固有的架構(gòu)問題的同時(shí),改進(jìn)了許多Logback
所具有的功能获枝。
可以這么理解蠢正,slf4j
和commons-logging
是一種抽象接口,Log4j
省店、Log4j2
和logback
是它們的實(shí)現(xiàn)嚣崭,在實(shí)際使用中,一般選擇slf4j+Log4j2
或者slf4j+logback
懦傍。
性能對(duì)比可以看:Apache Log4j 2.0值得升級(jí)嗎
下文僅討論slf4j+logback
配置及使用
1雹舀、引入相關(guān)日志依賴,去除其他無關(guān)日志依賴
既然選擇了slf4j+logback
粗俱,首先要將項(xiàng)目中的其他jar包说榆,如commons-logging
、Log4j
去掉寸认。
如果項(xiàng)目使用maven签财,可以使用mvn dependency:tree
命令來查看描繪項(xiàng)目依賴樹,看哪些顯式的dependecy依賴了它們偏塞。
如spring-core
里面就集成了commons-logging
唱蒸,可以通過exclusions
標(biāo)簽將其排除掉。
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>${spring.version}</version>
<!--因?yàn)槭褂昧藄l4j灸叼,所以去掉commons-logging-->
<exclusions>
<exclusion>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
</exclusion>
</exclusions>
</dependency>
但spring本身日志就使用的commons-logging
神汹,僅僅去掉就會(huì)使其不能正常工作,還需要添加commons logging
到slf4j
的橋接器jcl-over-slf4j
怜姿,如下在項(xiàng)目中添加該依賴:
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>jcl-over-slf4j</artifactId>
<version>${jcl.over.slf4j.version}</version>
</dependency>
如果有直接使用log4j
的組件慎冤,也要將log4j
排除掉,同時(shí)添加log4j-over-slf4
沧卢。
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>log4j-over-slf4j</artifactId>
</dependency>
最后,可以使用mvn dependency:tree
或者mvn dependency:list
查看項(xiàng)目依賴醉者,看是否存在重復(fù)引入等但狭,比如我們使用slf4j+logback
的方案披诗,只需要引入logback-classic
即可,不必再顯示添加slf4j-api
和logback-core
立磁,因?yàn)?code>logback-classic本身依賴它們呈队。
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>${logback.version}</version>
</dependency>
這一步就是引入logback
和slf4j
,去掉common-logging
唱歧、Log4j
宪摧、Log4j
等無關(guān)日志組件,同時(shí)添加commons logging
到slf4j
的橋接器jcl-over-slf4j
(如果項(xiàng)目中原來使用到了commons logging
)颅崩。
2几于、配置logback
logback
在啟動(dòng)時(shí),根據(jù)以下步驟尋找配置文件:①在classpath
中尋找logback-test.xml
文件→②如果找不到logback-test.xml
沿后,則在 classpath
中尋找logback.groovy
文件→③如果找不到 logback.groovy
沿彭,則在classpath
中尋找logback.xml
文件
如果上述的文件都找不到,則logback
會(huì)使用JDK
的SPI
機(jī)制查找 META-INF/services/ch.qos.logback.classic.spi.Configurator
中的 logback
配置實(shí)現(xiàn)類尖滚,這個(gè)實(shí)現(xiàn)類必須實(shí)現(xiàn)Configuration
接口喉刘,使用它的實(shí)現(xiàn)來進(jìn)行配置
如果上述操作都不成功,logback
就會(huì)使用它自帶的 BasicConfigurator
來配置漆弄,并將日志輸出到console
睦裳。
這里采用xml格式的配置文件:在resources
目錄下新建logback.xml
文件,logback.xml
文件的具體配置如下(僅供參考):
<?xml version="1.0" encoding="UTF-8"?>
<!--scan:
當(dāng)此屬性設(shè)置為true時(shí)撼唾,配置文件如果發(fā)生改變推沸,將會(huì)被重新加載,默認(rèn)值為true券坞。
scanPeriod:
設(shè)置監(jiān)測(cè)配置文件是否有修改的時(shí)間間隔鬓催,如果沒有給出時(shí)間單位,默認(rèn)單位是毫秒恨锚。當(dāng)scan為true時(shí)宇驾,此屬性生效。默認(rèn)的時(shí)間間隔為1分鐘猴伶。
debug:
當(dāng)此屬性設(shè)置為true時(shí)课舍,將打印出logback內(nèi)部日志信息,實(shí)時(shí)查看logback運(yùn)行狀態(tài)他挎。默認(rèn)值為false筝尾。
configuration 子節(jié)點(diǎn)為 appender、logger办桨、root
-->
<configuration scan="true" scanPeriod="60 seconds" debug="false">
<!--用于區(qū)分不同應(yīng)用程序的記錄-->
<contextName>edu-cloud</contextName>
<!--日志文件所在目錄筹淫,如果是tomcat,如下寫法日志文件會(huì)在則為${TOMCAT_HOME}/bin/logs/目錄下-->
<property name="LOG_HOME" value="logs"/>
<!--控制臺(tái)-->
<appender name="stdout" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<!--格式化輸出:%d表示日期呢撞,%thread表示線程名损姜,%-5level:級(jí)別從左顯示5個(gè)字符寬度 %logger輸出日志的logger名 %msg:日志消息饰剥,%n是換行符 -->
<pattern>[%d{yyyy-MM-dd HH:mm:ss.SSS}] [%thread] %-5level %logger{36} : %msg%n</pattern>
<!--解決亂碼問題-->
<charset>UTF-8</charset>
</encoder>
</appender>
<!--滾動(dòng)文件-->
<appender name="infoFile" class="ch.qos.logback.core.rolling.RollingFileAppender">
<!-- ThresholdFilter:臨界值過濾器,過濾掉 TRACE 和 DEBUG 級(jí)別的日志 -->
<filter class="ch.qos.logback.classic.filter.ThresholdFilter">
<level>INFO</level>
</filter>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>${LOG_HOME}/log.%d{yyyy-MM-dd}.log</fileNamePattern>
<maxHistory>30</maxHistory><!--保存最近30天的日志-->
</rollingPolicy>
<encoder>
<charset>UTF-8</charset>
<pattern>[%d{yyyy-MM-dd HH:mm:ss.SSS}] [%thread] %-5level %logger{36} : %msg%n</pattern>
</encoder>
</appender>
<!--滾動(dòng)文件-->
<appender name="errorFile" class="ch.qos.logback.core.rolling.RollingFileAppender">
<!-- ThresholdFilter:臨界值過濾器摧阅,過濾掉 TRACE 和 DEBUG 級(jí)別的日志 -->
<filter class="ch.qos.logback.classic.filter.ThresholdFilter">
<level>error</level>
</filter>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>${LOG_HOME}/error.%d{yyyy-MM-dd}.log</fileNamePattern>
<maxHistory>30</maxHistory><!--保存最近30天的日志-->
</rollingPolicy>
<encoder>
<charset>UTF-8</charset>
<pattern>[%d{yyyy-MM-dd HH:mm:ss.SSS}] [%thread] %-5level %logger{36} : %msg%n</pattern>
</encoder>
</appender>
<!--將日志輸出到logstack-->
<!--<appender name="logstash" class="net.logstash.logback.appender.LogstashTcpSocketAppender">
<destination>47.93.173.81:7002</destination>
<encoder class="net.logstash.logback.encoder.LogstashEncoder">
<charset>UTF-8</charset>
</encoder>
<keepAliveDuration>5 minutes</keepAliveDuration>
</appender>-->
<!--這里如果是info汰蓉,spring、mybatis等框架則不會(huì)輸出:TRACE < DEBUG < INFO < WARN < ERROR-->
<!--root是所有l(wèi)ogger的祖先棒卷,均繼承root顾孽,如果某一個(gè)自定義的logger沒有指定level,就會(huì)尋找
父logger看有沒有指定級(jí)別比规,直到找到root若厚。-->
<root level="debug">
<appender-ref ref="stdout"/>
<appender-ref ref="infoFile"/>
<appender-ref ref="errorFile"/>
<appender-ref ref="logstash"/>
</root>
<!--為某個(gè)包單獨(dú)配置logger
比如定時(shí)任務(wù),寫代碼的包名為:com.seentao.task
步驟如下:
1苞俘、定義一個(gè)appender盹沈,取名為task(隨意,只要下面logger引用就行了)
appender的配置按照需要即可
2吃谣、定義一個(gè)logger:
<logger name="com.seentao.task" level="DEBUG" additivity="false">
<appender-ref ref="task" />
</logger>
注意:additivity必須設(shè)置為false乞封,這樣只會(huì)交給task這個(gè)appender,否則其他appender也會(huì)打印com.seentao.task里的log信息岗憋。
3肃晚、這樣,在com.seentao.task的logger就會(huì)是上面定義的logger了仔戈。
private static Logger logger = LoggerFactory.getLogger(Class1.class);
-->
</configuration>
在進(jìn)行配置的時(shí)候关串,主要要理解或記住以下幾點(diǎn)(主要標(biāo)簽的用處):
-
appender
,負(fù)責(zé)定義日志的輸出目的地(控制臺(tái)监徘、日志文件晋修、滾動(dòng)日志文件,其他如logstash等)凰盔。-
encoder
負(fù)責(zé)定義日志的輸出樣式和字符編碼墓卦,如果在控制臺(tái)出現(xiàn)?????
或亂碼,則指定編碼(一般是UTF-8
)就好了户敬。 -
filter
負(fù)責(zé)過濾日志落剪,即使logger傳來了dubug
級(jí)別以上的日志,如果filter
中設(shè)定了級(jí)別為info
尿庐,則該appender
只會(huì)將info
級(jí)別及以上的日志輸出到目的地忠怖。 -
rollingPolicy
負(fù)責(zé)制定日志文件的滾動(dòng)規(guī)則,是根據(jù)日志文件大小還是根據(jù)日期進(jìn)行滾動(dòng)抄瑟。
-
logger
凡泣,負(fù)責(zé)定義我們實(shí)際代碼中使用的logger
。logger
中有一個(gè)非常重要的屬性name
,name
必須指定问麸。在logback
中往衷,logger
有繼承關(guān)系钞翔,而所有的logger
的祖先是root
严卖。
舉個(gè)例子,如果我們有個(gè)類叫UserService
布轿,所在的包為com.maxwell.service
哮笆,在UserService
中要打印日志,我們一般會(huì)這么寫:
①private Logger logger = LoggerFactory.getLogger(UserService.class);
或者
②private Logger logger = LoggerFactory.getLogger("com.maxwell.service.UserService");
這兩種寫法是一樣的汰扭,第①中寫法實(shí)際會(huì)轉(zhuǎn)化為②中的方式來獲取一個(gè)logger
實(shí)例稠肘。
當(dāng)我們寫下這行代碼時(shí),logback
會(huì)依次檢查以下各個(gè)logger
實(shí)例的是否存在萝毛,如果不存在則依次創(chuàng)建:
com
com.maxwell
com.maxwell.service
com.maxwell.service.UserService
而創(chuàng)建logger實(shí)例的時(shí)候项阴,就會(huì)去配置文件中查找名字為com
、com.maxwell
笆包、com.maxwell.service
环揽、com.maxwell.service.UserService
的logge
標(biāo)簽,并根據(jù)其中定義的規(guī)則創(chuàng)建庵佣。所以歉胶,假如你在配置文件中沒有定義name
為上述字符串的logger
時(shí),就會(huì)找到root
這個(gè)祖先巴粪,根據(jù)root
標(biāo)簽定義的規(guī)則創(chuàng)建logger
實(shí)例通今。
理解了上面的這一點(diǎn),想要實(shí)現(xiàn)某個(gè)包或者某個(gè)類單獨(dú)輸出到某日志文件的需求就很好實(shí)現(xiàn)了:①定義一個(gè)appender
肛根,指明日志文件的輸出目的地辫塌;②定義一個(gè)logger
,name
設(shè)為那個(gè)包或類的全路徑名派哲。如果只想將這個(gè)類或包的日志輸出到剛才定義的appender
中臼氨,就將additivity
設(shè)置為false
。
還有就是狮辽,上面的參考配置可以保證mybatis
的sql
語句一也、spring
的日志正常輸出到控制臺(tái),但由于mybatis
的sql
語句輸出的級(jí)別為debug
喉脖,所以不會(huì)輸出到name
為infoFile
的appender
中椰苟,因?yàn)樵?code>appender中設(shè)置了級(jí)別為info
的過濾器filter,如果想將mybatis
的sql
語句也輸出到日志文件中树叽,請(qǐng)將info
改為debug
舆蝴。
也就是,一條日志想要順利到達(dá)輸出目的地,除了logger
的級(jí)別要低于該級(jí)別洁仗,appender
中的filter
中設(shè)置的級(jí)別也要低于該級(jí)別层皱。(TRACE < DEBUG < INFO < WARN < ERROR)
3、使用
在代碼中獲取logger時(shí)赠潦,我們可能有兩種方式:
private static Logger logger = LoggerFactory.getLogger(UserService.class);
private Logger logger = LoggerFactory.getLogger(UserService.class);
你可能會(huì)認(rèn)為不加static
會(huì)為UserService
類的不同實(shí)例創(chuàng)建多個(gè)logger
實(shí)例叫胖,實(shí)際不是的,某個(gè)類的logger
實(shí)例一旦創(chuàng)建她奥,logback
會(huì)將其緩存在一個(gè)map
中(Map<String, Logger> loggerCache
)瓮增,下次獲取的時(shí)候直接從這個(gè)map
中獲取。所以哩俭,上述兩種寫法性能差距不大绷跑,只多一個(gè)很小的從cache
中取對(duì)象的開銷:加上static
,下次獲取的時(shí)候就直接從內(nèi)存中取了(類變量是多個(gè)實(shí)例共享的)凡资,不會(huì)再調(diào)用LoggerFactory.getLogger(UserService.class)
砸捏,而不加static
則從logback
的緩存中取,需要調(diào)用LoggerFactory.getLogger(UserService.class)
隙赁。
最后要注意的是垦藏,這里的Logger
和LoggerFactory
是slf4j
包里的,不要選成了java.util.logging
鸳谜。如果你的IDE
出現(xiàn)了slf4j
和java
自帶日志包外的其他選項(xiàng)膝藕,就要考慮你是不是沒有將其他不相關(guān)的日志組件從你的項(xiàng)目中去掉。
參考
logback中l(wèi)ogger對(duì)象的創(chuàng)建過程源碼分析
Why log4j's Logger.getLogger() need pass a Class type?
logback logback.xml常用配置詳解
java日志咐扭,需要知道的幾件事(commons-logging,log4j,slf4j,logback)
logback 配置詳解
logback為單獨(dú)的包配置日志輸出文件
Apache Log4j 2.0值得升級(jí)嗎