最全最詳細Logback高級講解
點擊上方藍色字體,選擇“標星公眾號”
優(yōu)質文章,第一時間送達
前言
在我們日常開發(fā)中,毫無疑問,我們需要花費精力去考慮如何將日志打印請求合理的嵌入到我們的程序中。有數(shù)據(jù)表明,大約有百分之四的代碼是用于日志記錄。即使是中等大小的應用程序也會嵌入上千行日志記錄語句。而且,日志是我們排查 bug ,調試程序的重要依據(jù),所以我們需要學習,并且使用工具來管理這些日志語句。
一 Logback 說明
Logback 目的是作為流行的 log4j 日志框架的繼承者。它是由 log4j 的創(chuàng)始人 Ceki Gülcü 設計的。logback 比所有現(xiàn)有的 logging 系統(tǒng)更快,并且占用的內存空間更小。而且,logback 提供了其他日志記錄系統(tǒng)所缺少的獨特且相當有用的功能。
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!");
}
}
運行程序后,在控制臺打印出了如下日志。根據(jù) logback 的默認配置策略,當未找到默認日志配置文件時,logback 會添加一個 ConsoleAppender(控制臺輸出器) 關聯(lián)到根 logger 記錄器中。
22:46:18.430 [main] INFO com.nobody.Main - Hello Logback!
請注意,以上示例未明顯引用任何 logback 類,是不是很奇妙。其實在大多數(shù)情況下,就日志記錄而言,需要打印日志的類中僅需要導入 SLF4J 的類即可。通過 LoggerFactory 獲取 Logger 日志記錄器對象。因為 SLF4J 運用了門面設計模式,屏蔽了底層具體的日志實現(xiàn)框架。因為 logback 完整實現(xiàn)了SLF4J API ,所以我們可以很方便地更換成其它日志系統(tǒng)如 log4j 或 JDK14 Logging,而不用修改代碼。
Logback 可以通過使用內置狀態(tài)系統(tǒng)打印有關其內部狀態(tài)的信息。我們可以通過一個叫 StatusManager 的組件來訪問在 logback 生命周期內發(fā)生的重要事件。我們通過調用 StatusPrinter 類的靜態(tài)方法 print() 來指示 logback 打印其內部狀態(tài)信息 。
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等等。當然,我們也可以根據(jù)自己的具體情況輕松創(chuàng)建自己的Appender。
請注意,如果出現(xiàn)錯誤,logback 將會自動在控制臺上打印其內部狀態(tài)信息。
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.
我們可以通過以下步驟來往我們的項目中添加日志系統(tǒng):
(1)通過 Maven 或 Gradle 添加日志框架依賴
(2)配置日志配置文件 logback.xml
(3)在需要打印日志的類中,通過 LoggerFactory 獲取 Logger 對象,然后調用它的 debug(),info(),warn() 和 error() 等方法。
二 Logger,Appenders 和 Layouts
Logback 依賴于三個主要類:Logger,Appender 和 Layout。配合這三種類型的組件,能讓開發(fā)人員能夠根據(jù)日志消息類型和級別記錄日志,并在運行時控制日志格式。
Logger 類是 logback-classic 模塊的一部分。而 Appender 和 Layout 接口是 logback-core 模塊的一部分。作為通用模塊,logback-core 沒有 Logger 記錄器的概念。
2.1 Logger 說明
在 logback-classic 中,Logger 是有繼承關系的。每個單獨的 logger 都會關聯(lián)到一個 LoggerContext,LoggerContext 負責制造 logger, 并將它們按樹狀結構排列。
logger 記錄器是帶有名稱的 Logger 對象,它們的名稱區(qū)分大小寫,并且遵循層級命名規(guī)則。
Logger 名稱層次規(guī)則:
記錄器的名稱層級規(guī)則跟 “.” 有關,它們有父親或者祖先的關系。
例如命名為 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 有效級別
有效級別也稱為日志級別繼承規(guī)則。我們可以為 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。

