第二章 架構(gòu)

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迅办、INFOWARNERROR)已經(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 中圣拄,分別為記錄器 rootXX.Y.Z 分配了級(jí)別 DEBUG毁欣、INFOERROR庇谆。記錄器 X.Y 從其父級(jí) X 繼承其級(jí)別值岳掐。

示例 4

記錄器名字 分配等級(jí) 有效等級(jí)
root DEBUG DEBUG
X INFO INFO
X.Y none INFO
X.Y.Z none INFO

????????在上面的示例 4 中,分別為記錄器 rootX 分配了級(jí)別 DEBUGINFO饭耳。記錄器 X.YX.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堕花。

????????也就是說文狱,ERRORWARN 的級(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

????????通過上面的例子,我們可以看出伏恐,xy 指向的是相同的記錄器對(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ì)于 LL 的子級(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ù) ientry[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)的信息(如 MarkerLevel疙剑、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組件已被重寫多次以提高性能在岂。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末奔则,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子蔽午,更是在濱河造成了極大的恐慌易茬,老刑警劉巖,帶你破解...
    沈念sama閱讀 219,366評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件祠丝,死亡現(xiàn)場(chǎng)離奇詭異疾呻,居然都是意外死亡除嘹,警方通過查閱死者的電腦和手機(jī)写半,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,521評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門岸蜗,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人叠蝇,你說我怎么就攤上這事璃岳。” “怎么了悔捶?”我有些...
    開封第一講書人閱讀 165,689評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵铃慷,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我蜕该,道長(zhǎng)犁柜,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,925評(píng)論 1 295
  • 正文 為了忘掉前任堂淡,我火速辦了婚禮馋缅,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘绢淀。我一直安慰自己萤悴,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,942評(píng)論 6 392
  • 文/花漫 我一把揭開白布皆的。 她就那樣靜靜地躺著覆履,像睡著了一般灵妨。 火紅的嫁衣襯著肌膚如雪原在。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,727評(píng)論 1 305
  • 那天抄腔,我揣著相機(jī)與錄音楞抡,去河邊找鬼伟众。 笑死,一個(gè)胖子當(dāng)著我的面吹牛拌倍,可吹牛的內(nèi)容都是我干的赂鲤。 我是一名探鬼主播,決...
    沈念sama閱讀 40,447評(píng)論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼柱恤,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼数初!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起梗顺,我...
    開封第一講書人閱讀 39,349評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤泡孩,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后寺谤,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體仑鸥,經(jīng)...
    沈念sama閱讀 45,820評(píng)論 1 317
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡吮播,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,990評(píng)論 3 337
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了眼俊。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片意狠。...
    茶點(diǎn)故事閱讀 40,127評(píng)論 1 351
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖疮胖,靈堂內(nèi)的尸體忽然破棺而出环戈,到底是詐尸還是另有隱情,我是刑警寧澤澎灸,帶...
    沈念sama閱讀 35,812評(píng)論 5 346
  • 正文 年R本政府宣布院塞,位于F島的核電站,受9級(jí)特大地震影響性昭,放射性物質(zhì)發(fā)生泄漏拦止。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,471評(píng)論 3 331
  • 文/蒙蒙 一糜颠、第九天 我趴在偏房一處隱蔽的房頂上張望汹族。 院中可真熱鬧,春花似錦括蝠、人聲如沸鞠抑。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,017評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽搁拙。三九已至,卻和暖如春法绵,著一層夾襖步出監(jiān)牢的瞬間箕速,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,142評(píng)論 1 272
  • 我被黑心中介騙來泰國(guó)打工朋譬, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留盐茎,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,388評(píng)論 3 373
  • 正文 我出身青樓徙赢,卻偏偏與公主長(zhǎng)得像字柠,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子狡赐,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,066評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容