Logback 的架構(gòu)
????????Logback的基本架構(gòu)非常通用杆煞,可以在不同的情況下應(yīng)用喷鸽。目前,logback分為 logback-core、logback-classic 和 logback-access 三個(gè)模塊油够。
????????核心模塊為其他兩個(gè)模塊奠定了基礎(chǔ)质帅。classic 模塊擴(kuò)展了核心模塊鹿驼。classic 模塊相當(dāng)于是 log4j 的一個(gè)改進(jìn)的版本紊婉。Logback classic 實(shí)現(xiàn)了 SLF4J API,因此您可以方便地在 Logback 和其他日志系統(tǒng)德召,如 log4j 或 在JDK1.4中引入的 java.util.logging (JUL) 之間來回切換白魂。第三個(gè)名為 access 的模塊與 Servlet 容器集成,以提供 HTTP 訪問日志功能上岗。另一個(gè)文檔包含 access 模塊文檔福荸。
????????在本文檔的其余部分中,我們將用 “l(fā)ogback” 來指代 logback-classic 模塊肴掷。
Logger, Appender 和 Layout
????????Logback 構(gòu)建在三個(gè)主要類上:Logger敬锐、Appender 和 Layout背传。這三種類型的組件協(xié)同工作,使開發(fā)人員能夠根據(jù)消息類型和級(jí)別記錄消息台夺,并在運(yùn)行時(shí)控制這些消息的格式和報(bào)告位置径玖。
????????Logger 類是 logback-classic 模塊的一部分。另一方面谒养,Appender 和 Layout 接口是 logback-core 的一部分。作為一個(gè)通用模塊明郭,logback-core 沒有 Logger 的概念买窟。
Logger 上下文
????????任何日志 API 相對(duì)于普通的 System.out.println 的首要優(yōu)勢(shì)就是,它能夠禁用某些日志語句薯定,同時(shí)允許其他語句不受阻礙地打印始绍。 此功能假定日志空間,即所有可能的日志語句的空間话侄,根據(jù)開發(fā)人員選擇的一些標(biāo)準(zhǔn)進(jìn)行分類亏推。在 logback-classic 中,這種分類是記錄器固有的一部分年堆。每個(gè)記錄器都連接到一個(gè)LoggerContext吞杭,該上下文負(fù)責(zé)制造記錄器并將它們排列成樹狀層次結(jié)構(gòu)。
????????記錄器是命名實(shí)體变丧。它們的名稱區(qū)分大小寫芽狗,并遵循分層命名規(guī)則:
命名層次結(jié)構(gòu)
如果一個(gè)記錄器的名稱后跟一個(gè)點(diǎn)是后代記錄器名稱的前綴,則該記錄器被稱為另一個(gè)記錄器的祖先痒蓬。如果記錄器本身和后代記錄器之間沒有祖先童擎,則稱記錄器為子記錄器的父記錄器。
????????例如攻晒,名為 "com.foo" 的記錄器是名為 "com.foo.Bar" 記錄器的父級(jí)顾复。 類似地, "java" 是 "java.util" 的父鲁捏,并且是 "java.util.Vector" 的祖先芯砸。 大多數(shù)開發(fā)人員都應(yīng)該熟悉這種命名方案。
????????根記錄器位于記錄器層次結(jié)構(gòu)的頂部给梅。它是例外的乙嘀,因?yàn)樗敲恳粋€(gè)層次的一部分,在其成立之初破喻。與每個(gè)記錄器一樣虎谢,可以按其名稱檢索,如下所示:
Logger rootLogger = LoggerFactory.getLogger(org.slf4j.Logger.ROOT_LOGGER_NAME);
????????所有其他記錄器也是通過 org.slf4j.LoggerFactory 類中靜態(tài)方法 getLogger
來獲取的曹质。此方法將所需記錄器的名稱作為參數(shù)婴噩。下面列出了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);
}
有效等級(jí)又稱等級(jí)繼承
????????Logger
可以分配等級(jí)。有一系列的級(jí)別(TRACE
几莽、DEBUG
迅办、INFO
、WARN
和 ERROR
)已經(jīng)定義在 ch.qos.logback.classic.Level
類里面了章蚣。注意站欺,在 Logback 中,Level
類是 final 修飾符修飾的類纤垂,它不能被繼承矾策,也就是說他不能有子類,因?yàn)橛幸粋€(gè)擴(kuò)展更好的方式峭沦,就是使用 Marker
對(duì)象贾虽,它更加的靈活,所以不需要使用 Level
類來擴(kuò)展吼鱼。
????????如果我們沒有為一個(gè) logger
對(duì)象分配日志等級(jí)蓬豁,它會(huì)從最近的一個(gè)父 logger
繼承。請(qǐng)看下面的解釋:
給定記錄器 L 的有效級(jí)別等于其層次結(jié)構(gòu)中的第一個(gè)非空級(jí)別菇肃,從 L 本身開始地粪,在層次結(jié)構(gòu)中向上延伸到根記錄器。
????????為了確保所有記錄器最終都可以繼承一個(gè)級(jí)別琐谤,根記錄器總是有一個(gè)指定的級(jí)別驶忌。默認(rèn)情況下,此級(jí)別為DEBUG笑跛。
????????下面是四個(gè)示例付魔,其中包含各種指定的級(jí)別值以及根據(jù)級(jí)別繼承規(guī)則生成的有效(繼承)級(jí)別。
示例 1
記錄器名字 | 分配等級(jí) | 有效等級(jí) |
---|---|---|
root | DEBUG | DEBUG |
X | none | DEBUG |
X.Y | none | DEBUG |
X.Y.Z | none | DEBUG |
????????在上面的示例1中飞蹂,只有根記錄器被分配了級(jí)別几苍,其他的記錄器,比如 X陈哑、X.Y妻坝、X.Y.Z,這三個(gè)記錄器都會(huì)繼承根記錄器的級(jí)別惊窖。
示例 2
記錄器名字 | 分配等級(jí) | 有效等級(jí) |
---|---|---|
root | ERROR | ERROR |
X | INFO | INFO |
X.Y | DEBUG | DEBUG |
X.Y.Z | WARN | WARN |
????????在上面的示例2中刽宪,所有記錄器都有一個(gè)指定的級(jí)別值。等級(jí)繼承不起作用界酒。
示例 3
記錄器名字 | 分配等級(jí) | 有效等級(jí) |
---|---|---|
root | DEBUG | DEBUG |
X | INFO | INFO |
X.Y | none | INFO |
X.Y.Z | ERROR | ERROR |
????????在上面的示例 3 中圣拄,分別為記錄器 root
、X
和 X.Y.Z
分配了級(jí)別 DEBUG
毁欣、INFO
和 ERROR
庇谆。記錄器 X.Y
從其父級(jí) X
繼承其級(jí)別值岳掐。
示例 4
記錄器名字 | 分配等級(jí) | 有效等級(jí) |
---|---|---|
root | DEBUG | DEBUG |
X | INFO | INFO |
X.Y | none | INFO |
X.Y.Z | none | INFO |
????????在上面的示例 4 中,分別為記錄器 root
和 X
分配了級(jí)別 DEBUG
和 INFO
饭耳。記錄器 X.Y
和 X.Y.Z
從最近的父級(jí) X
繼承其級(jí)別值串述,后者具有指定的級(jí)別。
打印方法和基本選擇原則
????????根據(jù)定義寞肖,打印方法確定日志記錄請(qǐng)求的級(jí)別纲酗。例如如果 L
是一個(gè)記錄器實(shí)例對(duì)象,那么 L.info("..")
是一個(gè) INFO 級(jí)別的日志打印語句新蟆。也就是說觅赊,我們調(diào)用的方法就決定了我們?nèi)罩据敵龅募?jí)別。
????????當(dāng)我們調(diào)用記錄器實(shí)例對(duì)象的方法的時(shí)候栅葡,如果這個(gè)方法的等級(jí)高于我們?cè)O(shè)定的級(jí)別那么就會(huì)輸出茉兰,否則不會(huì)輸出日志尤泽。如前所述欣簇,沒有指定級(jí)別的記錄器將從其最近的祖先繼承一個(gè)級(jí)別。這條規(guī)則總結(jié)如下坯约。
基本選擇規(guī)則
如果p>=q熊咽,則向有效級(jí)別為q的記錄器發(fā)出的級(jí)別為p的日志請(qǐng)求將被啟用。
????????其實(shí)說白了闹丐,就是我們?cè)谡?qǐng)求日志記錄的時(shí)候横殴,請(qǐng)求的方法的級(jí)別要比我們?cè)O(shè)置的或者繼承的級(jí)別更高才行,不然日志是不會(huì)打印的卿拴。下面就會(huì)介紹如何來比較日志的等級(jí)衫仑。
????????這條規(guī)則是 logback 的核心。級(jí)別的順序如下:TRACE < DEBUG < INFO < WARN < ERROR
堕花。
????????也就是說文狱,ERROR
比 WARN
的級(jí)別高,依次類推缘挽,所以我們?cè)谡{(diào)用方法的時(shí)候瞄崇,如果設(shè)定的有效級(jí)別或繼承的有效級(jí)別是 WARN
,那么我們?cè)谡{(diào)用方法的時(shí)候調(diào)用 warn
或者 error
都會(huì)打印壕曼,但是其他級(jí)別是不會(huì)打印的苏研。
????????以更形象的方式,下面是選擇規(guī)則的工作原理腮郊。在下表中摹蘑,垂直標(biāo)頭顯示日志請(qǐng)求的級(jí)別,由p指定轧飞,而水平標(biāo)頭顯示記錄器的有效級(jí)別纹蝴,由q指定庄萎。行(級(jí)別請(qǐng)求)和列(有效級(jí)別)的交集是基本選擇規(guī)則產(chǎn)生的布爾值。
請(qǐng)求等級(jí) p | 有效等級(jí) q | 有效等級(jí) q | 有效等級(jí) q | 有效等級(jí) q | 有效等級(jí) q | 有效等級(jí) q |
---|---|---|---|---|---|---|
TRACE | TRACE | DEBUG | INFO | WARN | ERROR | OFF |
TRACE | YES | YES | NO | NO | NO | NO |
DEBUG | YES | YES | YES | NO | NO | NO |
INFO | YES | YES | YES | YES | NO | NO |
WARN | YES | YES | YES | YES | YES | NO |
ERROR | YES | YES | YES | YES | YES | YES |
????????下面是一個(gè)基本選擇規(guī)則的示例塘安。
package chapters.introduction;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import ch.qos.logback.classic.Level;
public class HelloWorld4 {
public static void main(String[] args) {
// 獲得一個(gè)名字為 "com.foo" 的記錄器實(shí)例糠涛。
// 讓我們將類 Logger 的包一起指定,以便我們可以更方便的設(shè)置它的級(jí)別
ch.qos.logback.classic.Logger logger = (ch.qos.logback.classic.Logger) LoggerFactory.getLogger("com.foo");
// 設(shè)置級(jí)別為 INFO兼犯。 setLevel() 方法需要一個(gè) logback 記錄器對(duì)象
logger.setLevel(Level.INFO);
Logger barlogger = LoggerFactory.getLogger("com.foo.Bar");
// 這個(gè)日志會(huì)打印忍捡,因?yàn)?WARN >= INFO
logger.warn("Low fuel level.");
// 這個(gè)打印日志的請(qǐng)求不會(huì)被執(zhí)行,因?yàn)?DEBUG < INFO.
logger.debug("Starting search for nearest gas station.");
// 記錄器實(shí)例對(duì)象 barlogger, 它的名字是 "com.foo.Bar",將會(huì)從 "com.foo" 繼承日志等級(jí)切黔。
// 因此砸脊,接下來的打印日志請(qǐng)求將會(huì)成功執(zhí)行,因?yàn)?INFO >= INFO.
barlogger.info("Located nearest gas station.");
// 這個(gè)打印日志的請(qǐng)求就不會(huì)打印出日志纬霞,因?yàn)?DEBUG < INFO.
barlogger.debug("Exiting gas station search");
}
}
輸出:
17:58:57.016 [main] WARN com.foo - Low fuel level.
17:58:57.020 [main] INFO com.foo.Bar - Located nearest gas station.
檢索記錄器
????????調(diào)用 LoggerFactory.getLogger
方法凌埂,如果傳入其中的參數(shù)相同,那么返回的記錄器對(duì)象的引用是完全相同的诗芜。
例如瞳抓,在
package chapters.introduction;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class HelloWorld5 {
public static void main(String[] args) {
Logger x = LoggerFactory.getLogger("wombat");
Logger y = LoggerFactory.getLogger("wombat");
x.info(x == y ? "true" : "false");
y.info(x == y ? "true" : "false");
}
}
輸出:
17:57:56.528 [main] INFO wombat - true
17:57:56.533 [main] INFO wombat - true
????????通過上面的例子,我們可以看出伏恐,x
和 y
指向的是相同的記錄器對(duì)象孩哑。
????????因此,可以配置一個(gè)記錄器翠桦,然后在代碼中的其他地方檢索相同的實(shí)例横蜒,而無需傳遞引用。這里跟我們現(xiàn)實(shí)世界里的父子關(guān)系有一點(diǎn)不一樣销凑,我們現(xiàn)實(shí)生活中是應(yīng)該先有父母才有子女的丛晌,但是 Logback 是不需要關(guān)注他們的先后順序的,也就是說斗幼,他們的父子關(guān)系與先后創(chuàng)建的關(guān)系是無關(guān)的澎蛛。需要特別說明的是,一個(gè) “父級(jí)” 的記錄器將查找并鏈接到他的子記錄器上孟岛,即使父記錄器是在子記錄器之后實(shí)例化的瓶竭。
????????logback 環(huán)境的配置通常在應(yīng)用程序初始化時(shí)完成。首選方法是讀取配置文件渠羞。這一方法將很快討論斤贰。
????????Logback 對(duì)記錄器命名非常容易,只需要我們?cè)诿總€(gè)類里面是一個(gè)記錄器就可以了次询,在傳入靜態(tài)方法獲取記錄器對(duì)象的時(shí)候傳入的是類的權(quán)限定名荧恍,也就是包括了自己的包名和類名組合在一起。這是一種定義記錄器特別行之有效的方法。由于日志輸出帶有生成日志的記錄器的名稱送巡,因此這種命名策略可以很容易地識(shí)別日志消費(fèi)的來源摹菠。然而,這只是一種命名記錄器的可能(盡管很常見)策略骗爆。Logback 并不會(huì)限制記錄器的集合次氨,開發(fā)人員可以非常隨意的命名記錄器。
????????但是摘投,到目前為止煮寡,我們最好還是使用全限定類名來命名記錄器比較好。
Appender (附加器)和 Layout(布局)
????????上面我們看到了犀呼,logback 會(huì)根據(jù)我們請(qǐng)求打印日志的不通級(jí)別來有選擇的打印一些消息幸撕,這只是 Logback 眾多能力中的一個(gè)。Logback 允許日志請(qǐng)求打印到多個(gè)目的地外臂。在 Logback 中坐儿,輸出目的地成為 appender。目前宋光,有控制臺(tái)的 appender貌矿,文件的 appender,遠(yuǎn)程套接字服務(wù)的 appender跃须,MySQL站叼、PostgreSQL娃兽、Oracle和其他數(shù)據(jù)庫(kù)的 appender菇民,JMS 和遠(yuǎn)程 UNIX Syslog 守護(hù)進(jìn)程的 appender。
????????很多 appender(附加器) 可以附加在一個(gè) logger(記錄器)上面投储。
???????? addAppender
方法將附加器添加到指定的記錄器中第练。每次我們調(diào)用記錄器的方法發(fā)送打印日志的請(qǐng)求的時(shí)候,這些請(qǐng)求都會(huì)轉(zhuǎn)發(fā)到這個(gè)記錄器的所有附加器中玛荞,以及層級(jí)結(jié)構(gòu)中更高級(jí)的附加器娇掏。換句話說,附加器是從記錄器層次結(jié)構(gòu)附加繼承的勋眯。例如婴梧,如果控制臺(tái)的附加器添加到了根記錄器中,那么所有啟用的日志請(qǐng)求都會(huì)至少打印到控制臺(tái)上客蹋。如果另外一個(gè)文件附加器被添加到一個(gè)記錄器中塞蹭,比如說 L,那么對(duì)于 L 和 L 的子級(jí)的記錄器中發(fā)送的日志請(qǐng)求都將打印在一個(gè)文件和控制臺(tái)上讶坯。通過將記錄器的 additivity 標(biāo)志設(shè)置為 false番电,可以覆蓋此默認(rèn)行為,從而使附加器不再是繼承父級(jí)的附加器,并將他們疊加的漱办。
????????附錄可加性的規(guī)則總結(jié)如下这刷。
附加器的疊加性
????????記錄器 L 的日志語句輸出將轉(zhuǎn)到 L 記錄器和它的父級(jí)記錄器中的所有附加器中。這就是術(shù)語 “附加器的疊加性” 的含義娩井。
????????然而暇屋,如果是記錄器 L 的祖先,比如我們管它叫 P洞辣,P 這個(gè)祖先已經(jīng)將疊加性這個(gè)屬性設(shè)置為了 false 了率碾,那么 L 在輸出日志的時(shí)候,會(huì)輸出所有 L 本身自己定義的附加器屋彪,當(dāng)然這是肯定的所宰,然后還有它的父級(jí)記錄器 P 所定義的附加器,但是畜挥,P 的所有父級(jí)記錄器所定義的附加器都不會(huì)進(jìn)行輸出仔粥。
????????默認(rèn)情況下,記錄器的疊加性標(biāo)志都會(huì)設(shè)置為 true蟹但。
????????下面的表格展示了一個(gè)示例:
記錄器名稱 | 其上的附加器 | 疊加性標(biāo)志 | 輸出目標(biāo) | 說明 |
---|---|---|---|---|
root | A1 | 不適用 | A1 | 因?yàn)楦涗浧魑挥谟涗浧鲗蛹?jí)結(jié)構(gòu)的頂部躯泰,所以疊加性標(biāo)志不適用于它。 |
x | A-x1, A-x2 | true | A1, A-x1, A-x2 | 附加器包括了自身 "x" 上的附加器和根附加器华糖。 |
x.y | none | true | A1, A-x1, A-x2 | 其上的附加器包括了 "x" 的附加器還有根的附加器麦向。 |
x.y.z | A-xyz1 | true | A1, A-x1, A-x2, A-xyz1 | 其上的附加器包括了自身記錄器 "x.y.z" 上的附加器還有 "x" 和根記錄器上的附加器。 |
security | A-sec | false | A-sec | 由于記錄器的疊加性標(biāo)志設(shè)置為 false 客叉,因此沒有附加器的疊加诵竭。所以它只使用自己的附加器 A-sec。 |
security.access | none | true | A-sec | 只有記錄器 "security" 的附加器兼搏,因?yàn)?"security" 中的疊加性標(biāo)志設(shè)置為 false 卵慰,而且它自己本身也沒有附加器,所以就只能繼承父記錄器的附加器佛呻。 |
????????通常裳朋,用戶不僅希望自定義輸出目的地,還希望自定義輸出格式吓著。這是通過將布局與追加器關(guān)聯(lián)來實(shí)現(xiàn)的鲤嫡。布局負(fù)責(zé)根據(jù)用戶的意愿格式化日志記錄請(qǐng)求,而appender負(fù)責(zé)將格式化輸出發(fā)送到目的地绑莺。PatternLayout
是標(biāo)準(zhǔn) logback 的一部分暖眼,它允許用戶根據(jù)與 C 語言 printf
函數(shù)類似的轉(zhuǎn)換模式指定輸出格式。
????????例如紊撕,轉(zhuǎn)換模式為 "%-4relative [%thread] %-5level %logger{32} - %msg%n" 的 PatternLayout 將輸出類似于:
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 模塊中的記錄器實(shí)現(xiàn)了 SLF4J 的 Logger 接口,這些打印方法允許多個(gè)參數(shù)笼才。這些打印方法變體主要是為了提高性能漱受,同時(shí)盡量減少對(duì)代碼可讀性的影響。
????????比如說有一個(gè) Logger 類的對(duì)象 logger
骡送,也就是我們所說的記錄器昂羡,可以有如下的調(diào)用形式:
logger.debug("Entry number: " + i + " is " + String.valueOf(entry[i]));
????????產(chǎn)生構(gòu)造消息參數(shù)的成本,即將整數(shù) i
和entry[i]
轉(zhuǎn)換為字符串摔踱,并連接中間字符串虐先。這與是否記錄消息無關(guān)。
????????避免日志損耗生產(chǎn)環(huán)境的性能派敷,可以先判斷了是否開啟了 debug 蛹批,也就是說,我們這個(gè) 記錄器是否可以使用 debug 級(jí)別進(jìn)行日志的輸出篮愉「郑看下面的例子:
if(logger.isDebugEnabled()) {
logger.debug("Entry number: " + i + " is " + String.valueOf(entry[i]));
}
????????如果我們需要對(duì)記錄器禁用 debug 模式,那么就不會(huì)產(chǎn)生性能的損耗试躏。但是如果我們開啟了記錄器的調(diào)試模式猪勇,那么我們就需要花費(fèi)兩次的時(shí)間,一次是在調(diào)用 debugEnabled
的時(shí)候颠蕴,另一次是在 實(shí)際調(diào)用 debug
函數(shù)的時(shí)候泣刹。在實(shí)際的生產(chǎn)環(huán)境或者測(cè)試環(huán)境中,經(jīng)過測(cè)試裁替,前期判斷是否啟用 debug 模式的判斷其實(shí)損耗不了多少性能项玛,它花費(fèi)的性能還不到實(shí)際日志調(diào)用的 1%貌笨,所以調(diào)試代碼用到的日志最好還是先判斷一下比較好弱判。
更好的選擇
????????上面的日志輸出中,我們有另一種比較好的輸出日志的方式锥惋。假設(shè) entry
是一個(gè)對(duì)象昌腰,您可以編寫:
Object entry = new SomeObject();
logger.debug("The entry is {}.", entry);
????????在輸出日志之前,我們需要根據(jù)目前調(diào)用日志的級(jí)別膀跌,還有記錄器可以輸出的級(jí)別的設(shè)置來判定目前的日志是否需要輸出遭商,如果需要輸出該條日志,記錄器才會(huì)格式化消息并且用 entry
對(duì)象來替換符號(hào) '{}'捅伤。也就是說劫流,如果判斷當(dāng)前要輸出的級(jí)別比我們配置的輸出級(jí)別低的時(shí)候,這條語句中的消息是不會(huì)去構(gòu)造的,也就是不會(huì)產(chǎn)生任何的性能上的開銷祠汇。
????????下面的兩行代碼將會(huì)產(chǎn)生完全相同的輸出仍秤。但是,在禁用日志語句輸出的情況下可很,第二種的輸出方式比第一種至少要好30倍诗力。
logger.debug("The new entry is "+entry+".");
logger.debug("The new entry is {}.", entry);
????????在輸出日志的時(shí)候,也可以輸出兩個(gè)的變量我抠,具體可以查看 debug 的 API苇本,我們可以使用如下的方式寫代碼:
logger.debug("The new entry is {}. It replaces {}.", entry, oldEntry);
????????如果需要傳遞三個(gè)或更多的參數(shù)的時(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. 獲得過濾器鏈決策
????????假設(shè)我們現(xiàn)有已經(jīng)使用 LoggerFactory
的工廠方法生成了記錄器的對(duì)象啥么,并且使用這個(gè)記錄器請(qǐng)求了一條日志,那么它就會(huì)調(diào)用 TurboFilter
鏈贰逾。如下就是 TurboFilter
的代碼:
/**
* Logback: the reliable, generic, fast and flexible logging framework.
* Copyright (C) 1999-2015, QOS.ch. All rights reserved.
*
* This program and the accompanying materials are dual-licensed under
* either the terms of the Eclipse Public License v1.0 as published by
* the Eclipse Foundation
*
* or (per the licensee's choosing)
*
* under the terms of the GNU Lesser General Public License version 2.1
* as published by the Free Software Foundation.
*/
package ch.qos.logback.classic.turbo;
import org.slf4j.Marker;
import ch.qos.logback.classic.Level;
import ch.qos.logback.classic.Logger;
import ch.qos.logback.core.spi.ContextAwareBase;
import ch.qos.logback.core.spi.FilterReply;
import ch.qos.logback.core.spi.LifeCycle;
/**
* TurboFilter is a specialized filter with a decide method that takes a bunch
* of parameters instead of a single event object. The latter is cleaner but
* the first is much more performant.
* <p>
* For more information about turbo filters, please refer to the online manual at
* http://logback.qos.ch/manual/filters.html#TurboFilter
*
* @author Ceki Gulcu
*/
public abstract class TurboFilter extends ContextAwareBase implements LifeCycle {
private String name;
boolean start = false;
/**
* Make a decision based on the multiple parameters passed as arguments.
* The returned value should be one of <code>{@link FilterReply#DENY}</code>,
* <code>{@link FilterReply#NEUTRAL}</code>, or <code>{@link FilterReply#ACCEPT}</code>.
* @param marker
* @param logger
* @param level
* @param format
* @param params
* @param t
* @return
*/
public abstract FilterReply decide(Marker marker, Logger logger, Level level, String format, Object[] params, Throwable t);
public void start() {
this.start = true;
}
public boolean isStarted() {
return this.start;
}
public void stop() {
this.start = false;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
????????TurboFilter
可以設(shè)置上下文范圍的閾值悬荣,或者根據(jù)與每個(gè)日志記錄請(qǐng)求相關(guān)聯(lián)的信息(如 Marker
、Level
疙剑、Logger
氯迂、消息或 Throwable
)過濾掉某些事件。如果過濾鏈的回復(fù)是 FilterReply.DENY
言缤,則刪除日志記錄請(qǐng)求嚼蚀。如果是 FilterReply.NEUTRAL
的話,那么就進(jìn)行下一步管挟,即第2步轿曙。如果答復(fù)是 FilterReply.ACCEPT
的話,我們跳過下一步僻孝,直接跳到第3步导帝。
2. 應(yīng)用 基本選擇規(guī)則
????????在這步中,logback 將當(dāng)前記錄器的有效等級(jí)與請(qǐng)求的等級(jí)之間進(jìn)行對(duì)比穿铆。如果說根據(jù)這次比較您单,日志請(qǐng)求的等級(jí)比我們?cè)O(shè)定的等級(jí)要低的話,那么就會(huì)禁用該日志請(qǐng)求荞雏,并且 logback 馬上會(huì)放棄這次請(qǐng)求虐秦,不進(jìn)行任何處理平酿。如果通過了比較,也就是說我們請(qǐng)求的日志等級(jí)大于或者等級(jí)我們?nèi)罩居涗浧髟O(shè)定的級(jí)別的話悦陋,就會(huì)進(jìn)入下一步染服。
3. 創(chuàng)建 LoggingEvent
對(duì)象
????????如果這次日志的請(qǐng)求通過上面兩步的過濾,還能走到這里的話叨恨,logback 就會(huì)創(chuàng)建 ch.qos.logback.classic.LoggingEvent
對(duì)象柳刮。該對(duì)象包含請(qǐng)求的所有相關(guān)參數(shù),例如請(qǐng)求的記錄器痒钝、請(qǐng)求級(jí)別秉颗、消息本身、可能隨請(qǐng)求一起傳遞的異常送矩、當(dāng)前時(shí)間蚕甥,當(dāng)前線程、有關(guān)發(fā)出日志記錄請(qǐng)求的類的各種數(shù)據(jù)以及 MDC
栋荸。請(qǐng)注意菇怀,這些字段中的一些是延遲初始化的,這只是在實(shí)際需要時(shí)才初始化晌块。 MDC
用于用其他上下文信息修飾日志記錄請(qǐng)求爱沟。MDC將在[下一章]中討論。
4. 調(diào)用附加器
????????創(chuàng)建 LoggingEvent
對(duì)象后匆背,logback 會(huì)調(diào)用 doAppend()
方法來將所有適用的附加器(appender)呼伸,這些附加器包括從日志上下文中加載的父級(jí)記錄器中繼承來的附加器。
????????logback 中的所有附加器(appender)都擴(kuò)展了抽象類 AppenderBase
钝尸,這個(gè)類實(shí)現(xiàn)了 doAppend
方法括享,這個(gè) doAppend
方法它是用 synchronized
關(guān)鍵字修飾的,所以執(zhí)行這個(gè) doAppend
方法是線程安全的珍促。代碼如下所示:
abstract public class AppenderBase<E> extends ContextAwareBase implements Appender<E> {
public synchronized void doAppend(E eventObject) {
}
}
????????上面的代碼只是簡(jiǎn)單的部分代碼铃辖,省略了很多冗余的部分。
????????如果我們自己定義了一些過濾器猪叙,并附加到這個(gè)附加器(appender)上的話娇斩,抽象類 AppenderBase
中的 doAppend
方法也會(huì)調(diào)用這些自定義的過濾器,用于過濾附加器(appender)沐悦。代碼如下所示:
public synchronized void doAppend(E eventObject) {
if (getFilterChainDecision(eventObject) == FilterReply.DENY) {
return;
}
}
????????上面的代碼同樣只是展示了部分代碼成洗。
????????自定義的過濾器可以動(dòng)態(tài)地加到任何的附加器(appender)上。詳細(xì)說明我們可以在 separate chapter 中查看藏否。
5. 格式化輸出
????????logback 調(diào)用附加器(appender)來格式化日志的請(qǐng)求。但是有一些(并不是所有的)附加器會(huì)將格式化日志的任務(wù)委托給布局(layout)來執(zhí)行充包。布局(layout)格式化 LoggingEvent
的實(shí)例對(duì)象并且將結(jié)果作為字符串返回副签。請(qǐng)注意遥椿,某些附加器(appender),比如 SocketAppender
淆储,它并不會(huì)將日志事件轉(zhuǎn)換為字符串冠场,而是將其序列化。因此本砰,它們沒有也不需要布局(layout)碴裙。
6. 發(fā)送 LoggingEvent
日志事件完全格式化后,每個(gè)appender都會(huì)將其發(fā)送到目的地点额。
????????下面是一個(gè) UML 的時(shí)序圖舔株,它展示了 logback是如何工作的。點(diǎn)擊可以放大查看还棱。
性能
反對(duì)伐木的一個(gè)經(jīng)常被引用的論點(diǎn)是它的計(jì)算成本载慈。這是一個(gè)合理的問題,因?yàn)榧词故侵械纫?guī)模的應(yīng)用程序也可以生成數(shù)千個(gè)日志請(qǐng)求珍手。我們的大部分開發(fā)工作都花在測(cè)量和調(diào)整logback的性能上办铡。除了這些努力之外,用戶還應(yīng)該意識(shí)到以下性能問題琳要。
1. 完全關(guān)閉日志記錄時(shí)的日志記錄性能
您可以通過將根記錄器的級(jí)別設(shè)置為 Level.OFF
寡具,可能的最高級(jí)別。當(dāng)日志記錄完全關(guān)閉時(shí)稚补,日志請(qǐng)求的開銷包括方法調(diào)用和整數(shù)比較晒杈。在3.2Ghz的奔騰D機(jī)器上,這一成本通常在20納秒左右孔厉。
然而拯钻,任何方法調(diào)用都涉及參數(shù)構(gòu)造的“隱藏”成本。例如撰豺,對(duì)于某些記錄器x寫入粪般,
x.debug("Entry number: " + i + "is " + entry[i]);
產(chǎn)生構(gòu)造消息參數(shù)的成本,即將整數(shù)“i”和“entry[i]”轉(zhuǎn)換為字符串污桦,并連接中間字符串亩歹,而不管消息是否將被記錄。
參數(shù)構(gòu)造的成本可能相當(dāng)高凡橱,并且取決于所涉及參數(shù)的大小小作。為了避免參數(shù)構(gòu)造的成本,您可以利用SLF4J的參數(shù)化日志:
x.debug("Entry number: {} is {}", i, entry[i]);
這種變體不會(huì)產(chǎn)生參數(shù)構(gòu)造的成本稼钩。與以前對(duì) debug()
方法的調(diào)用相比顾稀,它將快很多。只有將日志記錄請(qǐng)求發(fā)送到附加的附加程序時(shí)坝撑,才會(huì)格式化消息静秆。此外粮揉,格式化消息的組件得到了高度優(yōu)化。
盡管有上述規(guī)定抚笔,將日志語句置于緊密循環(huán)中(即非常頻繁地調(diào)用代碼)是一種兩敗俱傷的建議扶认,可能會(huì)導(dǎo)致性能下降。即使關(guān)閉了日志記錄殊橙,緊密循環(huán)中的日志記錄也會(huì)降低應(yīng)用程序的速度辐宾,如果打開日志記錄,則會(huì)生成大量(因此是無用的)輸出膨蛮。
2. 在日志記錄打開時(shí)決定是否記錄的性能叠纹。
在logback中,不需要遍歷logger層次結(jié)構(gòu)鸽疾。記錄器在創(chuàng)建時(shí)知道它的有效級(jí)別(即吊洼,一旦考慮到級(jí)別繼承,它的級(jí)別)制肮。如果父記錄器的級(jí)別發(fā)生更改冒窍,則會(huì)聯(lián)系所有子記錄器以注意更改。因此豺鼻,在基于有效級(jí)別接受或拒絕請(qǐng)求之前综液,記錄器可以做出準(zhǔn)即時(shí)決策,而無需咨詢其祖先儒飒。
3. 實(shí)際記錄(格式化并寫入輸出設(shè)備)
這是格式化日志輸出并將其發(fā)送到目標(biāo)的成本谬莹。在這里,我們?cè)僖淮握J(rèn)真地努力使布局(格式化程序)盡可能快地執(zhí)行桩了。追加者也是如此附帽。當(dāng)記錄到本地計(jì)算機(jī)上的文件時(shí),實(shí)際記錄的典型成本約為9到12微秒井誉。當(dāng)?shù)卿浀竭h(yuǎn)程服務(wù)器上的數(shù)據(jù)庫(kù)時(shí)蕉扮,時(shí)間會(huì)長(zhǎng)達(dá)幾毫秒。
盡管功能豐富颗圣,logback最重要的設(shè)計(jì)目標(biāo)之一是執(zhí)行速度喳钟,這是僅次于可靠性的要求。一些logback組件已被重寫多次以提高性能在岂。