打印方法決定記錄請求的級別。如果 L 是一個 logger 實例,則語句 L.info("…") 是一條級別為 INFO 的記錄語句。記錄請求的級別只有高于或等于其 logger 的有效級別時被稱為被啟用,否則,稱為被禁用。假設記錄請求級別為 p,其 logger 的有效級別為 q,只有則當 p>=q 時,該請求才會被執(zhí)行。
以下演示 logger 級別的繼承關系,日志級別關系決定是否能打?。?/span>
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 對象,沒有設置級別,根據(jù)繼承關系,繼承"com.foo"的 logger 的級別 INFO
Logger barLogger = LoggerFactory.getLogger("com.foo.Bar");
// 可以執(zhí)行,因為 WARN >= INFO
logger.warn("Low fuel level.");
// 不能執(zhí)行,因為 DEBUG < INFO.
logger.debug("Starting search for nearest gas station.");
// 根據(jù)級別繼承關系,可以執(zhí)行,因為 INFO >= INFO.
barLogger.info("Located nearest gas station.");
// 根據(jù)級別繼承關系,不能執(zhí)行,因為 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 和其他數(shù)據(jù)庫,JM S和 遠程 UNIX Syslog 守護程序等等。
一個 logger 可以與多個 appender 綁定。
一個可以執(zhí)行的日志打印請求,會將日志輸出到當前 logger 關聯(lián)的 appender,并且會根據(jù)層級關系輸出到所有上層級 logger 所關聯(lián)的 appender 中。我們可以通過將 logger 的可疊加性標志(additivity flag)設置為 false,覆蓋此默認行為,這樣不會將日志輸出到更高層級 logger 的 appender 中。
假如有個 logger X.Y.Z,默認會將日志輸出到 X.Y.Z,X.Y,X 這三個 logger 所關聯(lián)的 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 。

