logback是java的日志開源組件,是log4j創(chuàng)始人寫的,性能比log4j要好,目前主要分為3個(gè)模塊
logback-core:核心代碼模塊
logback-classic:log4j的一個(gè)改良版本,同時(shí)實(shí)現(xiàn)了slf4j
的接口,這樣你如果之后要切換其他日志組件也是一件很容易的事
logback-access:訪問模塊與Servlet容器集成提供通過Http來訪問日志的功能
本篇博客會講解logback的使用、配置詳解、以及l(fā)ogback簡單的一個(gè)原理。
引入maven依賴
<!--這個(gè)依賴直接包含了 logback-core 以及 slf4j-api的依賴--><dependency> <groupId>ch.qos.logback</groupId> <artifactId>logback-classic</artifactId> <version>1.2.3</version></dependency>
然后就可以直接在代碼中使用slf4j的接口獲取Logger輸出日志了。(配置在下面的章節(jié)介紹)
//這是slf4j的接口,由于我們引入了logback-classic依賴,所以底層實(shí)現(xiàn)是logbackprivate static final Logger LOGGER = LoggerFactory.getLogger(Test.class);public static void main(String[] args) throws InterruptedException { LOGGER.info('hello world');}
logback在啟動的時(shí)候,會按照下面的順序加載配置文件
如果java程序啟動時(shí)指定了logback.configurationFile
屬性,就用該屬性指定的配置文件。如java -Dlogback.configurationFile=/path/to/mylogback.xml Test
,這樣執(zhí)行Test類的時(shí)候就會加載/path/to/mylogback.xml配置
在classpath中查找 logback.groovy 文件
在classpath中查找 logback-test.xml 文件
在classpath中查找 logback.xml 文件
如果是 jdk6 ,那么會調(diào)用ServiceLoader 查找 com.qos.logback.classic.spi.Configurator接口的第一個(gè)實(shí)現(xiàn)類
自動使用ch.qos.logback.classic.BasicConfigurator,在控制臺輸出日志
上面的順序表示優(yōu)先級,使用java -D配置的優(yōu)先級最高,只要獲取到配置后就不會再執(zhí)行下面的流程。相關(guān)代碼可以看ContextInitializer#autoConfig()
方法。
在slf4j中,從小到大的日志級別依舊是trace、debug、info、warn、error
。
<?xml version='1.0' encoding='UTF-8'?><configuration debug='true' scan='true' scanPeriod='1 seconds'> <contextName>logback</contextName> <!--定義參數(shù),后面可以通過${app.name}使用--> <property name='app.name' value='logback_test'/> <!--ConsoleAppender 用于在屏幕上輸出日志--> <appender name='stdout' class='ch.qos.logback.core.ConsoleAppender'> <!--定義了一個(gè)過濾器,在LEVEL之下的日志輸出不會被打印出來--> <!--這里定義了DEBUG,也就是控制臺不會輸出比ERROR級別小的日志--> <filter class='ch.qos.logback.classic.filter.ThresholdFilter'> <level>DEBUG</level> </filter> <!-- encoder 默認(rèn)配置為PatternLayoutEncoder --> <!--定義控制臺輸出格式--> <encoder> <pattern>%d [%thread] %-5level %logger{36} [%file : %line] - %msg%n</pattern> </encoder> </appender> <appender name='file' class='ch.qos.logback.core.rolling.RollingFileAppender'> <!--定義日志輸出的路徑--> <!--這里的scheduler.manager.server.home 沒有在上面的配置中設(shè)定,所以會使用java啟動時(shí)配置的值--> <!--比如通過 java -Dscheduler.manager.server.home=/path/to XXXX 配置該屬性--> <file>${scheduler.manager.server.home}/logs/${app.name}.log</file> <!--定義日志滾動的策略--> <rollingPolicy class='ch.qos.logback.core.rolling.TimeBasedRollingPolicy'> <!--定義文件滾動時(shí)的文件名的格式--> <fileNamePattern>${scheduler.manager.server.home}/logs/${app.name}.%d{yyyy-MM-dd.HH}.log.gz </fileNamePattern> <!--60天的時(shí)間周期,日志量最大20GB--> <maxHistory>60</maxHistory> <!-- 該屬性在 1.1.6版本后 才開始支持--> <totalSizeCap>20GB</totalSizeCap> </rollingPolicy> <triggeringPolicy class='ch.qos.logback.core.rolling.SizeBasedTriggeringPolicy'> <!--每個(gè)日志文件最大100MB--> <maxFileSize>100MB</maxFileSize> </triggeringPolicy> <!--定義輸出格式--> <encoder> <pattern>%d [%thread] %-5level %logger{36} [%file : %line] - %msg%n</pattern> </encoder> </appender> <!--root是默認(rèn)的logger 這里設(shè)定輸出級別是debug--> <root level='trace'> <!--定義了兩個(gè)appender,日志會通過往這兩個(gè)appender里面寫--> <appender-ref ref='stdout'/> <appender-ref ref='file'/> </root> <!--對于類路徑以 com.example.logback 開頭的Logger,輸出級別設(shè)置為warn,并且只輸出到控制臺--> <!--這個(gè)logger沒有指定appender,它會繼承root節(jié)點(diǎn)中定義的那些appender--> <logger name='com.example.logback' level='warn'/> <!--通過 LoggerFactory.getLogger('mytest') 可以獲取到這個(gè)logger--> <!--由于這個(gè)logger自動繼承了root的appender,root中已經(jīng)有stdout的appender了,自己這邊又引入了stdout的appender--> <!--如果沒有設(shè)置 additivity='false' ,就會導(dǎo)致一條日志在控制臺輸出兩次的情況--> <!--additivity表示要不要使用rootLogger配置的appender進(jìn)行輸出--> <logger name='mytest' level='info' additivity='false'> <appender-ref ref='stdout'/> </logger> <!--由于設(shè)置了 additivity='false' ,所以輸出時(shí)不會使用rootLogger的appender--> <!--但是這個(gè)logger本身又沒有配置appender,所以使用這個(gè)logger輸出日志的話就不會輸出到任何地方--> <logger name='mytest2' level='info' additivity='false'/></configuration>
屬性名稱 | 默認(rèn)值 | 介紹 |
---|---|---|
debug | false | 要不要打印 logback內(nèi)部日志信息,true則表示要打印。建議開啟 |
scan | true | 配置發(fā)送改變時(shí),要不要重新加載 |
scanPeriod | 1 seconds | 檢測配置發(fā)生變化的時(shí)間間隔。如果沒給出時(shí)間單位,默認(rèn)時(shí)間單位是毫秒 |
設(shè)置日志上下文名稱,后面輸出格式中可以通過定義 %contextName 來打印日志上下文名稱
用來設(shè)置相關(guān)變量,通過key-value的方式配置,然后在后面的配置文件中通過 ${key}來訪問
日志輸出組件,主要負(fù)責(zé)日志的輸出以及格式化日志。常用的屬性有name和class
屬性名稱 | 默認(rèn)值 | 介紹 |
---|---|---|
name | 無默認(rèn)值 | appender組件的名稱,后面給logger指定appender使用 |
class | 無默認(rèn)值 | appender的具體實(shí)現(xiàn)類。常用的有 ConsoleAppender、FileAppender、RollingFileAppender |
ConsoleAppender:向控制臺輸出日志內(nèi)容的組件,只要定義好encoder節(jié)點(diǎn)就可以使用。
FileAppender:向文件輸出日志內(nèi)容的組件,用法也很簡單,不過由于沒有日志滾動策略,一般很少使用
RollingFileAppender:向文件輸出日志內(nèi)容的組件,同時(shí)可以配置日志文件滾動策略,在日志達(dá)到一定條件后生成一個(gè)新的日志文件。
appender節(jié)點(diǎn)中有一個(gè)子節(jié)點(diǎn)filter
,配置具體的過濾器,比如上面的例子配置了一個(gè)內(nèi)置的過濾器ThresholdFilter,然后設(shè)置了level的值為DEBUG。這樣用這個(gè)appender輸出日志的時(shí)候都會經(jīng)過這個(gè)過濾器,日志級別低于DEBUG的都不會輸出來。
在RollingFileAppender中,可以配置相關(guān)的滾動策略,具體可以看配置樣例的注釋。
root節(jié)點(diǎn)和logger節(jié)點(diǎn)其實(shí)都是表示Logger
組件。個(gè)人覺的可以把他們之間的關(guān)系可以理解為父子關(guān)系,root是最頂層的logger,正常情況getLogger('name/class')沒有找到對應(yīng)logger的情況下,都是使用root節(jié)點(diǎn)配置的logger。
如果配置了logger,并且通過getLogger('name/class')獲取到這個(gè)logger,輸出日志的時(shí)候,就會使用這個(gè)logger配置的appender輸出,同時(shí)還會使用rootLogger配置的appender。我們可以使用logger節(jié)點(diǎn)的additivity='false'
屬性來屏蔽rootLogger的appender。這樣就可以不使用rootLogger的appender輸出日志了。
關(guān)于logger的獲取,一般logger是配置name的。我們再代碼中經(jīng)常通過指定的CLass來獲取Logger,比如這樣LoggerFactory.getLogger(Test.class);
,其實(shí)這個(gè)最后也是轉(zhuǎn)成對應(yīng)的包名 類名的字符串com.kongtrio.Test.class
。假設(shè)有一個(gè)logger配置的那么是com.kongtrio
,那么通過LoggerFactory.getLogger(Test.class)
獲取到的logger就是這個(gè)logger。
也就是說,name可以配置包名,也可以配置自定義名稱。
上面說的logger和root節(jié)點(diǎn)的父子關(guān)系只是為了方便理解,具體的底層實(shí)現(xiàn)本人并沒有看,他們之間真正的關(guān)系讀者有興趣的話可以去看logback的源碼
在看logback的啟動日志時(shí),看到下面這句話。
no applicable action for [totalSizeCap], current ElementPath is [[configuration][appender][rollingPolicy][totalSizeCap]]
no applicable action for [maxFileSize], current ElementPath is [[configuration][appender][rollingPolicy][maxFileSize]]
大概意思解析logbck配置時(shí)不支持totalSizeCap、maxFileSize的配置。后來查了下,果然,totalSizeCap是在1.1.6之后的版本才開始支持的,切換到1.1.7之后就不會出現(xiàn)這句話了。
maxFileSize比較奇怪,試了目前所有的版本都不支持rollingPolicy—maxFileSize的配置方案,如果配置到triggeringPolicy節(jié)點(diǎn)下,又是可以生效的。但是官網(wǎng)給的文檔上又有出現(xiàn)rollingPolicy下面的。
Ps:啟動的時(shí)候建議多看看日志,可以及早發(fā)現(xiàn)一些問題。比如這些配置沒生效,看到這些日志就可以馬上調(diào)整,而不會因?yàn)闆]達(dá)到預(yù)期的效果而造成一些損失。
slf4j只是一套標(biāo)準(zhǔn),通俗來講,就是定義了一系列接口,它并不提供任何的具體實(shí)現(xiàn)。所以,我們使用這套接口進(jìn)行開發(fā),可以任意的切換底層的實(shí)現(xiàn)框架。
比如,一開始項(xiàng)目用的是log4j的實(shí)現(xiàn),后來發(fā)現(xiàn)log4j的性能太差了,想換成logback,由于我們代碼中都是面向slf4j接口的,這樣我們只要吧log4j的依賴換成logback就可以了。
我們在調(diào)用LoggerFactory.getLogger(Test.class)
時(shí),這些接口或者類都是slf4j的,那么,它是怎么切換到logback的實(shí)現(xiàn)的呢?
為了解決這個(gè)問題,我追蹤了一下代碼,發(fā)現(xiàn)logback-classic底下,有一個(gè)slf4j的包.
slf4j在初始化時(shí)會調(diào)用org.slf4j.StaticLoggerBinder
進(jìn)行初始化。因此,每個(gè)要實(shí)現(xiàn)slf4j的日志組件項(xiàng)目,底下都要有org.slf4j.StaticLoggerBinder
的具體實(shí)現(xiàn)。這樣slf4j才會在初始化的關(guān)聯(lián)到具體的實(shí)現(xiàn)。
比如logback在自己定義的StaticLoggerBinder做了自己組件的初始化工作。下面是網(wǎng)上找的一個(gè)時(shí)序圖:
如果引入了多個(gè)slf4j的實(shí)現(xiàn)依賴包,那么各個(gè)包底下都有org.slf4j.StaticLoggerBinder
的實(shí)現(xiàn),這時(shí)候slf4j會調(diào)用哪個(gè)包的StaticLoggerBinder實(shí)現(xiàn)呢?
這個(gè)問題和java的類加載機(jī)制有關(guān)系,在雙親委派機(jī)制的模型中,這些引入的依賴包通常都是由Application ClassLoader
來加載的。Application ClassLoader
會加載用戶路徑(classpath)上指定的類庫,如果多個(gè)org.slf4j.StaticLoggerBinder
的jar包實(shí)現(xiàn),類加載器先掃到哪個(gè)jar包,就會使用jar包提供的實(shí)現(xiàn)。
舉個(gè)例子,我們通過 java -classpath a.jar:b.jar Test
運(yùn)行Test類,a.jar和b.jar都定義了org.slf4j.StaticLoggerBinder
的實(shí)現(xiàn),那么執(zhí)行Test時(shí)加載StaticLoggerBinder類就會加載a.jar中的那個(gè)定義類。因?yàn)閍.jar在classpath中排在比較前面。
日志組件的使用一般都非常簡單,幾乎所有的項(xiàng)目中都會用到各種各樣的日志組件。但是可能就是由于太簡單了,比較少的人會愿意深入系統(tǒng)的去了解。本人也只是對logback的配置以及一些簡單的原理做了一些了解,并沒有很深入的去看logback的具體實(shí)現(xiàn)。
因此,本文的內(nèi)容大部分都是基于官網(wǎng)的文檔以及網(wǎng)上一些其他關(guān)于logback的博客,雖然也做了一些簡單的測試,但并不保證全部都是正確的。