在現現在的應用中,日誌已經成爲了一個很是重要的工具。經過系統打印的日誌,能夠監測系統的運行狀況,排查系統錯誤的緣由。日誌從最先期的System.out.print
到現在各類成熟的框架,使得日誌打印更加規範化和清晰化。尤爲是SLF4J的出現,爲日誌框架定義了通用的FACADE接口和能力。只須要在應用中引入SLF4J包和具體實現該FACADE的日誌包,上層應用就能夠只須要面向SLF4J接口編程,而無需關心具體的底層的日誌框架,實現了上層應用和底層日誌框架的解耦。Logback做爲一個支持SLF4J通用能力的框架,成爲了煊赫一時的日誌框架之一。今天就來稍微瞭解一下Logback日誌的一些基礎能力以及配置文件。html
logback主要由三個模塊組成,分別是logback-core
,logback-classic
和logback-access
。其中logback-core
是整個Logback的核型模塊,logback-classic
支持了SLF4J FACADE,而logback-access
則集成了Servlet容齊來提供HTTP日誌功能,適用於web應用。下面主要是基於logback-classic
來進行介紹。java
引入logback-classic的包以下:web
<dependency> <groupId>ch.qos.logback</groupId> <artifactId>logback-classic</artifactId> <version>1.3.0-alpha5</version> </dependency>
上面拉取的Maven包基於傳遞性遠離,會自動拉取logback-classic,logback-core和slf4j-api.jar,所以無需在項目中再額外聲明SLF4J和logback-core的依賴。數據庫
由於logback-classic
實現了SLF4J FACADE,因此上層應用只須要面向SLF4J的調用語法便可。下面代碼展現瞭如何獲取到Logger對象用來打印日誌。編程
import org.slf4j.Logger; import org.slf4j.LoggerFactory; import ch.qos.logback.classic.LoggerContext; import ch.qos.logback.core.util.StatusPrinter; public class HelloWorld2 { public static void main(String[] args) { //這裏的Logger和LoggerFactory均爲SLF4J的類,真正調用時會使用Logback的日誌能力 //getLogger方法中傳入的是Logger的名稱,這個名稱在後面講解配置文件中的<logger>時會繼續提到 Logger logger = LoggerFactory.getLogger("chapters.introduction.HelloWorld2"); //打印一條Debug級別的日誌 logger.debug("Hello world."); //獲取根Logger,使用場景比較少 Logger rootLogger = LoggerFactory.getLogger(org.slf4j.Logger.ROOT_LOGGER_NAME); } }
Logback中每個Logger都有對應的日誌級別,該日誌級別能夠是Logger本身定義的,也能夠是從父Logger上繼承下來的。Logback一共支持5個日誌級別,從高到低分別是ERROR,WARN,INFO,DEBUG,TRACE。Logger的日誌級別決定了哪些級別的日誌能夠被輸出。只有大於等於該Logger級別的日誌纔會被打印出來。好比假設上文中獲取的名爲"chapters.introduction.HelloWorld2"的Logger日誌級別爲INFO,則調用logger.debug("xxx")不會輸出日誌內容,由於DEBUG日誌級別低於INFO日誌級別。api
日誌級別能夠幫助咱們控制日誌打印的粒度,好比在開發環境能夠將日誌級別設置到DEBUG幫助排查問題,而在生產環境則能夠將日誌級別設置到INFO,從而減小沒必要要的打印日誌帶來的性能影響。服務器
有時候咱們每每並不僅是打印出一條完整的日誌,而是但願在日誌中附帶一些運行中參數,以下:架構
Logger logger = LoggerFactory.getLogger("chapters.introduction.HelloWorld2"); logger.debug("Hello World To " + username);
上文的日誌中除了打印了一些結構化的語句,還拼接了運行時執行這段邏輯的用戶的名稱。這裏就會帶來一個問題,即字符串拼接的問題。雖然JVM對String字符串的拼接已經進行了優化,可是假如當前的日誌級別爲INFO,那麼這段代碼所執行字符串拼接操做就是徹底沒必要要的。所以,建議在代碼加上一行日誌級別的判斷進行優化,以下:併發
//非debug級別不會執行字符串拼接操做,可是debug級別會執行兩次isDebugEnabled操做,性能影響不大 if(logger.isDebugEnabled()) { logger.debug("Hello World To " + username); }
可是,logback並不推薦在系統中使用字符串拼接的方式來輸出日誌,而是提倡使用參數傳遞的方式,由logback本身來執行日誌的序列化。以下:app
//logger方法會判斷是否爲debug級別,再決定將entry序列化拼接如字符串 logger.debug("The entry is {}.", entry);
這種日誌輸出方式就無需額外包一層日誌級別的判斷,由於logger.debug方法內部本身會判斷一第二天志級別,再去執行日誌內容轉碼的操做。注意,傳入的參數必須實現了toString方法,否則日誌在對對象進行轉碼時,只會打印出對象的內存地址,而不是對象中的具體內容
前文已經簡單介紹了logback包含的三個主要模塊,以及如何在代碼中基於SLF4J FACADE自由的使用日誌框架。下面開始從配置文件的角度來了解如何配置Logback。
Logback主要支持XML和groovy結構的配置文件,下文中將以XML結構爲基礎進行介紹。
上圖爲官網中對Logback配置文件總體結構的描述。配置文件以<configuration>
做爲根元素,其下包含1個<root>
元素用於定義根日誌的配置信息,還有0到多個<logger>
元素以及0到多個<appender>
元素。其中<logger>
元素對應了應用中經過LoggerFactory.getLogger()
獲取到的日誌工具,<appender>
元素定義了日誌的輸出目的地,一個<logger>
能夠關聯多個<appender>
,即容許將一樣的一行日誌輸出到多個目的地。
一個簡單的Logback配置文件以下:
<configuration> <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender"> <!-- encoders are by default assigned the type ch.qos.logback.classic.encoder.PatternLayoutEncoder --> <encoder> <pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern> </encoder> </appender> <root level="debug"> <appender-ref ref="STDOUT" /> </root> </configuration>
該配置文件聲明瞭一個輸出到控制檯名稱爲STDOUT的appender,再聲明瞭root logger的日誌級別爲debug,且規定將日誌輸出到STDOUT流中。
logback容許多配置文件,其加載時讀取配置文件的順序以下:
[com.qos.logback.classic.spi.Configurator](http://logback.qos.ch/xref/ch/qos/logback/classic/spi/Configurator.html)接口的實現
在簡單的瞭解了logback配置文件的基礎結構後,這一章詳細介紹一下logback中比較經常使用的幾個標籤以及各自表明的含義。
做爲配置文件的根標籤,configuration更多的是對整個Logback配置讀取的模式進行定義,configuration標籤匯中能夠定義的屬性以下:
logger是日誌流隔離的基本單元,每一個logger都會綁定到一個LoggerContext。Logger之間存在樹狀層級關係,即A Logger能夠是B Logger的父Logger。而它們之間的層級關係則是根據logger的名稱來決定的。假如logger A的name爲com.moduleA
,而logger B的name爲com.moduleA.packageA
,則能夠說A是B的父logger。這種樹狀結構的做用在於,假如B並無定義本身的日誌級別,則會繼承A的日誌級別。其它的如appender也會根據繼承關係計算得出。
logger只有一個name屬性是必填的,一般來講,除了須要特殊定義的幾個logger name以外,其它的基本都會以module的維度進行定義,從而確保模塊下的每個類在以本身的類名獲取Logger時,可以向上找到對應的Logger。
舉個例子,假如如今定義了一個name爲com.rale.service
的logger,則位於com.rale.service.HelloService.java
類中使用LoggerFactory.getLogger(HelloService.class)
獲取到的Logger,雖然在配置文件中並無聲明,可是會以該類的全路徑做爲logger的名稱,按照Logger的層級不斷向上找到最近的父Logger,並最終返回name爲com.rale.service
的logger。
logger還有一個標籤爲level,能夠爲該logger分配對應的日誌級別,只有高於該級別的日誌會輸出。若是沒有顯示定義level的值,則會從最近的顯式聲明瞭日誌級別的父節點繼承其日誌級別。
一個基礎的logger配置以下:
<logger name="integration" level="INFO" additivity="false"> <appender-ref ref="integration"/> <appender-ref ref="common-error"/> </logger>
一個logger下能夠包含多個appender-ref標籤,該標籤聲明瞭該logger的日誌會打印到這些輸出流中。這裏還有一個比較特殊的屬性additivity,它是用來約束appender繼承行爲的。在默認狀況下,aditicity的值爲true,即logger除了會打印到當前顯式聲明的appender-ref中,還會打印到全部從父Logger中繼承的appender中。例如假設root中聲明瞭<appender-ref ref="common">
,則integration會同時向這三個輸出流中打印日誌。若是父logger和子logger中存在相同的appender,該日誌也會向該appender打印兩遍。所以,經過additivity設置爲false,能夠減小由於意料以外的appender繼承致使日誌的過量輸出。
一個appender對應一個日誌輸出流。同一個appender能夠綁定在多個logger上,即多個logger都可以向該appender輸出日誌。所以appender的實現內部進行了併發控制,防止日誌亂碼。
appender支持的輸出端不少,包括控制檯,文件,遠程Socket服務器,MySQL,PostgreSQL等數據庫,遠程UNIX日誌進程,JMS等。
<appender>有兩個強制屬性name和class(Appender類的全路徑),包含0到多個<layout class="">標籤,0到多個<encoder class="">標籤,0到多個<filter>標籤。它還能夠包含任意多個Appender Bean類的成員變量屬性值。
其中layout和encoder標籤用來對appender中的日誌進行格式化,filter標籤則支持對appender中傳來的日誌信息進行過濾,來決定哪些日誌打印哪些不打印,所以能夠經過filter來定義appender維度的日誌級別。
一個典型的appender以下:
<appender name="common-error" class="ch.qos.logback.core.rolling.RollingFileAppender"> <file>${LOG_PATH}/sls-common-error.log</file> <encoder> <pattern>${LOCAL_FILE_LOG_PATTERN}</pattern> </encoder> <filter class="ch.qos.logback.classic.filter.ThresholdFilter"> <level>ERROR</level> </filter> </appender>
這裏聲明瞭一個文件輸出流,而且用file標籤訂義了輸出文件的位置,用encoder定義了日誌打印的格式。這裏經過引用變量的形式來定義,變量將在後面property標籤中詳細介紹。接着綁定了一個filter,而且使用該filter定義了appender只會打印出日誌級別大於等於ERROR級別的日誌。
root標籤要求在配置中必須聲明一次,root標籤其實定義的是root logger的配置信息,它的默認的日誌級別爲debug。全部的logger的最終的父logger必定是root logger。
property標籤支持在配置文件中聲明變量。配置文件的變量有三種來源,分別是經過JVM COMMAND,JAVA COMMAND,Classpth以及當前的配置文件。舉個例子,JAVA命令傳入變量的格式以下java -DUSER_HOME="/home/sebastien" MyApp2
。<property>標籤支持configuration文件中聲明成員變量,它支持三種類型:KV,文件相對路徑,Classpth下的文件。
<!--鍵值型聲明--> <property name="USER_HOME" value="/home/sebastien" /> <!--配置文件聲明--> <property file="src/main/java/chapters/configuration/variables1.properties" /> <!--Classpath資源--> <property resource="resource1.properties"/>
對於這些變量的引用採用標準Linux變量引用方法,經過${變量名稱}便可以引用變量的值。一樣也支持爲這些變量聲明默認值,經過${變量名稱:-默認值}
的語法結構。
一個簡單的聲明配置並使用的例子以下:
<configuration> <property name="USER_HOME" value="/home/sebastien" /> <appender name="FILE" class="ch.qos.logback.core.FileAppender"> <file>${USER_HOME}/myApp.log</file> <encoder> <pattern>%msg%n</pattern> </encoder> </appender> <root level="debug"> <appender-ref ref="FILE" /> </root> </configuration>
define標籤也是用來聲明變量的,可是和上面的property的不一樣點在於,define聲明的是動態變量,即這些變量的值是在程序運行起來後才能獲得的。好比配置文件中默認存在的${HOSTNAME}變量,就是經過define標籤實現的,它會在程序運行後動態的獲取當前所處容器的主機名,而且賦值給HOSTNAME變量。
一個典型的define標籤用法以下,要求define的class中填入的類必須是PropertyDefiner接口的實現。
<configuration> <define name="rootLevel" class="a.class.implementing.PropertyDefiner"> <shape>round</shape> <color>brown</color> <size>24</size> </define> <root level="${rootLevel}"/> </configuration>
logback提供了幾個基礎的Definer的實現,如FileExistsPropertyDefiner
就是用來判斷path中聲明的文件是否存在的一個definer。
include標籤容許引入另外一個路徑下存儲的logback配置,示例以下:
<configuration> <include file="src/main/java/chapters/configuration/includedConfig.xml"/> <root level="DEBUG"> <appender-ref ref="includedConsole" /> </root> </configuration>
src/main/java/chapters/configuration/includedConfig.xml
文件的內容以下:
<included> <appender name="includedConsole" class="ch.qos.logback.core.ConsoleAppender"> <encoder> <pattern>"%d - %m%n"</pattern> </encoder> </appender> </included>
要求被include進來的文件的內容必須包含在included標籤內,且語法知足logback配置文件的語法。這裏就是引入了includeConfig.xml中聲明的一個appender。