文章目錄
- 前言
- 一 Logback 說明
- 二 Logger,Appenders 和 Layouts
- 2.1 Logger 說明
- 2.1.1 有效級別
- 2.2 Appenders 說明
- 2.3 Layouts 說明
- 三 Logback 日志列印步驟
- 四 logback.xl 配置
- 4.1 自動重新加載組態檔
- 4.2 在堆疊跟蹤中啟用包資料
- 4.3 停止 logback-classic
- 4.4 組態檔語法
- 4.4.1 <logger> 元素
- 4.4.2 配置 root logger,<root\> 元素
- 4.4.3 配置 Appenders
- 4.5 設定背景關系名稱
- 4.6 變數定義和替換
- 五 Appenders
前言
在我們日常開發中,毫無疑問,我們需要花費精力去考慮如何將日志列印請求合理的嵌入到我們的程式中,有資料表明,大約有百分之四的代碼是用于日志記錄,即使是中等大小的應用程式也會嵌入上千行日志記錄陳述句,而且,日志是我們排查 bug ,除錯程式的重要依據,所以我們需要學習,并且使用工具來管理這些日志陳述句,
一 Logback 說明
Logback 目的是作為流行的 log4j 日志框架的繼承者,它是由 log4j 的創始人 Ceki Gülcü 設計的,logback 比所有現有的 logging 系統更快,并且占用的記憶體空間更小,而且,logback 提供了其他日志記錄系統所缺少的獨特且相當有用的功能,
Logback 的架構可以分為三個模塊:logback-core,logback-classic 和 logback-access,logback-core 模塊是其他2個模塊的基礎,logback-classic 擴展了 logback-core,它是對應于 log4j 的顯著改良版本,logback-access 模塊與 Servlet 容器集成在一起,以提供 HTTP 訪問日志功能,我們一般使用 logback-core 和 logback-classic,
使用 Logback-classic 時,除了引入 logback-classic.jar 外,還需要 slf4j-api.jar 和 logback-core.jar,不過如果我們使用 Maven 等構建工具,只需要引入 logback-classic 依賴,會自動引入其他2個依賴,
先簡單演示下效果,首先匯入依賴 logback-classic,
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.nobody</groupId>
<artifactId>logback-demo</artifactId>
<version>1.0-SNAPSHOT</version>
<dependencies>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.2.3</version>
</dependency>
</dependencies>
</project>
package com.nobody;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* @Description
* @Author Mr.nobody
* @Date 2021/2/20
* @Version 1.0
*/
public class Main {
public static void main(String[] args) {
// 定義一個 Logger 記錄器物件,名字為Main類的全限定名,即 com.nobody.Main
Logger logger = LoggerFactory.getLogger(Main.class);
// 在 info 級別上輸出日志 Hello Logback!
logger.info("Hello Logback!");
}
}
運行程式后,在控制臺列印出了如下日志,根據 logback 的默認配置策略,當未找到默認日志組態檔時,logback 會添加一個 ConsoleAppender(控制臺輸出器) 關聯到根 logger 記錄器中,
22:46:18.430 [main] INFO com.nobody.Main - Hello Logback!
請注意,以上示例未明顯參考任何 logback 類,是不是很奇妙,其實在大多數情況下,就日志記錄而言,需要列印日志的類中僅需要匯入 SLF4J 的類即可,通過 LoggerFactory 獲取 Logger 日志記錄器物件,因為 SLF4J 運用了門面設計模式,屏蔽了底層具體的日志實作框架,因為 logback 完整實作了SLF4J API ,所以我們可以很方便地更換成其它日志系統如 log4j 或 JDK14 Logging,而不用修改代碼,
Logback 可以通過使用內置狀態系統列印有關其內部狀態的資訊,我們可以通過一個叫 StatusManager 的組件來訪問在 logback 生命周期內發生的重要事件,我們通過呼叫 StatusPrinter 類的靜態方法 print() 來指示 logback 列印其內部狀態資訊 ,
package com.nobody;
import ch.qos.logback.classic.LoggerContext;
import ch.qos.logback.core.util.StatusPrinter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class Main {
public static void main(String[] args) {
Logger logger = LoggerFactory.getLogger(Main.class);
logger.info("Hello Logback!");
LoggerContext loggerContext = (LoggerContext) LoggerFactory.getILoggerFactory();
StatusPrinter.print(loggerContext);
}
}
運行程式,控制臺輸出如下日志,意思是說沒有找到 logback-test.xml 和 logback.xml 組態檔,所以使用默認策略配置了 ConsoleAppender,一個 Appender 可以看成是一個日志輸出目的地的類,它有很多種輸出目的地,包括控制臺,檔案,Syslog,TCP Sockets,JMS等等,當然,我們也可以根據自己的具體情況輕松創建自己的Appender,
請注意,如果出現錯誤,logback 將會自動在控制臺上列印其內部狀態資訊,
22:59:57.169 [main] INFO com.nobody.Main - Hello Logback!
22:59:57,020 |-INFO in ch.qos.logback.classic.LoggerContext[default] - Could NOT find resource [logback-test.xml]
22:59:57,021 |-INFO in ch.qos.logback.classic.LoggerContext[default] - Could NOT find resource [logback.groovy]
22:59:57,021 |-INFO in ch.qos.logback.classic.LoggerContext[default] - Could NOT find resource [logback.xml]
22:59:57,036 |-INFO in ch.qos.logback.classic.BasicConfigurator@439f5b3d - Setting up default configuration.
我們可以通過以下步驟來往我們的專案中添加日志系統:
- 通過 Maven 或 Gradle 添加日志框架依賴
- 配置日志組態檔 logback.xml
- 在需要列印日志的類中,通過 LoggerFactory 獲取 Logger 物件,然后呼叫它的 debug(),info(),warn() 和 error() 等方法,
二 Logger,Appenders 和 Layouts
Logback 依賴于三個主要類:Logger,Appender 和 Layout,配合這三種型別的組件,能讓開發人員能夠根據日志訊息型別和級別記錄日志,并在運行時控制日志格式,
Logger 類是 logback-classic 模塊的一部分,而 Appender 和 Layout 介面是 logback-core 模塊的一部分,作為通用模塊,logback-core 沒有 Logger 記錄器的概念,
2.1 Logger 說明
在 logback-classic 中,Logger 是有繼承關系的,每個單獨的 logger 都會關聯到一個 LoggerContext,LoggerContext 負責制造 logger, 并將它們按樹狀結構排列,
logger 記錄器是帶有名稱的 Logger 物件,它們的名稱區分大小寫,并且遵循層級命名規則,
Logger 名稱層次規則:
記錄器的名稱層級規則跟 “.” 有關,它們有父親或者祖先的關系,
例如命名為 com.nobody 的 logger 是命名為 com.nobody.User 的 logger 的父親;同理,java 是 java.util 的父親,但是是 java.util.List 的祖先,
root logger 位于 Logger 層次結構的頂部,我們可以按其名稱獲取到它,如下所示:
public class Main {
public static void main(String[] args) {
Logger rootLogger = LoggerFactory.getLogger(org.slf4j.Logger.ROOT_LOGGER_NAME);
rootLogger.info("rootLogger:{}", rootLogger.getName()); // 輸出 rootLogger:ROOT
}
}
我們一般用如下方式,將類的全限定名稱作為 logger 名稱,獲取一個 Logger 物件,用于類中列印日志:
// Logger logger = LoggerFactory.getLogger("com.nobody.Main"); 等價
Logger logger = LoggerFactory.getLogger(Main.class);
logger.info("Hello Logback!");
通過 LoggerFactory.getLogger 獲取相同名字的 logger 記錄器,都是回傳同一物件,例如以下回傳的三個物件都是同一個物件,
Logger logger1 = LoggerFactory.getLogger(Main.class);
Logger logger2 = LoggerFactory.getLogger("com.nobody.Main");
Logger logger3 = LoggerFactory.getLogger("com.nobody.Main");
2.1.1 有效級別
有效級別也稱為日志級別繼承規則,我們可以為 logger 分配級別,級別種類有 TRACE,DEBUG,INFO,WARN 和 ERROR,它們在 ch.qos.logback.classic.Level 中定義,在 logback 中,Level 類是 final 的,不能被繼承的,
級別按以下順序排序: TRACE < DEBUG < INFO < WARN < ERROR,
如果沒有為給定的 logger 記錄器分配一個級別,那么它將從其最接近的祖先那里繼承一個已分配的級別,嚴格上講,比如一個 logger 的有效級別等于其層次結構中的第一個非空級別,它從其本身開始,在層次結構中向上擴展直到 root logger,
為了確保所有 logger 記錄器最終都可以繼承到級別,root logger 始終具有分配的級別,默認情況下,此級別是 DEBUG,
| Logger name | Assigned level | Effective level |
|---|---|---|
| root | DEBUG | DEBUG |
| X | none | DEBUG |
| X.Y | none | DEBUG |
| X.Y.Z | none | DEBUG |
| Logger name | Assigned level | Effective level |
|---|---|---|
| root | ERROR | ERROR |
| X | INFO | INFO |
| X.Y | DEBUG | DEBUG |
| X.Y.Z | WARN | WARN |
| Logger name | Assigned level | Effective level |
|---|---|---|
| root | DEBUG | DEBUG |
| X | INFO | INFO |
| X.Y | none | INFO |
| X.Y.Z | ERROR | ERROR |
| Logger name | Assigned level | Effective level |
|---|---|---|
| root | DEBUG | DEBUG |
| X | INFO | INFO |
| X.Y | none | INFO |
| X.Y.Z | none | INFO |
列印方法決定記錄請求的級別,如果 L 是一個 logger 實體,則陳述句 L.info("…") 是一條級別為 INFO 的記錄陳述句,記錄請求的級別只有高于或等于其 logger 的有效級別時被稱為被啟用,否則,稱為被禁用,假設記錄請求級別為 p,其 logger 的有效級別為 q,只有則當 p>=q 時,該請求才會被執行,
以下演示 logger 級別的繼承關系,日志級別關系決定是否能列印:
package com.nobody;
import ch.qos.logback.classic.Level;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* @Description
* @Author Mr.nobody
* @Date 2021/2/21
* @Version 1.0
*/
public class LoggerLevelTest {
public static void main(String[] args) {
// 獲取一個名為 "com.foo" 的 logger 物件,并且轉換為 ch.qos.logback.classic.Logger logger,
// 這樣我們能為它設定級別
ch.qos.logback.classic.Logger logger =
(ch.qos.logback.classic.Logger) LoggerFactory.getLogger("com.foo");
logger.setLevel(Level.INFO);
// 獲取一個名為 "com.boo.Bar" 的 logger 物件,沒有設定級別,根據繼承關系,繼承"com.foo"的 logger 的級別 INFO
Logger barLogger = LoggerFactory.getLogger("com.foo.Bar");
// 可以執行,因為 WARN >= INFO
logger.warn("Low fuel level.");
// 不能執行,因為 DEBUG < INFO.
logger.debug("Starting search for nearest gas station.");
// 根據級別繼承關系,可以執行,因為 INFO >= INFO.
barLogger.info("Located nearest gas station.");
// 根據級別繼承關系,不能執行,因為 DEBUG < INFO.
barLogger.debug("Exiting gas station search");
}
}
運行程式,控制臺輸出的日志資訊如下:
14:37:58.354 [main] WARN com.foo - Low fuel level.
14:37:58.368 [main] INFO com.foo.Bar - Located nearest gas station.
2.2 Appenders 說明
Logback 允許日志記錄請求列印到多個目標目的地,在 logback 中,輸出目標稱為 appender,目前,存在的 appender 有,控制臺,檔案,遠程 socket 服務,MySQL,PostgreSQL,Oracle 和其他資料庫,JM S和 遠程 UNIX Syslog 守護程式等等,
一個 logger 可以與多個 appender 系結,
一個可以執行的日志列印請求,會將日志輸出到當前 logger 關聯的 appender,并且會根據層級關系輸出到所有上層級 logger 所關聯的 appender 中,我們可以通過將 logger 的可疊加性標志(additivity flag)設定為 false,覆寫此默認行為,這樣不會將日志輸出到更高層級 logger 的 appender 中,
假如有個 logger X.Y.Z,默認會將日志輸出到 X.Y.Z,X.Y,X 這三個 logger 所關聯的 appender 中,如果將 X.Y 這個 logger 的 additivity flag 設定為 false,則 X.Y.Z logger 列印的日志只會輸出到 X.Y.Z 和 X.Y,如果將 X.Y.Z logger 的 additivity flag 設定為 false,則 X.Y.Z logger 列印的日志只會輸出到 X.Y.Z ,
| Logger Name | Attached Appenders | Additivity Flag | Output Targets | Comment |
|---|---|---|---|---|
| root | A1 | 不適用 | A1 | 由于 root logger 位于層次結構的頂部,因此可加性標志不適用于它, |
| X | A2,A3 | true | A1,A2,A3 | root 和 X logger 總的 appender |
| X.Y | none | true | A1,A2,A3 | root 和 X logger 總的 appender |
| X.Y.Z | A4 | true | A1,A2,A3,A4 | root 和 X 和 X.Y.Z logger 總的 appender |
| K | B1 | false | B1 | 因為 K 的 additivity flag 設定為 false,所以 appender 只有它自己關聯的, |
| K.J | none | true | B1 | 因為 K 的 additivity flag 設定為 false,所以層級往上查找只到K,不能到 root, |
2.3 Layouts 說明
通常,我們不僅希望自定義日志輸出目的地,還希望自定義日志輸出格式,這可以通過將 layout 和 appender 相關聯來實作,layout 負責根據用戶的需求格式化日志記錄請求,而 appender 負責將格式化后的日志輸出發送到目的地,
例如,如果 patternLayout 配置為 “%-4relative [%thread] %-5level %logger{32} - %msg%n”,將輸出類似以下格式日志資訊:
176 [main] DEBUG manual.architecture.HelloWorld2 - Hello world.
第一個欄位是自程式啟動以來經過的毫秒數,第二個欄位是發出日志請求的執行緒名稱,第三個欄位是日志請求的級別,第四個欄位是與日志請求關聯的 logger 的名稱,“-” 之后的文本是日志資訊,
引數化的日志
因為 logback-classic 的 Logger 實作了 SLF4J 的 Logger 介面,所以有些日志列印方法允許使用多個引數,這些帶有多個引數的列印方法能提高性能,同時最大程度提高代碼可讀性,
例如,以下寫法,為了構造 debug 方法的 msg 引數,會將整數 i 和 entry[i] 字串都轉換為字串,并連接中間字串,從而產生開銷,不管最終此行代碼是否會列印,
logger.debug("Entry number: " + i + " is " + String.valueOf(entry[i]));
為避免引數構造成本的一種方法,是在列印日志前,判斷此 logger 是否開啟此級別,
if(logger.isDebugEnabled()) {
logger.debug("Entry number: " + i + " is " + String.valueOf(entry[i]));
}
這樣,如果我們禁用了 debug 級別,則不會產生 msg 引數構造的開銷,但是,如果 logger 開啟了 debug 級別,則將產生兩次開銷,一次是判斷 debugEnabled,一次是 debug 列印,實際上,debugEnabled 這種開銷微不足道,因為它是實際日志列印請求所花費時間的不到1%,
不過有更好的選擇,基于訊息格式的方法,例如如下所示:
Object entry = new SomeObject();
logger.debug("The entry is {}.", entry);
只有在 debug 列印陳述句是開啟的情況下,logger 才會格式化訊息,并用 entry 代替 {},也就是說,當禁用 debug 級別時,是不會產生 msg 引數構造的開銷的,
以下兩行將產生完全相同的輸出,但是,在禁用日志記錄陳述句的情況下,第二個將比第一個好至少30倍,
logger.debug("The new entry is "+entry+".");
logger.debug("The new entry is {}.", entry);
如果需要傳遞三個或更多引數,您可以這樣寫:
Object[] paramArray = {newVal, below, above};
logger.debug("Value {} was inserted between {} and {}.", paramArray);
三 Logback 日志列印步驟
當用戶呼叫 logger 的日志列印方法時,logback 框架所采取的步驟是怎么樣的呢?現在我們分析當用戶呼叫名為 “com.nobody.UserService” 的 logger 的 info() 方法時,logback 采取的步驟 :
-
獲得過濾器鏈決策
如果存在,那么 TurboFilter 鏈會被呼叫,Turbo filters 可以設定一個背景關系范圍閾值,或過濾掉某些基于資訊例如 Marker, Level,Logger,日志訊息的事件,或與每個日志記錄請求相關聯的 Throwable,如果過濾器鏈的答復是 FilterReply.DENY,那么日志記錄請求會被拋棄,如果是 FilterReply.NEUTRAL,則繼續進行下一步,即步驟2,如果答復為 FilterReply.ACCEPT,則跳過下一步,直接跳至步驟3, -
logger 級別比較規則
在此步驟中,logback 將 logger 的有效級別與列印請求級別進行比較,如果根據級別規則禁用了日志記錄請求,則 logback 將丟棄該請求,(請求列印日志的級別小于 logger 設定的級別則拋棄請求)而無需進一步處理,否則,將繼續進行下一步, -
創建一個 LoggingEvent 物件
如果請求在先前的過濾器中存活下來了,則 logback 將創建一個 ch.qos.logback.classic.LoggingEvent 物件,其中包含請求的所有相關引數,例如請求的 logger,請求級別,日志訊息,可能與請求一起傳遞的例外,當前時間,當前執行緒,跟發出日志記錄請求相關的類的各種資料以及MDC,其中某些欄位僅在實際需要時才延遲初始化,MDC用額外的背景關系資訊(例如請求唯一ID)來裝飾日志記錄請求, -
呼叫 appenders
創建 LoggingEvent 物件后,logback 將呼叫所有能用的 appenders 的 doAppend() 方法, -
格式化輸出
被呼叫的 appender 負責格式化日志記錄事件,但是,一些(但不是全部) appender 將格式化日志記錄事件的任務委托給 layout,layout 會格式化 LoggingEvent 實體,并以字串形式回傳結果,注意,某些 appender,例如 SocketAppender 不會將日志記錄事件轉換為字串,而是將其序列化,因此,它們沒有 layout,也不需要 layout, -
發出 LoggingEvent
日志記錄事件被完全格式化后,每個 appender 會將其發送到其目的地,
下面是一個顯示了全部作業原理的 UML 序列圖,