2.3 Layouts 說明
通常,我們不僅希望自定義日志輸出目的地,還希望自定義日志輸出格式。這可以通過將 layout 和 appender 相關聯(lián)來實現(xiàn)。layout 負責根據(jù)用戶的需求格式化日志記錄請求,而 appender 負責將格式化后的日志輸出發(fā)送到目的地。
例如,如果 patternLayout 配置為 “%-4relative [%thread] %-5level %logger{32} - %msg%n”,將輸出類似以下格式日志信息:
176 [main] DEBUG manual.architecture.HelloWorld2 - Hello world.
第一個字段是自程序啟動以來經過的毫秒數(shù)。第二個字段是發(fā)出日志請求的線程名稱。第三個字段是日志請求的級別。第四個字段是與日志請求關聯(lián)的 logger 的名稱?!?” 之后的文本是日志信息。
參數(shù)化的日志
因為 logback-classic 的 Logger 實現(xiàn)了 SLF4J 的 Logger 接口,所以有些日志打印方法允許使用多個參數(shù)。這些帶有多個參數(shù)的打印方法能提高性能,同時最大程度提高代碼可讀性。
例如,以下寫法,為了構造 debug 方法的 msg 參數(shù),會將整數(shù) i 和 entry[i] 字符串都轉換為字符串,并連接中間字符串,從而產生開銷。不管最終此行代碼是否會打印。
logger.debug("Entry number: " + i + " is " + String.valueOf(entry[i]));
為避免參數(shù)構造成本的一種方法,是在打印日志前,判斷此 logger 是否開啟此級別。
if(logger.isDebugEnabled()) {
logger.debug("Entry number: " + i + " is " + String.valueOf(entry[i]));
}
這樣,如果我們禁用了 debug 級別,則不會產生 msg 參數(shù)構造的開銷。但是,如果 logger 開啟了 debug 級別,則將產生兩次開銷,一次是判斷 debugEnabled,一次是 debug 打印。實際上,debugEnabled 這種開銷微不足道,因為它是實際日志打印請求所花費時間的不到1%。
不過有更好的選擇,基于消息格式的方法,例如如下所示:
Object entry = new SomeObject();
logger.debug("The entry is {}.", entry);
只有在 debug 打印語句是開啟的情況下,logger 才會格式化消息,并用 entry 代替 {}。也就是說,當禁用 debug 級別時,是不會產生 msg 參數(shù)構造的開銷的。
以下兩行將產生完全相同的輸出。但是,在禁用日志記錄語句的情況下,第二個將比第一個好至少30倍。
logger.debug("The new entry is "+entry+".");
logger.debug("The new entry is {}.", entry);
如果需要傳遞三個或更多參數(shù),您可以這樣寫:
Object[] paramArray = {newVal, below, above};
logger.debug("Value {} was inserted between {} and {}.", paramArray);
三 Logback 日志打印步驟
當用戶調用 logger 的日志打印方法時,logback 框架所采取的步驟是怎么樣的呢?現(xiàn)在我們分析當用戶調用名為 “com.nobody.UserService” 的 logger 的 info() 方法時,logback 采取的步驟 :
(1)獲得過濾器鏈決策
如果存在,那么 TurboFilter 鏈會被調用。Turbo filters 可以設置一個上下文范圍閾值,或過濾掉某些基于信息例如 Marker, Level,Logger,日志消息的事件,或與每個日志記錄請求相關聯(lián)的 Throwable。如果過濾器鏈的答復是 FilterReply.DENY,那么日志記錄請求會被拋棄。如果是 FilterReply.NEUTRAL,則繼續(xù)進行下一步,即步驟2。如果答復為 FilterReply.ACCEPT,則跳過下一步,直接跳至步驟3。
(2)logger 級別比較規(guī)則
在此步驟中,logback 將 logger 的有效級別與打印請求級別進行比較。如果根據(jù)級別規(guī)則禁用了日志記錄請求,則 logback 將丟棄該請求,(請求打印日志的級別小于 logger 設定的級別則拋棄請求)而無需進一步處理。否則,將繼續(xù)進行下一步。
(3)創(chuàng)建一個 LoggingEvent 對象
如果請求在先前的過濾器中存活下來了,則 logback 將創(chuàng)建一個 ch.qos.logback.classic.LoggingEvent 對象,其中包含請求的所有相關參數(shù),例如請求的 logger,請求級別,日志消息,可能與請求一起傳遞的異常,當前時間,當前線程,跟發(fā)出日志記錄請求相關的類的各種數(shù)據(jù)以及MDC。其中某些字段僅在實際需要時才延遲初始化。MDC用額外的上下文信息(例如請求唯一ID)來裝飾日志記錄請求。
(4)調用 appenders
創(chuàng)建 LoggingEvent 對象后,logback 將調用所有能用的 appenders 的 doAppend() 方法。
(5)格式化輸出
被調用的 appender 負責格式化日志記錄事件。但是,一些(但不是全部) appender 將格式化日志記錄事件的任務委托給 layout。layout 會格式化 LoggingEvent 實例,并以字符串形式返回結果。注意,某些 appender,例如 SocketAppender 不會將日志記錄事件轉換為字符串,而是將其序列化。因此,它們沒有 layout,也不需要 layout。
(6)發(fā)出 LoggingEvent
日志記錄事件被完全格式化后,每個 appender 會將其發(fā)送到其目的地。
下面是一個顯示了全部工作原理的 UML 序列圖。

