006_logback体系结构
1. logback的體系結構
1.1. Logback的基本結構充分通用, 可應用于各種不同環境。目前, logback分為三個模塊: Core、Classic和Access。
1.2. Core模塊是其他兩個模塊的基礎。Classic模塊擴展了core模塊。Classic模塊相當于log4j的顯著改進版。Logback-classic直接實現了SLF4J API, 因此你可以在logback與其他記錄
系統如log4j和java.util.logging(JUL)之間輕松互相切換。Access模塊與Servlet容器集成, 提供HTTP訪問記錄功能。
2. Logger、Appender和Layout
2.1. Logback建立于三個主要類之上: Logger、Appender和Layout。這三種組件協同工作, 使開發者可以按照消息類型和級別來記錄消息, 還可以在程序運行期內控制消息的輸出格式
和輸出目的地。
2.2. Logger類是logback-classic模塊的一部分, 而Appender和Layout接口來自logback-core。作為一個多用途模塊, logback-core不包含任何logger。
3. Logger上下文
3.1. 任何比System.out.println高級的記錄API的第一個也是最重要的優點便是能夠在禁用特定記錄語句的同時卻不妨礙輸出其他語句。這種能力源自記錄隔離(space), 即所有各
種記錄語句的隔離是根據開發者選擇的條件而進行分類的。在logback-classic里, 這種分類是logger固有的。各個logger都被關聯到一個LoggerContext, LoggerContext負責制造logger, 也負責以樹結構排列各logger。
4. 獲取Logger
4.1. 用同一名字調用LoggerFactory.getLogger方法所得到的永遠都是同一個logger對象的引用。
4.2. x和y指向同一個logger對象。
Logger x = LoggerFactory.getLogger("wombat"); Logger y = LoggerFactory.getLogger("wombat");4.3. Logback簡化了logger命名, 方法是在每個類里初始化logger, 以類的全限定名作為logger名。這種定義logger的方法即有用又直觀。由于記錄輸出里包含logger名, 這種命名方法很容易確定記錄消息來源。
4.4. Logback不限制logger名, 你可以隨意命名logger。然而, 目前已知最好的策略是以logger所在類的名字作為logger名稱。
5. Level層次化的命名規則
5.1. Logger是命名了的實體。它們的名字大小寫敏感且遵從下面的層次化的命名規則: 如果logger的名稱帶上一個點號后是另外一個logger的名稱的前綴, 那么, 前者就被稱為后者的祖先。如果logger與其后代logger之間沒有其他祖先, 那么, 前者就被稱為子logger之父。
5.2. 根logger位于logger等級的最頂端, 它的特別之處是它是每個層次等級的共同始祖。如同其他logger, 根logger可以通過其名稱取得, 如下所示:
Logger rootLogger1 = (Logger) LoggerFactory.getLogger(org.slf4j.Logger.ROOT_LOGGER_NAME); Logger rootLogger2 = (Logger) LoggerFactory.getLogger("ROOT");5.3. 例子
5.3.1. 新建一個名為LogbackArchitecture的Java工程, 添加相關jar包
5.3.2. 編寫LoggerGradation.java
package com.zr.rct;import java.util.List; import ch.qos.logback.classic.Logger; import org.slf4j.LoggerFactory; import ch.qos.logback.classic.LoggerContext;public class LoggerGradation {public static void main(String[] args) {LoggerFactory.getLogger("com.zr.zrsc.Shopping");LoggerFactory.getLogger("net.zr.zrxxw.Article");LoggerContext lc = (LoggerContext)LoggerFactory.getILoggerFactory();/*** logger列表*/List<Logger> loggers = lc.getLoggerList();for (Logger logger : loggers) {System.out.println(logger.getName());}} }5.3.3. 運行結果
5.3.4. 層次結構
5.4. 各個logger都被關聯到一個LoggerContext, LoggerContext負責制造logger, 也負責以樹結構排列各logger。
6. Level類
6.1. Level類位于ch.qos.logback.classic包下, 并且是最終類, 不能被繼承。
6.2. Level類定義了幾個日志級別: ERROR、WARN、INFO、DEBUG和TRACE。
6.3. Level類還有2個特殊的日志級別: OFF和ALL。
6.4. 日志級別都分配了相應的整數, OFF的整數值是Integer.MAX_VALUE, ERROR的整數值是40000, WARN的整數值是30000, INFO的整數值是20000, DEBUG的整數值是10000, TRACE的整數值是5000, ALL的整數值是Integer.MIN_VALUE。
7. Level級別繼承
7.1. Logger可以被分配級別。級別包括: TRACE、DEBUG、INFO、WARN和ERROR。如果logger沒有被分配級別, 那么它將從有被分配級別的最近的祖先那里繼承級別。
7.2. 為確保所有logger都能夠最終繼承一個級別, 根logger總是有級別, 默認情況下, 這個級別是DEBUG。
7.3. 僅根logger被分配了級別。根級別值DEBUG被其他logger, X、X.Y和X.Y.Z繼承。
7.4. 所有logger都被分配了級別。級別繼承不發揮作用。
7.5. 根logger、X和X.Y.Z分別被分配了DEBUG、INFO和ERROR級別。X.Y從其父X繼承級別。
7.6. 根logger和X分別被分配了DEBUG和INFO級別。X.Y和X.Y.Z從其最近的父X繼承級別, 因為X被分配了級別。
7.7. 例子
7.7.1. 編寫LoggerLevelGradation.java?
package com.zr.rct;import java.util.List; import ch.qos.logback.classic.Level; import ch.qos.logback.classic.Logger; import org.slf4j.LoggerFactory; import ch.qos.logback.classic.LoggerContext;public class LoggerLevelGradation {public static void main(String[] args) {LoggerFactory.getLogger("com.zr.zrsc.Shopping");LoggerFactory.getLogger("net.zr.zrxxw.Article");LoggerContext lc = (LoggerContext)LoggerFactory.getILoggerFactory();List<Logger> loggers = lc.getLoggerList();System.out.println("日志名\t日志級別\t有效級別");defaultLevel(loggers);}public static void defaultLevel(List<Logger> loggers) {for (Logger logger : loggers) {System.out.println(logger.getName() + " " + logger.getLevel() + " " + logger.getEffectiveLevel());}}public static void comInfoLevel(List<Logger> loggers) {Logger rootLogger = (Logger) LoggerFactory.getLogger("com");rootLogger.setLevel(Level.INFO);for (Logger logger : loggers) {System.out.println(logger.getName() + " " + logger.getLevel() + " " + logger.getEffectiveLevel());}}public static void netRjbdWarnLevel(List<Logger> loggers) {Logger rootLogger = (Logger) LoggerFactory.getLogger("net.zr");rootLogger.setLevel(Level.WARN);for (Logger logger : loggers) {System.out.println(logger.getName() + " " + logger.getLevel() + " " + logger.getEffectiveLevel());}}public static void rootAllLevel(List<Logger> loggers) {Logger rootLogger = (Logger) LoggerFactory.getLogger("ROOT");rootLogger.setLevel(Level.ALL);for (Logger logger : loggers) {System.out.println(logger.getName() + " " + logger.getLevel() + " " + logger.getEffectiveLevel());}}public static void rootOffLevel(List<Logger> loggers) {Logger rootLogger = (Logger) LoggerFactory.getLogger("ROOT");rootLogger.setLevel(Level.OFF);for (Logger logger : loggers) {System.out.println(logger.getName() + " " + logger.getLevel() + " " + logger.getEffectiveLevel());}} }7.7.2. 都不設置日志級別, 默認使用根logger的日志級別
7.7.3. 日志com設置info日志級別, com.zr, com.zr.zrsc和com.zr.zrsc.Shopping的日志級別都是null, 但是它們的有效級別繼承自com, 是info。
7.7.4. 日志net.zr設置warn日志級別, net.zr.zrxxw和net.zr.zrxxw.Article的日志級別都是null, 但是它們的有效級別繼承自net.zr, 是warn。net是net.zr的父親, 它的日志級別是null, 有效日志級別繼承自根日志級別是debug。
7.7.5. 根日志級別設置為all。
7.7.6. 根日志級別設置為off。
8. 打印方法和基本選擇規則
8.1. 根據定義, 打印方法決定記錄請求的級別。例如, 如果L是一個logger實例, 那么, 語句L.info("..")是一條級別為INFO的記錄語句。
8.2. 記錄請求的級別在高于或等于其logger的有效級別時被稱為被啟用, 否則, 稱為被禁用。如前所述, 沒有被分配級別的logger將從其最近的祖先繼承級別。
8.3. 記錄請求級別為p, 其logger的有效級別為q, 只有則當p>=q時, 該請求才會被執行。該規則是logback的核心。級別排序為:TRACE < DEBUG < INFO < WARN < ERROR。
8.4. 下表顯示了選擇規則是如何工作的。行頭是記錄請求的級別p。列頭是logger的有效級別q。行(請求級別)與列(有效級別)的交叉部分是按照基本選擇規則得出的布爾值。
8.5.?例子?
8.5.1. 編寫LoggerPrint.java
package com.zr.rct;import org.slf4j.LoggerFactory; import ch.qos.logback.classic.Level; import ch.qos.logback.classic.Logger;public class LoggerPrint {public static void main(String[] args) {Logger rootLogger = (Logger) LoggerFactory.getLogger("ROOT");Logger comLogger = (Logger) LoggerFactory.getLogger("com.rjbd.hnsc.Shopping");Logger netLogger = (Logger) LoggerFactory.getLogger("net.rjbd.rjbdw.Article");netWarn(rootLogger);comLogger.error("com.zr.zrsc.Shopping error");comLogger.warn("com.zr.zrsc.Shopping warn");comLogger.info("com.zr.zrsc.Shopping info");comLogger.debug("com.zr.zrsc.Shopping debug");comLogger.trace("com.zr.zrsc.Shopping trace");netLogger.error("net.zr.zrxxw.Article error");netLogger.warn("net.zr.zrxxw.Article warn");netLogger.info("net.zr.zrxxw.Article info");netLogger.debug("net.zr.zrxxw.Article debug");netLogger.trace("net.zr.zrxxw.Article trace");}public static void comInfo(Logger comLogger) {comLogger.setLevel(Level.INFO);}public static void netWarn(Logger netLogger) {netLogger.setLevel(Level.WARN);}public static void rootAll(Logger rootLogger) {rootLogger.setLevel(Level.ALL);}public static void rootOff(Logger rootLogger) {rootLogger.setLevel(Level.OFF);} }8.5.2. 日志com設置為info, 打印信息
8.5.3. 日志net設置為warn, 打印信息
8.5.4. 根日志設置為all, 打印信息
8.5.5. 根日志設置為off, 關閉所有日志信息。
9. Appender和Layout
9.1. 在logback里, 一個輸出目的地稱為一個appender。目前有控制臺、文件、遠程套接字服務器、MySQL、PostreSQL、Oracle和其他數據庫和遠程UNIX Syslog守護進程等多種appender。
9.2. 一個logger可以被關聯多個appender。方法addAppender為指定的logger添加一個appender。對于logger的每個啟用了的記錄請求, 都將被發送到logger里的全部appender及繼承的appender。
9.3. Logger L的記錄語句的輸出會發送給L及其祖先的全部appender。這就是"appender疊加性"的含義。然而, 如果logger L的某個祖先P設置疊加性標識為false, 那么, L的輸出會發送給L與P之間(含P)的所有appender, 但不會發送給P的任何祖先的appender。Logger的疊加性默認為true。
9.4. 示例
9.5. 有些用戶希望不僅可以定制輸出目的地, 還可以定制輸出格式。這時為appender關聯一個layout即可。Layout負責根據用戶意愿對記錄請求進行格式化, appender負責將格式化后的輸出發送到目的地。PatternLayout是標準logback發行包的一部分, 允許用戶按照類似于C語言的printf函數的轉換模式設置輸出格式。
9.6. 例如 ?
9.6.1. 轉換模式"%-4relative [%thread] %-5level %logger{32} - %msg%n"。
9.6.2. 第一個字段是自程序啟動以來的逝去時間, 單位是毫秒。
9.6.3. 第二個地段發出記錄請求的線程。
9.6.4. 第三個字段是記錄請求的級別。
9.6.5. 第四個字段是與記錄請求關聯的logger的名稱。
9.6.6. "-"之后是請求的消息文字。
9.7. 參數化記錄
9.7.1. 因為logback-classic里的logger實現了SLF4J的Logger接口, 某些打印方法可接受多個參數。這些不同的打印方法主要是為了在提高性能的同時盡量不影響代碼可讀性。
9.7.2. 對于某個Logger, 如下面的代碼, 在構造消息參數時有性能消耗, 即把整數i和entry[i]都轉換為字符串時, 還有連接多個字符串時。不管消息會不會被記錄, 都會造成上述消耗。
logger.debug("Entry number: " + i + " is " + String.valueOf(entry[i]));9.7.3. 一個可行的辦法是用測試語句包圍記錄語句以避免上述消耗, 如下面的代碼, 當logger的debug級別被禁用時, 這個方法可以避免參數構造帶來的性能消耗。另一方面, 如果logger的DEBUG級別被啟用, 那么會導致兩次評估logger是否被啟用: 一次是isDebugEnabled方法, 一次是debug方法。在實踐中, 這種額外開銷無關緊要, 因為評估logger所消耗的時間不足實際記錄請求所用時間的1%。
if(logger.isDebugEnabled()) { logger.debug("Entry number: " + i + " is " + String.valueOf(entry[i])); }9.7.4. 還有一種基于消息格式的更好的替代方法。如下面的代碼, 在評估是否作記錄后, 僅當需要作記錄時, logger才會格式化消息, 用entry的字符串值替換"{}"。換句話說, 當記錄語句被禁用時, 這種方法不會產生參數構造所帶來的性能消耗。
Object entry = new SomeObject(); ??logger.debug("The entry is {}.", entry); ?
9.8. 例子
9.8.1. 編寫LoggerAppender.java
package com.zr.rct;import org.slf4j.LoggerFactory; import ch.qos.logback.classic.Logger; import ch.qos.logback.classic.LoggerContext; import ch.qos.logback.classic.PatternLayout; import ch.qos.logback.classic.spi.ILoggingEvent; import ch.qos.logback.core.FileAppender;public class LoggerAppender {public static void main(String[] args) {Logger comLogger = (Logger) LoggerFactory.getLogger("com.zr.zrsc.Shopping");Logger netLogger = (Logger) LoggerFactory.getLogger("net.zr.zrxxw.Article");LoggerContext lc = (LoggerContext)LoggerFactory.getILoggerFactory();PatternLayout pl = new PatternLayout();pl.setPattern("%-4relative [%thread] %-5level %logger{32} - %msg%n");pl.setContext(lc);pl.start();FileAppender<ILoggingEvent> fa = new FileAppender<ILoggingEvent>();fa.setContext(lc);fa.setFile("log/my.log");fa.setLayout(pl);fa.start();Logger rootLogger = lc.getLogger(Logger.ROOT_LOGGER_NAME);rootLogger.addAppender(fa);comLogger.error("com.zr.zrsc.Shopping {}", "錯誤");comLogger.warn("com.zr.zrsc.Shopping {}", "警告");comLogger.info("com.zr.zrsc.Shopping {}", "信息");comLogger.debug("com.zr.zrsc.Shopping {}", "測試");comLogger.trace("com.zr.zrsc.Shopping {}", "跟蹤");netLogger.error("net.zr.zrxxw.{} {}", "Article", "錯誤");netLogger.warn("net.zr.zrxxw.{} {}", "Article", "警告");netLogger.info("net.zr.zrxxw.{} {}", "Article", "信息");netLogger.debug("net.zr.zrxxw.{} {}", "Article", "測試");netLogger.trace("net.zr.zrxxw.{} {}", "Article", "跟蹤");} }9.8.2. 運行結果, 在項目根目錄下多了一個log文件夾, log文件夾下生成了一個my.log文件
10. 工作原理
10.1. 取得過濾鏈(filter chain)的判定結果
10.1.1. 如果TurboFilter鏈存在, 它將被調用。Turbo filters能夠設置一個上下文范圍內的臨界值, 這個臨界值或者表示過濾某些與信息有關(比如Marker、級別、Logger、
消息)的特定事件或者表示與每個記錄請求相關聯的Throwable。如果過濾鏈的結果是FilterReply.DENY, 則記錄請求被拋棄。如果結果是FilterReply.NEUTRAL, ?則繼續下一個步, 也就是第二步。如果結果是FilterReply.ACCEPT, 則忽略過第二步, 進入第三步。
10.2. 應用基本選擇規則
10.2.1. Logback對logger的有效級別與請求的級別進行比較。如果比較的結果是記錄請求被禁用, logback會直接拋棄請求, 不做任何進一步處理。否則, 繼續下一步。
10.3. 創建LoggingEvent對象
10.3.1. 記錄請求到了這一步后, logback會創建一個ch.qos.logback.classic.LoggingEvent對象, 該對象包含所有與請求相關的參數, 比如請求用的logger、請求級別、消息、請求攜帶的異常、當前時間、當前線程、執行記錄請求的類的各種數據, 還有MDC。注意有些成員是延遲初始化的, 只有當它們真正被使用時才會被初始化。MDC用來為記錄請求添加額外的上下文信息。
10.4. 調用appender
10.4.1. 創建了LoggingEvent對象后, logback將調用所有可用appender的doAppend()方法, 這就是說, appender繼承logger的上下文。
10.4.2. 所有appender都繼承AppenderBase抽象類, AppenderBase在一個同步塊里實現了doAppend方以確保線程安全。AppenderBase的doAppender()方法也調用appender
關聯的自定義過濾器, 如果它們存在的話。自定義過濾器能被動態地關聯到任何appender。
10.5. 格式化輸出
10.5.1. 那些被調用了的appender負責對記錄事件(LoggingEvent)進行格式化。然而, 有些但不是全部appender把格式化記錄事件的工作委托給layout。Layout對
LoggingEvent實例進行格式化, 然后把結果以字符串的形式返回。注意有些appender, 比如SocketAppender, 把記錄事件進行序列化而不是轉換成字符串, 所
以它們不需要也沒有layout。
10.6. 發送記錄事件(LoggingEvent)
10.6.1. 記錄事件被格式化后, 被各個appender發送到各自的目的地。
10.7. 下圖是整個流程的UML圖
總結
以上是生活随笔為你收集整理的006_logback体系结构的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 005_logback介绍
- 下一篇: 007_logback配置