四 logback.xl 配置
通過編程,或使用以 XML 或 Groovy 格式表示的配置腳本都可以達到配置 Logback 的效果,
logback 會按以下步驟來配置自己:
-
Logback 嘗試在 classpath 中找一個名為 logback-test.xml 的檔案 ,
-
如果找不到此類檔案,則 logback 嘗試在 classpath 中找一個名為 logback.groovy 的檔案 ,
-
如果找不到這樣的檔案,它將在 classpath 中找一個名為 logback.xml 的檔案,
-
如果還沒有找到這樣的檔案, ServiceLoader(在JDK 1.6中引入)會通過 META-INF\services\ch.qos.logback.classic.spi.Configurator 加載 com.qos.logback.classic.spi.Configurator 介面的實作類,
-
如果以上方法均未成功,則 logback 將使用 BasicConfigurator 進行自動配置,這會將日志輸出定向到控制臺,
以下我們演示沒有組態檔,logback 使用默認配置的效果,
package com.nobody;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class MyApp1 {
private static final Logger LOGGER = LoggerFactory.getLogger(MyApp1.class);
public static void main(String[] args) {
LOGGER.info("我是在 MyApp1 類中,使用info級別列印日志");
User user = new User();
user.say();
}
}
package com.nobody;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class User {
private static final Logger LOGGER = LoggerFactory.getLogger(User.class);
public void say() {
LOGGER.debug("我是在 User 類中,使用debug級別列印日志");
}
}
運行程式,控制臺輸出日志如下:
21:20:34.035 [main] INFO com.nobody.MyApp1 - 我是在 MyApp1 類中,使用info級別列印日志
21:20:34.041 [main] DEBUG com.nobody.User - 我是在 User 類中,使用debug級別列印日志
如果找不到組態檔,那么 logback 默認會呼叫 BasicConfigurator ,創建一個最小化配置,最小化配置由一個關聯到根 logger 的 ConsoleAppender 組成,輸出用模式為 %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n 的 PatternLayoutEncoder 進行格式化,默認情況下,為 root logger 分配了 DEBUG 級別,
Logback 組態檔的語法非常靈活,因為靈活,所以無法用 DTD 或 XML schema 進行定義,盡管如此,可以這樣描述組態檔的基本結構:以 <configuration> 開頭,后面有零個或多個 <appender> 元素,有零個或多個 <logger> 元素,有最多一個 <root> 元素,