四 logback.xl 配置
通過編程,或使用以 XML 或 Groovy 格式表示的配置腳本都可以達到配置 Logback 的效果。
logback 會按以下步驟來配置自己:
(1)Logback 嘗試在 classpath 中找一個名為 logback-test.xml 的文件 。
(2)如果找不到此類文件,則 logback 嘗試在 classpath 中找一個名為 logback.groovy 的文件 。
(3)如果找不到這樣的文件,它將在 classpath 中找一個名為 logback.xml 的文件。
(4)如果還沒有找到這樣的文件, ServiceLoader(在JDK 1.6中引入)會通過 META-INF\services\ch.qos.logback.classic.spi.Configurator 加載 com.qos.logback.classic.spi.Configurator 接口的實現(xiàn)類。
(5)如果以上方法均未成功,則 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 ,創(chuàng)建一個最小化配置。最小化配置由一個關聯(lián)到根 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 進行配置,首先我們在類路徑中創(chuàng)建 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級別打印日志
如果程序在解析配置文件期間發(fā)生警告或錯誤,則 logback 會自動在控制臺上打印其內部狀態(tài)信息。如果在沒有警告或錯誤時,你也希望檢查 logback 的內部狀態(tài),你可以指示通過調用 StatusPrinter 類的 print() 方法。如下所示:
LoggerContext lc = (LoggerContext) LoggerFactory.getILoggerFactory();
StatusPrinter.print(lc);
如果你不想在程序中編寫打印 logback 的內部狀態(tài),那可以在配置文件 configuration 元素的 debug 屬性設置為 true,同樣也可以在程序啟動時打印 logback 內部狀態(tài)。當然,前提是找到配置文件或者配置文件是格式正確的XML才會輸出內部狀態(tài)。
<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>
當然,我們也可以通過配置系統(tǒng)屬性(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 在堆棧跟蹤中啟用包數(shù)據(jù)
注意從版本1.1.4開始,包裝數(shù)據(jù)默認為禁用??砂慈缦屡渲瞄_啟包數(shù)據(jù):
<configuration packagingData="true">
...
</configuration>
當然,也可以在程序中進行配置,如下:
LoggerContext lc = (LoggerContext) LoggerFactory.getILoggerFactory();
lc.setPackagingDataEnabled(true);
如果開啟了,logback 會在輸出的堆棧行中顯示它是屬于哪個 jar 或者哪個類的。此信息由 jar 文件的名稱和版本組成,表明堆棧信息來源于此。此機制對于識別軟件版本問題非常有用。但是,計算成本相當昂貴,尤其是在經常引發(fā)異常的應用程序中。以下演示開啟的結果,即多了 [] 括號內的信息。
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 關聯(lián)的 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 屬性的值是不區(qū)分大小寫的字符串 TRACE,DEBUG,INFO,WARN,ERROR,ALL,OFF。還有不區(qū)分大小寫的值 INHERITED 或其同義詞 NULL,代表將強制從層次結構中較高的層次繼承記錄器繼承級別。
<logger> 元素里面可包含0或多個 <appender-ref> 元素,這樣引用的 appender 會關聯(lián)到此 logger。不同 log4j,即便你在配置文件配置了 logger 關聯(lián)的 appender,logback-classic 也不會關閉或者移除之前關聯(lián)的 appender。
4.4.2 配置 root logger,<root> 元素
<root>元素用來配置 root logger。它支持單個屬性,即 level 級別屬性。它沒有其他屬性,因為可疊加性標志不適用于根記錄器。此外,由于根記錄器已被命名為 “ ROOT” ,因此它也不允許使用 name 屬性。level 屬性的值可以是不區(qū)分大小寫的字符串 TRACE,DEBUG,INFO,WARN,ERROR,ALL或OFF之一。但是,根記錄器的級別不能設置為 INHERITED 或 NULL。
與 <logger> 元素類似,<root> 元素也可以包含零個或多個 <appender-ref>元素。如此引用的每個附加程序都會添加到根記錄器中。不同 log4j,即便你在配置文件配置了 root logger 關聯(lián)的 appender,logback-classic 也不會關閉或者移除之前關聯(lián)的 appender。
下面演示個 demo,假設我們不想打印 “com.nobody.entity” 包下任何組件的任何 DEBUG 消息??梢园慈缦屡渲茫?/span>
<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>
我們可以根據(jù)需要配置任意數(shù)量的記錄器。如下,我們將 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> 可以包含任意數(shù)量的與 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 會將日志輸出到它自己關聯(lián)的所有 appender 和 它上層級(祖先)所關聯(lián)的所有 appender。所以,如果將同一個 appender 關聯(lián)到不同的 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 關聯(lián)的 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> 配置可以更改其名稱,使用此值打印到日志中,用于區(qū)分不同應用程序的記錄。但一旦設置,它的名稱就無法變更。
<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 配置文件支持變量的定義和替換。變量具有作用域。變量可以在配置文件中,在外部文件中,在外部資源中,甚至可以即時計算和定義。
變量替換可以發(fā)生在配置文件中可以指定值的任何位置。語法是 ${variableName}。
考慮到常用性,HOSTNAME 和 CONTEXT_NAME 變量默認已定義,并具有上下文作用域??紤]到在某些環(huán)境中可能需要花費一些時間來計算主機名,因此它的值是延遲計算的(僅在需要時)。
<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
如果你定義的變量太多時,可以創(chuàng)建單獨的文件來保存,方便管理。
<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>
變量作用域
定義的變量是有作用域的,如本地作用域,上下文作用域,系統(tǒng)級作用域。默認是本地作用域。從操作系統(tǒng)環(huán)境中讀取變量很容易,但是無法寫入到操作系統(tǒng)環(huán)境中。
(1)Local Scope(本地作用域):從配置文件中定義的本地變量即在本地配置文件使用。每次解析和執(zhí)行配置文件時,都會重新定義本地作用域中的變量。
(2)Context Scope(上下文作用域):一個擁有上下文作用域的變量存在于上下文中,于上下文共存,直到被清除。在所有記錄事件中都可用到,包括那些通過序列化發(fā)送到遠程主機的事件。
(3)System Scope(系統(tǒng)級作用域):系統(tǒng)級作用域的變量被插入到JVM的系統(tǒng)屬性中,生命周期和JVM一致,直到被清除。
在進行屬性替換時,查找變量的順序為:local scope,context scope,system
<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”。
有條件地處理配置文件
我們可能需要在不同的環(huán)境(例如dev,test,prod)切換不同的logback配置文件。然而這些配置文件大部分內容是一樣的,極少內容是不同的。為了減少多個配置文件,可以使用條件處理標簽,<if>, <then> 和 <else>,根據(jù)不同環(huán)境進行配置。不過,需要引入Janino 庫。
<!-- if-then 形式 -->
<if condition="表達式">
<then>
...
</then>
</if>
<!-- if-then-else 形式 -->
<if condition="表達式">
<then>
...
</then>
<else>
...
</else>
</if>
condition 條件只能是上下文屬性或系統(tǒng)屬性的Java表達式。對于通過參數(shù)傳遞的鍵,可以通過 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語法非常繁瑣,為以后后續(xù)其他開發(fā)者以及自己能快速理解,盡量少用。
文件包含
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會打印內部的狀態(tài)信息。如果包含的文件是可選的,可以通過optional屬性設置為true來進制打印顯示警告信息。
<include optional="true" ..../>
五 Appenders
Logback 將寫日志記錄事件的任務委派給appender組件。Appenders必須實現(xiàn)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接口中的大多數(shù)方法是setter和getter。不過有個例外是doAppend(E event)方法。E的實際類型取決于logback模塊。在logback-classic模塊中,E的類型為ILoggingEvent;在logback-access模塊中,類型的E為AccessEvent。doAppend()方法是logback框架中比較重要的方法。它負責以合適的格式將日志記錄事件輸出到合適的輸出設備中。
Appender接口擴展了FilterAttachable接口。所以可以將一個或多個過濾器關聯(lián)到appender實例。
Appender負責輸出日志記錄事件。但是,他們可以將事件的實際格式委托給Layout或Encoder對象處理。每個layout和encoder都只能與有且一個appender相關聯(lián)。某些appender具有內置或固定的事件格式。因此,它們不需要Layout或Encoder。
————————————————
版權聲明:本文為CSDN博主「陳皮的JavaLib」的原創(chuàng)文章,遵循CC 4.0 BY-SA版權協(xié)議,轉載請附上原文出處鏈接及本聲明。
原文鏈接:
https://blog.csdn.net/chenlixiao007/article/details/113899664
鋒哥最新SpringCloud分布式電商秒殺課程發(fā)布
??????
??長按上方微信二維碼 2 秒
感謝點贊支持下哈 
