簡單地說,Logback 是一個 Java 領域的日誌框架。它被認爲是 Log4J 的繼承人。
Logback 主要由三個模塊組成:java
logback-core 是其它模塊的基礎設施,其它模塊基於它構建,顯然,logback-core 提供了一些關鍵的通用機制。logback-classic 的地位和做用等同於 Log4J,它也被認爲是 Log4J 的一個改進版,而且它實現了簡單日誌門面 SLF4J;而 logback-access 主要做爲一個與 Servlet 容器交互的模塊,好比說 tomcat 或者 jetty,提供一些與 HTTP 訪問相關的功能。node
目前 Logback 的使用很普遍,不少知名的開源軟件都使用了 Logback做爲日誌框架,好比說 Akka,Apache Camel 等。express
實際上,這兩個日誌框架都出自同一個開發者之手,Logback 相對於 Log4J 有更多的優勢編程
想在 Java 程序中使用 Logback,須要依賴三個 jar 包,分別是 slf4j-api,logback-core,logback-classic。其中 slf4j-api 並非 Logback 的一部分,是另一個項目,可是強烈建議將 slf4j 與 Logback 結合使用。要引用這些 jar 包,在 maven 項目中引入如下3個 dependenciesapi
<dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-api</artifactId> <version>1.7.5</version> </dependency> <dependency> <groupId>ch.qos.logback</groupId> <artifactId>logback-core</artifactId> <version>1.0.11</version> </dependency> <dependency> <groupId>ch.qos.logback</groupId> <artifactId>logback-classic</artifactId> <version>1.0.11</version> </dependency>
package io.beansoft.logback.demo.universal; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * * * @author beanlam * @date 2017年2月9日 下午11:17:53 * @version 1.0 * */ public class SimpleDemo { private static final Logger logger = LoggerFactory.getLogger(SimpleDemo.class); public static void main(String[] args) { logger.info("Hello, this is a line of log message logged by Logback"); } }
以上代碼的運行結果是:tomcat
23:19:41.131 [main] INFO i.b.l.demo.universal.SimpleDemo - Hello, this is a line of log message logged by Logback
注意到這裏,代碼裏並無引用任何一個跟 Logback 相關的類,而是引用了 SLF4J 相關的類,這邊是使用 SLF4J 的好處,在須要將日誌框架切換爲其它日誌框架時,無需改動已有的代碼。微信
LoggerFactory
的 getLogger()
方法接收一個參數,以這個參數決定 logger 的名字,這裏傳入了 SimpleDemo
這個類的 Class 實例,那麼 logger 的名字即是 SimpleDemo
這個類的全限定類名:io.beansoft.logback.demo.universal.SimpleDemo
網絡
package io.beansoft.logback.demo.universal; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import ch.qos.logback.classic.LoggerContext; import ch.qos.logback.core.util.StatusPrinter; /** * * * @author beanlam * @date 2017年2月9日 下午11:31:55 * @version 1.0 * */ public class LogInternalStateDemo { private static final Logger logger = LoggerFactory.getLogger(LogInternalStateDemo.class); public static void main(String[] args) { logger.info("Hello world"); //打印 Logback 內部狀態 LoggerContext lc = (LoggerContext) LoggerFactory.getILoggerFactory(); StatusPrinter.print(lc); } }
除了打印正常的日誌信息,還打印出了 Logback 自身的內部狀態信息app
23:33:19.340 [main] INFO i.b.l.d.u.LogInternalStateDemo - Hello world 23:33:19,265 |-INFO in ch.qos.logback.classic.LoggerContext[default] - Could NOT find resource [logback.groovy] 23:33:19,265 |-INFO in ch.qos.logback.classic.LoggerContext[default] - Could NOT find resource [logback-test.xml] 23:33:19,265 |-INFO in ch.qos.logback.classic.LoggerContext[default] - Could NOT find resource [logback.xml] 23:33:19,266 |-INFO in ch.qos.logback.classic.LoggerContext[default] - Setting up default configuration.
在 logback 裏,最重要的三個類分別是框架
Logger 類位於 logback-classic 模塊中, 而 Appender 和 Layout 位於 logback-core 中,這意味着, Appender 和 Layout 並不關心 Logger 的存在,不依賴於 Logger,同時也能看出, Logger 會依賴於 Appender 和 Layout 的協助,日誌信息才能被正常打印出來。
爲了能夠控制哪些信息須要輸出,哪些信息不須要輸出,logback 中引進了一個 分層 概念。每一個 logger 都有一個 name,這個 name 的格式與 Java 語言中的包名格式相同。這也是前面的例子中直接把一個 class 對象傳進 LoggerFactory.getLogger() 方法做爲參數的緣由。
logger 的 name 格式決定了多個 logger 可以組成一個樹狀的結構,爲了維護這個分層的樹狀結構,每一個 logger 都被綁定到一個 logger 上下文中,這個上下文負責釐清各個 logger 之間的關係。
例如, 命名爲 io.beansoft
的 logger,是命名爲 io.beansoft.logback
的 logger 的父親,是命名爲 io.beansoft.logback.demo
的 logger 的祖先。
在 logger 上下文中,有一個 root logger,做爲全部 logger 的祖先,這是 logback 內部維護的一個 logger,並不是開發者自定義的 logger。
可經過如下方式得到這個 logger :
Logger rootLogger = LoggerFactory.getLogger(org.slf4j.Logger.ROOT_LOGGER_NAME);
一樣,經過 logger 的 name,就能得到對應的其它 logger 實例。
Logger
這個接口主要定義的方法有:
package org.slf4j; public interface Logger { // Printing methods: public void trace(String message); public void debug(String message); public void info(String message); public void warn(String message); public void error(String message); }
logger 有日誌打印級別,能夠爲一個 logger 指定它的日誌打印級別。
若是不爲一個 logger 指定打印級別,那麼它將繼承離他最近的一個有指定打印級別的祖先的打印級別。這裏有一個容易混淆想不清楚的地方,若是 logger 先找它的父親,而它的父親沒有指定打印級別,那麼它會當即忽略它的父親,往上繼續尋找它爺爺,直到它找到 root logger。所以,也能看出來,要使用 logback, 必須爲 root logger 指定日誌打印級別。
日誌打印級別從低級到高級排序的順序是:TRACE < DEBUG < INFO < WARN < ERROR
若是一個 logger 容許打印一條具備某個日誌級別的信息,那麼它也必須容許打印具備比這個日誌級別更高級別的信息,而不容許打印具備比這個日誌級別更低級別的信息。
舉個例子:
package io.beansoft.logback.demo.universal; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import ch.qos.logback.classic.Level; /** * * * @author beanlam * @date 2017年2月10日 上午12:20:33 * @version 1.0 * */ public class LogLevelDemo { public static void main(String[] args) { //這裏強制類型轉換時爲了能設置 logger 的 Level ch.qos.logback.classic.Logger logger = (ch.qos.logback.classic.Logger) LoggerFactory.getLogger("com.foo"); logger.setLevel(Level.INFO); Logger barlogger = LoggerFactory.getLogger("com.foo.Bar"); // 這個語句能打印,由於 WARN > INFO logger.warn("can be printed because WARN > INFO"); // 這個語句不能打印,由於 DEBUG < INFO. logger.debug("can not be printed because DEBUG < INFO"); // barlogger 是 logger 的一個子 logger // 它繼承了 logger 的級別 INFO // 如下語句能打印,由於 INFO >= INFO barlogger.info("can be printed because INFO >= INFO"); // 如下語句不能打印,由於 DEBUG < INFO barlogger.debug("can not be printed because DEBUG < INFO"); } }
打印結果是:
00:27:19.251 [main] WARN com.foo - can be printed because WARN > INFO 00:27:19.255 [main] INFO com.foo.Bar - can be printed because INFO >= INFO
在 logback 中,每一個 logger 都是一個單例,調用 LoggerFactory.getLogger
方法時,若是傳入的 logger name 相同,獲取到的 logger 都是同一個實例。
在爲 logger 命名時,用類的全限定類名做爲 logger name 是最好的策略,這樣可以追蹤到每一條日誌消息的來源。
在 logback 的世界中,日誌信息不只僅能夠打印至 console,也能夠打印至文件,甚至輸出到網絡流中,日誌打印的目的地由 Appender 來決定,不一樣的 Appender 能將日誌信息打印到不一樣的目的地去。
Appender 是綁定在 logger 上的,同時,一個 logger 能夠綁定多個 Appender,意味着一條信息能夠同時打印到不一樣的目的地去。例如,常見的作法是,日誌信息既輸出到控制檯,同時也記錄到日誌文件中,這就須要爲 logger 綁定兩個不一樣的 logger。
Appender 是綁定在 logger 上的,而 logger 又有繼承關係,所以一個 logger 打印信息時的目的地 Appender 須要參考它的父親和祖先。在 logback 中,默認狀況下,若是一個 logger 打印一條信息,那麼這條信息首先會打印至它本身的 Appender,而後打印至它的父親和父親以上的祖先的 Appender,但若是它的父親設置了 additivity = false
,那麼這個 logger 除了打印至它本身的 Appender 外,只會打印至其父親的 Appender,由於它的父親的 additivity
屬性置爲了 false,開始變得忘祖忘宗了,因此這個 logger 只認它父親的 Appender;此外,對於這個 logger 的父親來講,若是父親的 logger 打印一條信息,那麼它只會打印至本身的 Appender中(若是有的話),由於父親已經忘記了爺爺及爺爺以上的那些父輩了。
打印的日誌除了有打印的目的地外,還有日誌信息的展現格式。在 logback 中,用 Layout 來表明日誌打印格式。好比說,PatternLayout 可以識別如下這條格式:%-4relative [%thread] %-5level %logger{32} - %msg%n
而後打印出來的格式效果是:176 [main] DEBUG manual.architecture.HelloWorld2 - Hello world.
上面這個格式的第一個字段表明從程序啓動開始後通過的毫秒數,第二個字段表明打印出這條日誌的線程名字,第三個字段表明日誌信息的日誌打印級別,第四個字段表明 logger name,第五個字段是日誌信息,第六個字段僅僅是表明一個換行符。
常常能看到打印日誌的時候,使用如下這種方式打印日誌:
logger.debug("the message is " + msg + " from " + somebody);
這種打印日誌的方式有個缺點,就是不管日誌級別是什麼,程序總要先執行 "the message is " + msg + " from " + somebody
這段字符串的拼接操做。當 logger 設置的日誌級別爲比 DEBUG 級別更高級別時,DEBUG 級別的信息不回被打印出來的,顯然,字符串拼接的操做是沒必要要的,當要拼接的字符串很大時,這無疑會帶來很大的性能白白損耗。
因而,一種改進的打印日誌方式被人們發現了:
if(logger.isDebugEnabled()) { logger.debug("the message is " + msg + " from " + somebody); }
這樣的方式確實能避免字符串拼接的沒必要要損耗,但這也不是最好的方法,當日志級別爲 DEBUG 時,那麼打印這行消息,須要判斷兩第二天志級別。一次是logger.isDebugEnabled()
,另外一次是 logger.debug()
方法內部也會作的判斷。這樣也會帶來一點點效率問題,若是能找到更好的方法,誰願意無視白白消耗的效率。
有一種更好的方法,那就是提供佔位符的方式,以參數化的方式打印日誌,例如上述的語句,能夠是這樣的寫法:
logger.debug("the message {} is from {}", msg, somebody);
這樣的方式,避免了字符串拼接,也避免了多一第二天志級別的判斷。
當應用程序發起一個記錄日誌的請求,例如 info() 時,logback 的內部運行流程以下所示
LoggingEvent
對象LoggingEvent
到對應的目的地關於日誌系統,人們討論得最多的是性能問題,即便是小型的應用程序,也有可能輸出大量的日誌。打印日誌中的不當處理,會引起各類性能問題,例如太多的日誌記錄請求可能使磁盤 IO 成爲性能瓶頸,從而影響到應用程序的正常運行。在合適的時候記錄日誌、以更好的方式發起日誌請求、以及合理設置日誌級別方面,都有可能形成性能問題。
關於性能問題,如下幾個方面須要瞭解
logback 提供的配置方式有如下幾種:
logback 在啓動時,根據如下步驟尋找配置文件:
Configuration
接口,使用它的實現來進行配置BasicConfigurator
來配置,並將日誌輸出到 consolelogback-test.xml 通常用來在測試代碼中打日誌,若是是 maven 項目,通常把 logback-test.xml 放在 src/test/resources 目錄下。maven 打包的時候也不會把這個文件打進 jar 包裏。
logback 啓動的時候解析配置文件大概須要 100 毫秒的時間,若是但願更快啓動,能夠採用 SPI 的方式。
前面有提到默認的配置,由 BasicConfiguator
類配置而成,這個類的配置能夠用以下的配置文件來表示:
<configuration> <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender"> <!-- encoders are assigned the type ch.qos.logback.classic.encoder.PatternLayoutEncoder by default --> <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>
若是 logback 在啓動時,解析配置文件時,出現了須要警告的信息或者錯誤信息,那 logback 會自動先打印出自身的狀態信息。
若是但願正常狀況下也打印出狀態信息,則可使用以前提到的方式,在代碼裏顯式地調用使其輸出:
public static void main(String[] args) { // assume SLF4J is bound to logback in the current environment LoggerContext lc = (LoggerContext) LoggerFactory.getILoggerFactory(); // print logback's internal status StatusPrinter.print(lc); ... }
也能夠在配置文件中,指定 configuration 的 debug 屬性爲 true
<configuration debug="true"> <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender"> <!-- encoders are assigned the type ch.qos.logback.classic.encoder.PatternLayoutEncoder by default --> <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>
還能夠指定一個 Listener:
<configuration> <statusListener class="ch.qos.logback.core.status.OnConsoleStatusListener" /> ... the rest of the configuration file </configuration>
設置 logback.configurationFile 系統變量,能夠經過 -D 參數設置,所指定的文件名必須以 .xml 或者 .groovy 做爲文件後綴,不然 logback 會忽略這些文件。
要使配置文件自動重載,須要把 scan 屬性設置爲 true,默認狀況下每分鐘纔會掃描一次,能夠指定掃描間隔:
<configuration scan="true" scanPeriod="30 seconds" > ... </configuration>
注意掃描間隔要加上單位,可用的單位是 milliseconds,seconds,minutes 和 hours。若是隻指定了數字,但沒有指定單位,這默認單位爲 milliseconds。
在 logback 內部,當設置 scan 屬性爲 true 後,一個叫作 ReconfigureOnChangeFilter
的過濾器就會被牽扯進來,它負責判斷是否到了該掃描的時候,以及是否該從新加載配置。Logger 的任何一個打印日誌的方法被調用時,都會觸發這個過濾器,因此關於這個過濾器的自身的性能問題,變得十分重要。logback 目前採用這樣一種機制,當 logger 的調用次數到達必定次數後,才真正讓過濾器去作它要作的事情,這個次數默認是 16,而 logback 會在運行時根據調用的頻繁度來動態調整這個數目。
這個屬性默認是關閉,可經過如下方式開啓:
<configuration packagingData="true"> ... </configuration>
也能夠經過 LoggerContext 的 setPackagingDataEnabled(boolean) 方法來開啓
LoggerContext lc = (LoggerContext) LoggerFactory.getILoggerFactory(); lc.setPackagingDataEnabled(true);
Joran 是 logback 使用的一個配置加載庫,若是想要從新實現 logback 的配置機制,能夠直接調用這個類 JoranConfigurator
來實現:
package chapters.configuration; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import ch.qos.logback.classic.LoggerContext; import ch.qos.logback.classic.joran.JoranConfigurator; import ch.qos.logback.core.joran.spi.JoranException; import ch.qos.logback.core.util.StatusPrinter; public class MyApp3 { final static Logger logger = LoggerFactory.getLogger(MyApp3.class); public static void main(String[] args) { // assume SLF4J is bound to logback in the current environment LoggerContext context = (LoggerContext) LoggerFactory.getILoggerFactory(); try { JoranConfigurator configurator = new JoranConfigurator(); configurator.setContext(context); // Call context.reset() to clear any previous configuration, e.g. default // configuration. For multi-step configuration, omit calling context.reset(). context.reset(); configurator.doConfigure(args[0]); } catch (JoranException je) { // StatusPrinter will handle this } StatusPrinter.printInCaseOfErrorsOrWarnings(context); logger.info("Entering application."); Foo foo = new Foo(); foo.doIt(); logger.info("Exiting application."); } }
根節點是 configuration,可包含0個或多個 appender,0個或多個 logger,最多一個 root。
在配置文件中,logger 的配置在<logger> 標籤中配置,<logger> 標籤只有一個屬性是必定要的,那就是 name,除了 name 屬性,還有 level 屬性,additivity 屬性能夠配置,不過它們是可選的。
level 的取值能夠是 TRACE, DEBUG, INFO, WARN, ERROR, ALL, OFF, INHERITED, NULL
, 其中 INHERITED
和 NULL
的做用是同樣的,並非不打印任何日誌,而是強制這個 logger 必須從其父輩繼承一個日誌級別。
additivity 的取值是一個布爾值,true 或者 false。
<logger> 標籤下只有一種元素,那就是 <appender-ref>,能夠有0個或多個,意味着綁定到這個 logger 上的 Appender。
<root> 標籤和 <logger> 標籤的配置相似,只不過 <root> 標籤只容許一個屬性,那就是 level 屬性,而且它的取值範圍只能取 TRACE, DEBUG, INFO, WARN, ERROR, ALL, OFF
。
<root> 標籤下容許有0個或者多個 <appender-ref>。
<appender> 標籤有兩個必須填的屬性,分別是 name 和 class,class 用來指定具體的實現類。<appender> 標籤下能夠包含至多一個 <layout>,0個或多個 <encoder>,0個或多個 <filter>,除了這些標籤外,<appender> 下能夠包含一些相似於 JavaBean 的配置標籤。
<layout> 包含了一個必須填寫的屬性 class,用來指定具體的實現類,不過,若是該實現類的類型是 PatternLayout
時,那麼能夠不用填寫。<layout> 也和 <appender> 同樣,能夠包含相似於 JavaBean 的配置標籤。
<encoder> 標籤包含一個必須填寫的屬性 class,用來指定具體的實現類,若是該類的類型是 PatternLayoutEncoder
,那麼 class 屬性能夠不填。
若是想要往一個 logger 上綁定 appender,則使用如下方式:
<logger name="HELLO" level="debug"> <appender-ref ref="FILE" /> <appender-ref ref="STDOUT" /> </logger>
<configuration> <contextName>myAppName</contextName> <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender"> <encoder> <pattern>%d %contextName [%t] %level %logger{36} - %msg%n</pattern> </encoder> </appender> <root level="debug"> <appender-ref ref="STDOUT" /> </root> </configuration>
在 logback 中,支持以 ${varName} 來引用變量
能夠直接在 logback.xml 中定義變量
<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>
也能夠經過大D參數來定義
java -DUSER_HOME="/home/sebastien" MyApp2
也能夠經過外部文件來定義
<configuration> <property file="src/main/java/chapters/configuration/variables1.properties" /> <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>
外部文件也支持 classpath 中的文件
<configuration> <property resource="resource1.properties" /> <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>
外部文件的格式是 key-value 型。
USER_HOME=/home/sebastien
變量有三個做用域
local 做用域在配置文件內有效,context 做用域的有效範圍延伸至 logger context,system 做用域的範圍最廣,整個 JVM 內都有效。
logback 在替換變量時,首先搜索 local 變量,而後搜索 context,而後搜索 system。
如何爲變量指定 scope ?
<configuration> <property scope="context" name="nodeId" value="firstNode" /> <appender name="FILE" class="ch.qos.logback.core.FileAppender"> <file>/opt/${nodeId}/myApp.log</file> <encoder> <pattern>%msg%n</pattern> </encoder> </appender> <root level="debug"> <appender-ref ref="FILE" /> </root> </configuration>
在引用一個變量時,若是該變量未定義,那麼能夠爲其指定默認值,作法是:
${aName:-golden}
須要使用 <define> 標籤,指定接口 PropertyDfiner
對應的實現類。以下所示:
<configuration> <define name="rootLevel" class="a.class.implementing.PropertyDefiner"> <shape>round</shape> <color>brown</color> <size>24</size> </define> <root level="${rootLevel}"/> </configuration>
logback 容許在配置文件中定義條件語句,以決定配置的不一樣行爲,具體語法格式以下:
<!-- if-then form --> <if condition="some conditional expression"> <then> ... </then> </if> <!-- if-then-else form --> <if condition="some conditional expression"> <then> ... </then> <else> ... </else> </if>
示例:
<configuration debug="true"> <if condition='property("HOSTNAME").contains("torino")'> <then> <appender name="CON" class="ch.qos.logback.core.ConsoleAppender"> <encoder> <pattern>%d %-5level %logger{35} - %msg %n</pattern> </encoder> </appender> <root> <appender-ref ref="CON" /> </root> </then> </if> <appender name="FILE" class="ch.qos.logback.core.FileAppender"> <file>${randomOutputDir}/conditional.log</file> <encoder> <pattern>%d %-5level %logger{35} - %msg %n</pattern> </encoder> </appender> <root level="ERROR"> <appender-ref ref="FILE" /> </root> </configuration>
使用 <insertFromJNDI> 能夠從 JNDI 加載變量,以下所示:
<configuration> <insertFromJNDI env-entry-name="java:comp/env/appName" as="appName" /> <contextName>${appName}</contextName> <appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender"> <encoder> <pattern>%d ${CONTEXT_NAME} %level %msg %logger{50}%n</pattern> </encoder> </appender> <root level="DEBUG"> <appender-ref ref="CONSOLE" /> </root> </configuration>
可使用 ≶include> 標籤在一個配置文件中包含另一個配置文件,以下圖所示:
<configuration> <include file="src/main/java/chapters/configuration/includedConfig.xml"/> <root level="DEBUG"> <appender-ref ref="includedConsole" /> </root> </configuration>
被包含的文件必須有如下格式:
<included> <appender name="includedConsole" class="ch.qos.logback.core.ConsoleAppender"> <encoder> <pattern>"%d - %m%n"</pattern> </encoder> </appender> </included>
支持從多種源頭包含
從文件中包含
<include file="src/main/java/chapters/configuration/includedConfig.xml"/>
從 classpath 中包含
<include resource="includedConfig.xml"/>
從 URL 中包含
<include url="http://some.host.com/includedConfig.xml"/>
若是包含不成功,那麼 logback 會打印出一條警告信息,若是不但願 logback 抱怨,只需這樣作:
<include optional="true" ..../>
LoggerContextListener
接口的實例能監聽 logger context 上發生的事件,好比說日誌級別的變化,添加的方式以下所示:
<configuration debug="true"> <contextListener class="ch.qos.logback.classic.jul.LevelChangePropagator"/> .... </configuration>