下面我們演示使用 logback.xml 進行配置,首先我們在類路徑中創建 logback.xml 檔案,此為 logback-demo/src/main/resources/logback.xml ,填入以下內容:
<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>
再運行上面的程式,控制臺會輸出如下資訊:
21:45:40.941 [main] INFO com.nobody.MyApp1 - 我是在 MyApp1 類中,使用info級別列印日志
21:45:40.954 [main] DEBUG com.nobody.User - 我是在 User 類中,使用debug級別列印日志
如果程式在決議組態檔期間發生警告或錯誤,則 logback 會自動在控制臺上列印其內部狀態資訊,如果在沒有警告或錯誤時,你也希望檢查 logback 的內部狀態,你可以指示通過呼叫 StatusPrinter 類的 print() 方法,如下所示:
LoggerContext lc = (LoggerContext) LoggerFactory.getILoggerFactory();
StatusPrinter.print(lc);
如果你不想在程式中撰寫列印 logback 的內部狀態,那可以在組態檔 configuration 元素的 debug 屬性設定為 true,同樣也可以在程式啟動時列印 logback 內部狀態,當然,前提是找到組態檔或者組態檔是格式正確的XML才會輸出內部狀態,
<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>
當然,我們也可以通過配置系統屬性(logback.configurationFile)的方式,指定 logback 組態檔的位置,屬性的值可以是URL,類路徑上的資源或應用程式外部檔案的路徑,
java -Dlogback.configurationFile=/path/to/logback.xml com.nobody.MyApp1
4.1 自動重新加載組態檔
如果開啟了自動重新加載組態檔,logback-classic 會掃描組態檔中的更改,并在組態檔更改時自動重新配置自身,在 <configuration> 標簽中將 scan 屬性設定為 true 即可開啟,
當將 scan 屬性設定為 true 時,在后臺 ReconfigureOnChangeTask 會在單獨的執行緒中運行,它會檢查組態檔是否已更改,
由于在編輯組態檔時很容易出錯,因此如果最新版本的組態檔具有 XML 語法錯誤,則它將回退到先前沒有 XML 語法錯誤的組態檔,
<configuration scan="true">
...
</configuration>
默認情況下,每1分鐘掃描一次組態檔是否有更改,我們可以設定 <configuration> 標簽中的 scanPeriod 屬性來指定掃描周期,單位可以為毫秒,秒,分鐘或小時,如果未指定時間單位,則時間單位默認為毫秒
<configuration debug = "true" scan="true" scanPeriod="30 seconds">
<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>
4.2 在堆疊跟蹤中啟用包資料
注意從版本1.1.4開始,包裝資料默認為禁用,可按如下配置開啟包資料:
<configuration packagingData="true">
...
</configuration>
當然,也可以在程式中進行配置,如下:
LoggerContext lc = (LoggerContext) LoggerFactory.getILoggerFactory();
lc.setPackagingDataEnabled(true);
如果開啟了,logback 會在輸出的堆疊行中顯示它是屬于哪個 jar 或者哪個類的,此資訊由 jar 檔案的名稱和版本組成,表明堆疊資訊來源于此,此機制對于識別軟體版本問題非常有用,但是,計算成本相當昂貴,尤其是在經常引發例外的應用程式中,以下演示開啟的結果,即多了 [] 括號內的資訊,
14:28:48.835 [btpool0-7] INFO c.q.l.demo.prime.PrimeAction - 99 is not a valid value
java.lang.Exception: 99 is invalid
at ch.qos.logback.demo.prime.PrimeAction.execute(PrimeAction.java:28) [classes/:na]
at org.apache.struts.action.RequestProcessor.processActionPerform(RequestProcessor.java:431) [struts-1.2.9.jar:1.2.9]
at org.apache.struts.action.RequestProcessor.process(RequestProcessor.java:236) [struts-1.2.9.jar:1.2.9]
at org.apache.struts.action.ActionServlet.doPost(ActionServlet.java:432) [struts-1.2.9.jar:1.2.9]
at javax.servlet.http.HttpServlet.service(HttpServlet.java:820) [servlet-api-2.5-6.1.12.jar:6.1.12]
at org.mortbay.jetty.servlet.ServletHolder.handle(ServletHolder.java:502) [jetty-6.1.12.jar:6.1.12]
at ch.qos.logback.demo.UserServletFilter.doFilter(UserServletFilter.java:44) [classes/:na]
at org.mortbay.jetty.servlet.ServletHandler$CachedChain.doFilter(ServletHandler.java:1115) [jetty-6.1.12.jar:6.1.12]
at org.mortbay.jetty.servlet.ServletHandler.handle(ServletHandler.java:361) [jetty-6.1.12.jar:6.1.12]
at org.mortbay.jetty.webapp.WebAppContext.handle(WebAppContext.java:417) [jetty-6.1.12.jar:6.1.12]
at org.mortbay.jetty.handler.ContextHandlerCollection.handle(ContextHandlerCollection.java:230) [jetty-6.1.12.jar:6.1.12]
4.3 停止 logback-classic
為了釋放 logback-classic 資源,停止 logback context 是一個好主意,如果停止,會關閉所有 loggers 關聯的 appenders,并有序的停止所有活動執行緒,
import org.sflf4j.LoggerFactory;
import ch.qos.logback.classic.LoggerContext;
...
LoggerContext loggerContext = (LoggerContext) LoggerFactory.getILoggerFactory();
loggerContext.stop();
4.4 組態檔語法
4.4.1 <logger> 元素
一個 logger 記錄器可以使用 <logger> 元素配置,<logger> 元素中,name 屬性是必須的,level 級別屬性是可選的,additivity 可疊加性屬性也是可以選的(它的值是 true 或 false),級別 level 屬性的值是不區分大小寫的字串 TRACE,DEBUG,INFO,WARN,ERROR,ALL,OFF,還有不區分大小寫的值 INHERITED 或其同義詞 NULL,代表將強制從層次結構中較高的層次繼承記錄器繼承級別,
<logger> 元素里面可包含0或多個 <appender-ref> 元素,這樣參考的 appender 會關聯到此 logger,不同 log4j,即便你在組態檔配置了 logger 關聯的 appender,logback-classic 也不會關倍訓者移除之前關聯的 appender,
4.4.2 配置 root logger,<root> 元素
<root>元素用來配置 root logger,它支持單個屬性,即 level 級別屬性,它沒有其他屬性,因為可疊加性標志不適用于根記錄器,此外,由于根記錄器已被命名為 “ ROOT” ,因此它也不允許使用 name 屬性,level 屬性的值可以是不區分大小寫的字串 TRACE,DEBUG,INFO,WARN,ERROR,ALL或OFF之一,但是,根記錄器的級別不能設定為 INHERITED 或 NULL,
與 <logger> 元素類似,<root> 元素也可以包含零個或多個 <appender-ref>元素,如此參考的每個附加程式都會添加到根記錄器中,不同 log4j,即便你在組態檔配置了 root logger 關聯的 appender,logback-classic 也不會關倍訓者移除之前關聯的 appender,
下面演示個 demo,假設我們不想列印 “com.nobody.entity” 包下任何組件的任何 DEBUG 訊息,可以按如下配置:
<configuration>
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<!-- encoder 默認被分配 ch.qos.logback.classic.encoder.PatternLayoutEncoder 類 -->
<!-- 當然你也可以通過 class 屬性 顯示指定,即 <encoder class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy"> -->
<encoder>
<pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
</encoder>
</appender>
<logger name="com.nobody.entity" level="INFO"/>
<!-- 其實此level屬性設定也可以去除,因為默認就是 DEBUG 級別 -->
<root level="DEBUG">
<appender-ref ref="STDOUT"/>
</root>
</configuration>
我們可以根據需要配置任意數量的記錄器,如下,我們將 com.nobody.A 記錄器的級別設定 為 INFO,但同時將 com.nobody.B 記錄器的級別設定為DEBUG,
<configuration>
...
<logger name="com.nobody.A" level="INFO"/>
<logger name="com.nobody.B" level="DEBUG"/>
...
</configuration>
4.4.3 配置 Appenders
一個 appender 使用 <appender> 元素配置,該元素具有兩個必填屬性 name 和 class,name 屬性指定 appender 的名稱,class 屬性指定實體化此 appender 的類,<appender> 元素可包含零個或一個 <layout> 元素,零個或多個 <encoder> 元素,零個或多個層 <filter> 元素,除了這三個公共元素之外,<appender> 可以包含任意數量的與 appender 類的JavaBean屬性相對應的元素,

