以下內(nèi)容翻譯整理自logback
官方手冊(cè)蒂胞,地址:logback官方手冊(cè)
logback 的架構(gòu)
logback
的基本架構(gòu)足夠通用然磷,可以應(yīng)用于不同的環(huán)境。目前,logback
分為三個(gè)模塊:logback-core
喧笔,logback-classic
和logback-access
。
core
模塊是其它兩個(gè)模塊的基礎(chǔ)龟再,classic
模塊繼承core
模塊书闸,classic
模塊相對(duì)log4j
版本有顯著的改進(jìn),logback-classic
天生實(shí)現(xiàn)了SLF4J API
利凑,所以你可以在logback
和其他日志框架之間自由切換浆劲,比如log4j
和JDK1.4引入的JUL(java.util.logging)
嫌术。access
模塊集成了Servlet
容器,用來提供HTTP-access
日志功能牌借,一個(gè)單獨(dú)的文檔包含訪問模塊文檔度气。
在本文檔的其余部分中,我們將引用logback-classic
模塊來編寫logback
膨报。
Logger磷籍,Appenders 和 Layouts
Logback
基于三個(gè)主要類:Logger
,Appender
和Layout
现柠,這三種類型的組件協(xié)同工作院领,使開發(fā)人員能夠根據(jù)消息類型和級(jí)別記錄消息,并在運(yùn)行時(shí)控制這些消息的格式和報(bào)告位置晒旅。
Logger
類是logback-classic
模塊的一部分栅盲,Appender
和Layout
接口是logback-core
模塊的一部分。作為通用模塊废恋,logback-core
沒有日志記錄器的概念。
logger
是日志記錄器扒寄,appender
是追加器鱼鼓,layout
是布局。
Logger 上下文
任何日志API
相對(duì)于普通的System.out.println
的最重要的優(yōu)勢(shì)是能夠禁用某些日志語句该编,同時(shí)允許其他語句不受阻礙地打印迄本。該功能假定的日志空間是根據(jù)一些開發(fā)人員選擇的標(biāo)準(zhǔn)進(jìn)行分類的。在logback-classic
中课竣,這種分類是logger
的固有組成部分嘉赎。每個(gè)logger
都附加到一個(gè)LoggerContext
,該上下文負(fù)責(zé)生成logger
于樟,并將它們安排在類似層次結(jié)構(gòu)的樹中公条。
logger
是命名實(shí)體。它們的名字區(qū)分大小寫迂曲,并遵循分層命名規(guī)則:
如果一個(gè)
logger
的名稱后面跟著一個(gè)點(diǎn)靶橱,那么這個(gè)logger
就是另一個(gè)logger
的祖先。如果在其自身和后代logger
之間沒有祖先路捧,則該logger
被稱為子logger
的父关霸。
例如,名稱為com.foo
的logger
是名稱為com.foo.Bar
的logger
的父杰扫,類似地队寇,java
是java.util
的父,是java.util.Vector
的祖先章姓。開發(fā)人員都應(yīng)該熟悉這種命名方案佳遣。
根logger
位于logger
層次結(jié)構(gòu)的頂部炭序。它的特殊之處在于,它一開始就是每個(gè)層次結(jié)構(gòu)的一部分苍日。與每個(gè)logger
一樣惭聂,可以通過它的名稱獲取它,如下所示:
Logger rootLogger = LoggerFactory.getLogger(org.slf4j.Logger.ROOT_LOGGER_NAME);
所有其他logger
也可以通過org.slf4j.LoggerFactory
類中的靜態(tài)方法getLogger()
來獲取相恃。該方法需要傳遞日志記錄器的名稱作為參數(shù)辜纲。下面列出了Logger
接口中的一些基本方法。
package org.slf4j;
public interface Logger {
String ROOT_LOGGER_NAME = "ROOT";
// 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);
}
有效級(jí)別(級(jí)別繼承)
logger
可以被分配級(jí)別拦耐,可以設(shè)置的級(jí)別有TRACE, DEBUG, INFO, WARN, ERROR
耕腾,這些級(jí)別別定義在ch.qos.logback.classic.Level
類中,該類是final修飾的杀糯,不能被子類化扫俺。
如果一個(gè)給定的logger
沒有被分配一個(gè)級(jí)別,那么它將從其最近的祖先那里繼承一個(gè)級(jí)別固翰。為了確保所有的logger
最終都能繼承到一個(gè)級(jí)別狼纬,根logger
有一個(gè)默認(rèn)級(jí)別DEBUG
。
下面是四個(gè)例子骂际,根據(jù)級(jí)別繼承規(guī)則疗琉,使用各種指定的級(jí)別值和產(chǎn)生的有效(繼承)級(jí)別。
示例1
Logger name | 指定級(jí)別 | 有效級(jí)別 |
---|---|---|
root | DEBUG | DEBUG |
X | none | DEBUG |
X.Y | none | DEBUG |
X.Y.Z | none | DEBUG |
示例1中歉铝,只有根logger
被分配了一個(gè)級(jí)別盈简。這個(gè)級(jí)別是DEBUG
,由其他logger
繼承太示。X, X.Y, X.Y.Z
柠贤。
示例2
Logger name | 指定級(jí)別 | 有效級(jí)別 |
---|---|---|
root | ERROR | ERROR |
X | INFO | INFO |
X.Y | DEBUG | DEBUG |
X.Y.Z | WARN | WARN |
示例2中,所有logger
都有一個(gè)指定的級(jí)別值类缤,級(jí)別繼承不起作用臼勉。
示例3
Logger name | 指定級(jí)別 | 有效級(jí)別 |
---|---|---|
root | DEBUG | DEBUG |
X | INFO | INFO |
X.Y | none | INFO |
X.Y.Z | ERROR | ERROR |
示例3中,日志記錄器root
, X
和X.Y.Z
都有指定的級(jí)別呀非,X.Y
沒有指定級(jí)別坚俗,是從父日志記錄器X
繼承的級(jí)別。
示例4
Logger name | 指定級(jí)別 | 有效級(jí)別 |
---|---|---|
root | DEBUG | DEBUG |
X | INFO | INFO |
X.Y | none | INFO |
X.Y.Z | none | INFO |
示例4中岸裙,日志記錄器root
和X
有指定的級(jí)別猖败,X.Y
和X.Y.Z
沒有指定級(jí)別,從最近的有指定級(jí)別的父級(jí)X
繼承級(jí)別值降允。
打印方法和基本選擇規(guī)則
根據(jù)定義恩闻,打印方法確定日志請(qǐng)求的級(jí)別。例如剧董,如果L
是一個(gè)logger
實(shí)例幢尚,那么語句L. INFO(“..”)
就是一個(gè)級(jí)別INFO
的日志語句破停。
如果日志記錄請(qǐng)求的級(jí)別高于或等于其日志記錄程序的有效級(jí)別,則啟用日志記錄請(qǐng)求尉剩。否則真慢,該請(qǐng)求將被禁用。如前所述理茎,沒有指定級(jí)別的日志記錄器將從其最近的祖先那里繼承一個(gè)級(jí)別黑界。這條規(guī)則是logback
的核心。它規(guī)定各級(jí)的次序如下:
TRACE < DEBUG < INFO < WARN < ERROR
下面是一個(gè)基本選擇規(guī)則的例子皂林。
package com.wangbo.cto.logback;
import ch.qos.logback.classic.Level;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* @date 2019/9/13 22:48
* @auther wangbo
*/
public class LogLevelTest {
public static void main(String[] args) {
//獲取一個(gè)名為“com.foo”的logger朗鸠,為了能設(shè)置級(jí)別,轉(zhuǎn)換為ch.qos.logback.classic.Logger類型
ch.qos.logback.classic.Logger logger = (ch.qos.logback.classic.Logger) LoggerFactory.getLogger("com.foo");
//設(shè)置級(jí)別
logger.setLevel(Level.INFO);
//繼承最近的父com.foo的級(jí)別info
Logger barlogger = LoggerFactory.getLogger("com.foo.Bar");
//warn >= info础倍,啟用此請(qǐng)求
logger.warn("Low fuel level.");
//debug <= info烛占,此請(qǐng)求已禁用
logger.debug("Starting search for nearest gas station.");
//info >= info,啟用此請(qǐng)求
barlogger.info("Located nearest gas station.");
//debug <= info沟启,此請(qǐng)求已禁用
barlogger.debug("Exiting gas station search");
}
}
運(yùn)行結(jié)果
22:59:44.139 [main] WARN com.foo - Low fuel level.
22:59:44.141 [main] INFO com.foo.Bar - Located nearest gas station.
獲取 Logger
調(diào)用LoggerFactory.getLogger
忆家,相同名稱的方法將始終返回相同Logger
對(duì)象的引用。例如:
Logger x = LoggerFactory.getLogger("wombat");
Logger y = LoggerFactory.getLogger("wombat");
X
和Y
是相同的Logger
對(duì)象美浦。
因此弦赖,可以配置一個(gè)日志程序,然后在代碼的其他地方通過相同的名字獲取到相同的實(shí)例浦辨,而不需要傳遞引用。與生物學(xué)意義上的父母(父母總是先于子女)相反沼沈,logback
日志記錄器可以按任何順序創(chuàng)建和配置流酬。特別是,父logger
將發(fā)現(xiàn)并鏈接到它的后代列另,即使它是在它們之后實(shí)例化的芽腾。
通常在應(yīng)用程序初始化時(shí)配置logback環(huán)境。首選的方法是讀取配置文件页衙。不久將討論這種方法摊滔。
以日志記錄器所在的類命名日志記錄器似乎是迄今為止所知的最佳通用策略。
Appenders 和 Layouts
根據(jù)日志程序選擇性地啟用或禁用日志記錄請(qǐng)求的功能只是一部分店乐。Logback
允許將日志請(qǐng)求打印到多個(gè)目的地艰躺。在logback
中,輸出目的地稱為appender
眨八。目前腺兴,針對(duì)控制臺(tái)、文件廉侧、遠(yuǎn)程套接字服務(wù)器页响、MySQL篓足、PostgreSQL、Oracle和其他數(shù)據(jù)庫闰蚕、JMS和遠(yuǎn)程UNIX Syslog守護(hù)進(jìn)程存在附加程序栈拖。
一個(gè)logger
可以附加多個(gè)appender
。
addAppender
方法向給定的logger
添加一個(gè)appender
没陡。對(duì)于給定的logger
涩哟,每個(gè)啟用的日志請(qǐng)求都將被轉(zhuǎn)發(fā)到該logger
中的所有appender
以及層次結(jié)構(gòu)中更高的appender
。換句話說诗鸭,appender
是附加地從日志程序?qū)哟谓Y(jié)構(gòu)繼承的染簇。例如,如果將控制臺(tái)appender
添加到根logger
强岸,那么所有啟用的日志請(qǐng)求至少都將打印在控制臺(tái)上锻弓。此外,如果向logger
(L)添加了一個(gè)文件appender
蝌箍,然后青灼,為 L 和 L 的子節(jié)點(diǎn)啟用的日志記錄請(qǐng)求將打印在文件里和控制臺(tái)上。通過將logger
的additivity flag
設(shè)置為false
妓盲,可以覆蓋此默認(rèn)行為杂拨,使追加器積累不再是附加的。
下表是一個(gè)例子:
Logger Name | 附加的 Appenders | Additivity Flag | 輸出目標(biāo) | 注釋 |
---|---|---|---|---|
root | A1 | 不適用 | A1 | 由于根日志程序位于日志程序?qū)哟谓Y(jié)構(gòu)的頂部悯衬,所以不應(yīng)用加法標(biāo)志弹沽。 |
x | A-x1, A-x2 | true | A1, A-x1, A-x2 | 使用了 x 和 root 的追加器 |
x.y | none | true | A1, A-x1, A-x2 | 使用了 x 和 root 的追加器 |
x.y.z | A-xyz1 | true | A1, A-x1, A-x2, A-xyz1 | 使用了 x.y.z,x 和 root的追加器 |
security | A-sec | false | A-sec | 由于可加性標(biāo)志設(shè)置為 false筋粗,所以沒有追加器累加策橘,只會(huì)使用一個(gè)追加器 A-sec |
security.access | none | true | A-sec | 因?yàn)?security 中的可加性標(biāo)志設(shè)置為 false,所以只使用 security 的追加器 A-sec |
通常娜亿,用戶不僅希望自定義輸出目的地丽已,還希望自定義輸出格式÷蚓觯可以通過將layout
與appender
關(guān)聯(lián)來實(shí)現(xiàn)沛婴。layout
負(fù)責(zé)根據(jù)用戶的意愿格式化日志請(qǐng)求,appender
負(fù)責(zé)將格式化的輸出發(fā)送到它的目的地督赤。PatternLayout
是標(biāo)準(zhǔn)logback
分發(fā)版的一部分嘁灯,允許用戶根據(jù)類似于C
語言printf
函數(shù)的轉(zhuǎn)換模式指定輸出格式。
例如够挂,PatternLayout
設(shè)置為%-4relative [%thread] %-5level %logger{32} - %msg%n
旁仿,將輸出類似于下面格式的內(nèi)容:
176 [main] DEBUG manual.architecture.HelloWorld2 - Hello world.
第一個(gè)字段是自程序啟動(dòng)以來經(jīng)過的毫秒數(shù)。第二個(gè)字段是發(fā)出日志請(qǐng)求的線程。第三個(gè)字段是日志請(qǐng)求的級(jí)別枯冈。第四個(gè)字段是與日志請(qǐng)求關(guān)聯(lián)的日志記錄器的名稱毅贮。'-'后面的文本是請(qǐng)求的消息。
參數(shù)化日志
考慮到logback-classic
中的logger
實(shí)現(xiàn)了SLF4J
的Logger
接口尘奏,某些打印方法允許多個(gè)參數(shù)滩褥。這些打印方法變體主要是為了提高性能,同時(shí)降低對(duì)代碼可讀性的影響炫加。
普通寫法
對(duì)于一些logger
瑰煎,可以這樣寫:
logger.debug("Entry number: " + i + " is " + String.valueOf(entry[i]));
該參數(shù)將整數(shù) i 和 entry[i] 轉(zhuǎn)換為字符串,并連接中間的字符串俗孝。會(huì)導(dǎo)致構(gòu)造消息參數(shù)的額外開銷酒甸,但是這與是否記錄消息沒有關(guān)系。
避免參數(shù)構(gòu)造額外開銷的一種方法是用一個(gè)測(cè)試包圍 log 語句赋铝。比如這樣:
if(logger.isDebugEnabled()) {
logger.debug("Entry number: " + i + " is " + String.valueOf(entry[i]));
}
這樣插勤,如果logger
禁用了DEBUG
級(jí)別,就不會(huì)產(chǎn)生參數(shù)構(gòu)造的開銷革骨。另一方面农尖,如果logger
啟用了DEBUG
級(jí)別,系統(tǒng)將承擔(dān)兩次評(píng)估日志記錄器是否啟用的成本良哲,一次是在debugEnabled
盛卡,第二次是在debug
,在實(shí)踐中筑凫,這種開銷是微不足道的滑沧,因?yàn)樵u(píng)估一個(gè)日志記錄器所需時(shí)間相對(duì)于實(shí)際記錄一個(gè)請(qǐng)求所需的時(shí)間不到1%。
推薦寫法
存在一種基于消息格式的替代方法巍实。假設(shè)entry
是一個(gè)對(duì)象嚎货,可以這樣寫:
Object entry = new SomeObject();
logger.debug("The entry is {}.", entry);
只有在評(píng)估是否進(jìn)行日志記錄之后,并且只有在決定記錄日志的情況下蔫浆,日志程序才會(huì)實(shí)現(xiàn)將消息格式化,并用條目的字符串值替換“{}”姐叁。換句話說瓦盛,當(dāng)禁用 log 語句時(shí),這種寫法不會(huì)產(chǎn)生參數(shù)構(gòu)造的成本外潜。
下面兩行代碼將產(chǎn)生完全相同的輸出原环。然而,在禁用日志語句的情況下处窥,第二種變體的性能至少比第一種變體好30倍嘱吗。
logger.debug("The new entry is "+entry+".");
logger.debug("The new entry is {}.", entry);
還有一種雙參數(shù)變體。例如,你可以這樣寫:
logger.debug("The new entry is {}. It replaces {}.", entry, oldEntry);
如果需要傳遞三個(gè)或多個(gè)參數(shù)谒麦,還可以使用Object[]變體俄讹。例如,你可以這樣寫:
Object[] paramArray = {newVal, below, above};
logger.debug("Value {} was inserted between {} and {}.", paramArray);
底層原理
在介紹了基本的logback
組件之后绕德,現(xiàn)在可以描述當(dāng)用戶調(diào)用日志程序的打印方法時(shí)患膛,logback
框架所采取的步驟。現(xiàn)在讓我們分析用戶調(diào)用名為com.wombat
的日志記錄器的info()
方法時(shí)耻蛇,logback
所采取的步驟踪蹬。
1. 獲得過濾器鏈決策
如果存在,則調(diào)用TurboFilter
鏈臣咖。Turbo 過濾器可以設(shè)置上下文范圍的閾值跃捣,或者根據(jù)與每個(gè)日志請(qǐng)求關(guān)聯(lián)的標(biāo)記、級(jí)別夺蛇、日志記錄器疚漆、消息或可拋出性等信息過濾掉某些事件。如果過濾器鏈的響應(yīng)是拒絕FilterReply.DENY
蚊惯,則日志請(qǐng)求將被刪除愿卸。如果是中性FilterReply.NEUTRAL
,然后我們繼續(xù)下一步截型,即第2步趴荸。如果是接受FilterReply.ACCEPT
,我們跳過下一步宦焦,直接跳到步驟3发钝。
2. 應(yīng)用基本的選擇規(guī)則
在此步驟中,logback
將日志記錄器的有效級(jí)別與請(qǐng)求的級(jí)別進(jìn)行比較波闹。如果根據(jù)此測(cè)試禁用日志記錄請(qǐng)求酝豪,那么logback
將刪除該請(qǐng)求,而不進(jìn)行進(jìn)一步處理精堕。否則孵淘,將繼續(xù)下一步。
3. 創(chuàng)建一個(gè) LoggingEvent 對(duì)象
如果請(qǐng)求通過了前面的過濾器歹篓,logback
將創(chuàng)建一個(gè)ch. qs .logback.classic.LoggingEvent
對(duì)象瘫证,該對(duì)象包含請(qǐng)求的所有相關(guān)參數(shù),例如請(qǐng)求的日志記錄器庄撮,請(qǐng)求級(jí)別背捌,消息本身,可能隨請(qǐng)求一起傳遞的異常洞斯、當(dāng)前時(shí)間毡庆、當(dāng)前線程、發(fā)出日志記錄請(qǐng)求的類的各種數(shù)據(jù)以及 MDC。注意么抗,其中一些字段是延遲初始化的毅否,只有在實(shí)際需要時(shí)才會(huì)這樣做。MDC 用于用附加的上下文信息裝飾日志記錄請(qǐng)求乖坠。MDC將在下一章中討論搀突。
4. 調(diào)用 appenders
創(chuàng)建 LoggingEvent
對(duì)象之后,logback
將調(diào)用所有適用的appender
的doAppend()
方法熊泵,即從日志程序上下文中繼承的appender
仰迁。
logback
發(fā)行版附帶的所有附加程序都擴(kuò)展了AppenderBase
抽象類,該類在確保線程安全的同步塊中實(shí)現(xiàn)doAppend
方法顽分。如果存在附加的自定義過濾器徐许,AppenderBase
的doAppend()
方法也能調(diào)用∽湔海可以動(dòng)態(tài)附加到任何附加器的自定義過濾器將在單獨(dú)的一章中介紹雌隅。
5. 格式化輸出
被調(diào)用的附加程序負(fù)責(zé)格式化日志事件。然而缸沃,一些(但不是所有)附加程序?qū)⒏袷交罩臼录娜蝿?wù)委托給了layout
恰起,布局可以格式化LoggingEvent
實(shí)例并以字符串的形式返回結(jié)果。注意趾牧,有些附加程序检盼,如SocketAppender
,不將日志事件轉(zhuǎn)換為字符串翘单,而是序列化它吨枉。因此,它們沒有也不需要布局哄芜。
6. 發(fā)送 LoggingEvent
日志事件完全格式化后貌亭,由每個(gè)附加程序?qū)⑵浒l(fā)送到目的地。下面是一個(gè)序列 UML 圖认臊,展示了所有事情是如何工作的圃庭。