<layout> 有個必填的屬性指定實體化此物件的全限定類名,和 <appender>一樣,它也有自己的相關屬性,PatternLayout 有默認的屬性值,所以可以不指定屬性值,
<encoder>有個必填的屬性指定實體化此物件的全限定類名,PatternLayoutEncoder 有默認的屬性值,所以可以不指定屬性值,
<configuration debug="false" scan="true" scanPeriod="30 seconds" packagingData="true">
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
<!-- <pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>-->
<pattern>%-4relative [%thread] %-5level %logger{32} - %msg%n</pattern>
</encoder>
</appender>
<appender name="FILE" class="ch.qos.logback.core.FileAppender">
<file>myApp.log</file>
<encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
<pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
</encoder>
</appender>
<root level="INFO">
<appender-ref ref="STDOUT"/>
<appender-ref ref="FILE"/>
</root>
</configuration>
上述組態檔定義了兩個命名為 FILE 和STDOUT 的 appender ,FILE appender 將日志輸出到 myApp.log 檔案,STDOUT appender 將日志輸出到控制臺,
默認情況下,附加程式是累積式的:記錄器將記錄到附加到其自身的附加程式(如果有)以及附加到其祖先的所有附加程式,因此,將同一附加程式附加到多個記錄器將導致記錄輸出重復,
默認情況下,appender 是累積式的:一個 logger 會將日志輸出到它自己關聯的所有 appender 和 它上層級(祖先)所關聯的所有 appender,所以,如果將同一個 appender 關聯到不同的 logger,有可能會導致輸出的日志會重復,例如下面這個例子:
<configuration debug="false" scan="true" scanPeriod="30 seconds" packagingData="true">
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
<!-- <pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>-->
<pattern>%-4relative [%thread] %-5level %logger{32} - %msg%n</pattern>
</encoder>
</appender>
<appender name="FILE" class="ch.qos.logback.core.FileAppender">
<file>myApp.log</file>
<encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
<pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
</encoder>
</appender>
<logger name="com.nobody.Main">
<appender-ref ref="STDOUT"/>
</logger>
<root level="INFO">
<appender-ref ref="STDOUT"/>
<appender-ref ref="FILE"/>
</root>
</configuration>
package com.nobody;
import ch.qos.logback.classic.LoggerContext;
import ch.qos.logback.core.util.StatusPrinter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class Main {
private static final Logger logger = LoggerFactory.getLogger(Main.class);
public static void main(String[] args) {
logger.info("Hello Logback!");
Logger rootLogger = LoggerFactory.getLogger(org.slf4j.Logger.ROOT_LOGGER_NAME);
rootLogger.info("rootLogger:{}", rootLogger.getName());
}
}
會在控制臺輸出如下結果,因為名為 com.nobody.Main 的 logger 的可疊加性標識默認為 true,所以會將日志輸出到它上級的 logger 關聯的 appender 中,所以輸出2遍,而 root logger 沒有上級,輸出1遍,
674 [main] INFO com.nobody.Main - Hello Logback!
674 [main] INFO com.nobody.Main - Hello Logback!
679 [main] INFO ROOT - rootLogger:ROOT
當然,你可以將 com.nobody.Main 的 logger 的可疊加性標識默認為 false,那它的日志就不會輸送到上層級中,
<logger name="com.nobody.Main" additivity="false">
<appender-ref ref="STDOUT"/>
</logger>
4.5 設定背景關系名稱
每個 logger 記錄器都附加到一個記錄器背景關系,默認情況下,它的名稱為 “default”,通過 <contextName> 配置可以更改其名稱,使用此值列印到日志中,用于區分不同應用程式的記錄,但一旦設定,它的名稱就無法變更,
<configuration>
<contextName>myAppName</contextName>
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>%d{HH:mm:ss.SSS} %contextName [%thread] %-5level %logger{36} - %msg%n</pattern>
</encoder>
</appender>
<root level="INFO">
<appender-ref ref="STDOUT" />
</root>
</configuration>
4.6 變數定義和替換
logback 組態檔支持變數的定義和替換,變數具有作用域,變數可以在組態檔中,在外部檔案中,在外部資源中,甚至可以即時計算和定義,
變數替換可以發生在組態檔中可以指定值的任何位置,語法是 ${variableName},
考慮到常用性,HOSTNAME 和 CONTEXT_NAME 變數默認已定義,并具有背景關系作用域,考慮到在某些環境中可能需要花費一些時間來計算主機名,因此它的值是延遲計算的(僅在需要時),
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
<pattern>%d{HH:mm:ss.SSS} ${HOSTNAME} ${CONTEXT_NAME} [%thread] %-5level %logger{36} - %msg%n</pattern>
</encoder>
</appender>
變數可以在 logback 自己的組態檔中定義,也可以從外部屬性檔案或外部資源中批量加載,由于歷史原因,用于定義變數用 <property>,
<configuration>
<property name="LOG_HOME" value="./logs"/>
<appender name="FILE" class="ch.qos.logback.core.FileAppender">
<file>${LOG_HOME}/myApp.log</file>
<encoder>
<pattern>%msg%n</pattern>
</encoder>
</appender>
<root level="debug">
<appender-ref ref="FILE" />
</root>
</configuration>
它下面的效果一樣,logback 將在 System 屬性中查找它,
java -LOG_HOME="./logs" MyApp
如果你定義的變數太多時,可以創建單獨的檔案來保存,方便管理,
<configuration>
<property file="src/main/java/resources/logback-variables.properties"/>
<appender name="FILE" class="ch.qos.logback.core.FileAppender">
<file>${LOG_HOME}/myApp.log</file>
<encoder>
<pattern>%msg%n</pattern>
</encoder>
</appender>
<root level="debug">
<appender-ref ref="FILE" />
</root>
</configuration>
此配置會讀取logback-variables.properties檔案中的變數,然后在本地范圍內使用,logback-variables.properties檔案定義的變數如下:
LOG_HOME=./logs
當然,也可以寫成引入類路徑上的資源檔案的形式,
<configuration>
<property resource="logback-variables.properties"/>
<appender name="FILE" class="ch.qos.logback.core.FileAppender">
<file>${LOG_HOME}/myApp.log</file>
<encoder>
<pattern>%msg%n</pattern>
</encoder>
</appender>
<root level="debug">
<appender-ref ref="FILE" />
</root>
</configuration>
變數作用域
定義的變數是有作用域的,如本地作用域,背景關系作用域,系統級作用域,默認是本地作用域,從作業系統環境中讀取變數很容易,但是無法寫入到作業系統環境中,
- Local Scope(本地作用域):從組態檔中定義的本地變數即在本地組態檔使用,每次決議和執行組態檔時,都會重新定義本地作用域中的變數,
- Context Scope(背景關系作用域):一個擁有背景關系作用域的變數存在于背景關系中,于背景關系共存,直到被清除,在所有記錄事件中都可用到,包括那些通過序列化發送到遠程主機的事件,
- System Scope(系統級作用域):系統級作用域的變數被插入到JVM的系統屬性中,生命周期和JVM一致,直到被清除,
在進行屬性替換時,查找變數的順序為:local scope,context scope,system properties,OS environment,
<configuration>
<property scope="context" name="LOG_HOME" value="./logs"/>
<appender name="FILE" class="ch.qos.logback.core.FileAppender">
<file>${LOG_HOME}/myApp.log</file>
<encoder>
<pattern>%msg%n</pattern>
</encoder>
</appender>
<root level="debug">
<appender-ref ref="FILE" />
</root>
</configuration>
引入變數時,可能變數未定義或者值為null,我們可以使用“:-”符號指定默認值,例如${LOG_HOME:-./logs},
支持變數嵌套,默認值和值定義都可以參考其他變數,例如:
LOG_HOME=./logs
FILE_NAME=myApp.log
destination=${LOG_HOME}/${FILE_NAME}
名稱嵌套
參考變數時,變數名稱可能包含對另一個變數的參考,例如,如果為名為“ userid”的變數分配了值“ alice”,則“ $ {$ {userid} .password}”參考名稱為“ alice.password”的變數,
默認值嵌套
變數的默認值可以參考另一個變數,例如,假設未分配變數“ id”,并且為變數“ userid”分配了值“ alice”,則運算式“ $ {id :- $ {userid}}”將回傳“ alice”,
有條件地處理組態檔
我們可能需要在不同的環境(例如dev,test,prod)切換不同的logback組態檔,然而這些組態檔大部分內容是一樣的,極少內容是不同的,為了減少多個組態檔,可以使用條件處理標簽,<if>, <then> 和 <else>,根據不同環境進行配置,不過,需要引入Janino 庫,
<!-- if-then 形式 -->
<if condition="運算式">
<then>
...
</then>
</if>
<!-- if-then-else 形式 -->
<if condition="運算式">
<then>
...
</then>
<else>
...
</else>
</if>
condition 條件只能是背景關系屬性或系統屬性的Java運算式,對于通過引數傳遞的鍵,可以通過 property() 或簡寫的 p() 方法回傳屬性的字串值,例如,property(“k”) 或 p(“k”) 訪問鍵“ k”的值,如果鍵“ k”的屬性未定義,則屬性方法將回傳空字串,而不是null,這能避免判斷null值,
isDefined()方法可用于檢查是否定義了屬性,例如,isDefined(“k”) ,如果需要檢查屬性是否為null,則可以使用 isNull() 方法,例如,isNull(“k”),
<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>
在<configuration>范圍內都可以使用條件處理陳述句,還支持嵌套的if-then-else陳述句,但是,XML語法非常繁瑣,為以后后續其他開發者以及自己能快速理解,盡量少用,
檔案包含
Joran支持將組態檔的一部分包含在另一個檔案中,這是通過宣告一個 元素來完成的,如下所示:
可以通過標簽<include>來引入另一個組態檔,
<configuration>
<include file="src/main/java/resources/includedConfig.xml"/>
<root level="DEBUG">
<appender-ref ref="includedConsole" />
</root>
</configuration>
includedConfig.xml檔案定義了被參考的內容:
<included>
<appender name="includedConsole" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>"%d - %m%n"</pattern>
</encoder>
</appender>
</included>
<include>標簽引入的檔案可以為一個檔案,一個類路徑上的資源,或者一個URL,如下:
<include file="src/main/java/resources/includedConfig.xml"/>
<include resource="includedConfig.xml"/>
<include url="http://xxx.com/includedConfig.xml"/>
如果被參考的檔案不存在,logback會列印內部的狀態資訊,如果包含的檔案是可選的,可以通過optional屬性設定為true來進制列印顯示警告資訊,
<include optional="true" ..../>
五 Appenders
Logback 將寫日志記錄事件的任務委派給appender組件,Appenders必須實作ch.qos.logback.core.Appender 介面,此介面主要方法如下:
package ch.qos.logback.core;
import ch.qos.logback.core.spi.ContextAware;
import ch.qos.logback.core.spi.FilterAttachable;
import ch.qos.logback.core.spi.LifeCycle;
public interface Appender<E> extends LifeCycle, ContextAware, FilterAttachable<E> {
/**
* Get the name of this appender. The name uniquely identifies the appender.
*/
String getName();
/**
* This is where an appender accomplishes its work. Note that the argument
* is of type Object.
* @param event
*/
void doAppend(E event) throws LogbackException;
/**
* Set the name of this appender. The name is used by other components to
* identify this appender.
*
*/
void setName(String name);
}
Appender介面中的大多數方法是setter和getter,不過有個例外是doAppend(E event)方法,E的實際型別取決于logback模塊,在logback-classic模塊中,E的型別為ILoggingEvent;在logback-access模塊??中,型別的E為AccessEvent,doAppend()方法是logback框架中比較重要的方法,它負責以合適的格式將日志記錄事件輸出到合適的輸出設備中,
Appender介面擴展了FilterAttachable介面,所以可以將一個或多個過濾器關聯到appender實體,
Appender負責輸出日志記錄事件,但是,他們可以將事件的實際格式委托給Layout或Encoder物件處理,每個layout和encoder都只能與有且一個appender相關聯,某些appender具有內置或固定的事件格式,因此,它們不需要Layout或Encoder,
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/276330.html
標籤:java
