本文主要關於logback賬下大將Logger——記錄器。html
chapter 2java
Every single logger is attached to a
LoggerContext
which is responsible for manufacturing loggers as well as arranging them in a tree like hierarchy.
類LoggerContext
負責生成Logger
並將這些Logger
組織在一個樹狀的結構中。api
chapter 2app
Loggers are named entities. Their names are case-sensitive and they follow the hierarchical naming rule:A logger is said to be an ancestor of another logger if its name followed by a dot is a prefix of the descendant logger name. A logger is said to be a parent of a child logger if there are no ancestors between itself and the descendant logger.less
For example, the logger named
"com.foo"
is a parent of the logger named"com.foo.Bar"
. Similarly,"java"
is a parent of"java.util"
and an ancestor of"java.util.Vector"
. This naming scheme should be familiar to most developers.ideThe root logger resides at the top of the logger hierarchy. It is exceptional in that it is part of every hierarchy at its inception. Like every logger, it can be retrieved by its name, as follows:函數
Logger rootLogger = LoggerFactory.getLogger(org.slf4j.Logger.ROOT\_LOGGER\_NAME);
(org.slf4j.Logger.ROOT_LOGGER_NAME)性能All other loggers are also retrieved with the class static
getLogger
method found in the org.slf4j.LoggerFactory class. This method takes the name of the desired logger as a parameter.flex
這裏首先強調了記錄器是有名字的,而後進一步講明瞭上一節提到的LoggerContext
將記錄器安排在一個樹狀的層次結構中是怎麼根據名字去作的。最後說了一下根記錄器如何獲得。ui
chapter 2
Loggers may be assigned levels. The set of possible levels (TRACE, DEBUG, INFO, WARN and ERROR) are defined in thech.qos.logback.classic.Level
class. Note that in logback, theLevel
class is final and cannot be sub-classed, as a much more flexible approach exists in the form ofMarker
objects.If a given logger is not assigned a level, then it inherits one from its closest ancestor with an assigned level. More formally:
The effective level for a given logger
L
, is equal to the first non-null level in its hierarchy, starting atL
itself and proceeding upwards in the hierarchy towards the root logger.To ensure that all loggers can eventually inherit a level, the root logger always has an assigned level. By default, this level is DEBUG.
本段說明 logback 會對記錄器(Logger
類的實例)劃分級別。若是沒有特別指定,則會讓其不斷向父輩詢問,直至遇到一個non-null
級別的記錄器後繼承它的。爲了確保一個沒有指定級別的記錄器必定會有級別,根記錄器默認有 DEBUG 的級別而且級別不能置爲 null。
其實寫到這裏仔細一想會以爲奇怪,咱們前面接觸到的例子,不都是一個Logger
類實例L
經過L.info(...)
或者L.debug(...)
啥的來記錄日誌麼。不該該對消息分級,怎麼對記錄器分級了呢?
另外,記錄器的級別要怎麼設置,這裏怎麼沒說呢?
繼續閱讀文檔,可以回答這些問題。
chapter 2
Printing methods and the basic selection ruleBy definition, the printing method determines the level of a logging request. For example, if
L
is a logger instance, then the statementL.info("..")
is a logging statement of level INFO.A logging request is said to beenabled_if its level is higher than or equal to the effective level of its logger. Otherwise, the request is said to be_disabled. As described previously, a logger without an assigned level will inherit one from its nearest ancestor. This rule is summarized below.
Basic Selection Rule:
A log request of level p issued to a logger having an effective level q, is enabled if p >= q.This rule is at the heart of logback. It assumes that levels are ordered as follows:
TRACE < DEBUG < INFO < WARN < ERROR
.
解決了在以前看到的例子中L.info("...")
這樣好像消息自己纔是被分級的,爲什麼上一節說記錄器被分級的疑惑。級別爲q的記錄器只會批准級別大於等於q的記錄請求。
最後給出了日誌等級的前後大小關係爲TRACE < DEBUG < INFO < WARN < ERROR
。
chapter 2
Calling the LoggerFactory.getLogger method with the same name will always return a reference to the exact same logger object.
For example, in
Logger x = LoggerFactory.getLogger("wombat"); Logger y = LoggerFactory.getLogger("wombat");x and y refer to exactly the same logger object.
Thus, it is possible to configure a logger and then to retrieve the same instance somewhere else in the code without passing around references.
說明LoggerFactory.getLogger(param)
中,同樣的 param,返回獲得的對象老是那一個。
In fundamental contradiction to biological parenthood, where parents always precede their children, logback loggers can be created and configured in any order. In particular, a "parent" logger will find and link to its descendants even if it is instantiated after them.Configuration of the logback environment is typically done at application initialization. The preferred way is by reading a configuration file. This approach will be discussed shortly.
指明配置日誌記錄器時的順序不須要像傳統生物學同樣先有祖先再有子代。在運行時,徹底能夠先實例化一個小輩日誌記錄器,再實例化一個祖輩的日誌記錄器。
chapter 2
Logback makes it easy to name loggers by software component. This can be accomplished by instantiating a logger in each class, with the logger name equal to the fully qualified name of the class. This is a useful and straightforward method of defining loggers. As the log output bears the name of the generating logger, this naming strategy makes it easy to identify the origin of a log message. However, this is only one possible, albeit common, strategy for naming loggers. Logback does not restrict the possible set of loggers. As a developer, you are free to name loggers as you wish.Nevertheless, naming loggers after the class where they are located seems to be the best general strategy known so far.
推薦方式是:在哪一個類使用,就使用哪一個類的全限定名做爲LoggerFactory.getLogger(param)
的 param。
仔細思考會發現這個命名範式是很詭異的。若是按照此範式命名,若是某個類的日誌記錄器名爲"a.b",那麼不可能存在「a.b.c」的日誌記錄器,由於一個類不可能像一個包同樣,裏面裝一個類。這樣的話 logback 裏的祖輩子輩日誌記錄器的結構被徹底破壞,所謂樹形結構也不復存在。
這個疑惑隨着繼續閱讀文檔會被解決,這裏簡單說一下。由於記錄器的配置實際上並非經過代碼,而是經過xml文件的。這裏LoggerFactory.getLogger(param)
的直接寫代碼的用法,實際上是用而不是配置——設置記錄器等級,添加記錄器擁有的輸出起之類的。而在配置文件中,經常會以一個包的全限定路徑爲名去配置記錄器。logback在讀取配置文件時會實例化這樣的記錄器,同時咱們又可能經過LoggerFactory.getLogger(param)
的方法獲得這個包下的記錄器,這樣兩個記錄器就構成了父子關係。同時,就像咱們說的,LoggerFactory.getLogger(param)
只是在用而沒有配置,實際上這裏獲得的記錄器並無擁有輸出器,也就等同於即便記錄請求被批准了,實際上也沒有可輸出的地方。真正的輸出實際上是經過可加性將消息傳遞到父級的,在xml文件中配置的,以包全限定路徑(固然,你也能夠用這個類的全限定路徑,或者用一個aabb,而後LoggerFactory.getLogger("aabb")
)爲名字的記錄器,由於這個記錄器在配置中指定了擁有的記錄器,因此才能產生輸出。
chapter 2
The rules governing appender additivity are summarized below.Appender Additivity:
The output of a log statement of loggerL
will go to all the appenders inL
and its ancestors. This is the meaning of the term "appender additivity".However, if an ancestor of logger
L
, sayP
, has the additivity flag set to false, thenL
's output will be directed to all the appenders inL
and its ancestors up to and includingP
but not the appenders in any of the ancestors ofP
.Loggers have their additivity flag set to true by default.
Since the root logger stands at the top of the logger hierarchy, the additivity flag does not apply to it.
可加性是Appender
的,可是設置的時候是設置Logger
的,文檔編撰者,作我的好嗎?因此我這裏直接改成的了記錄器的可加性。由於前兩段是很好想到的,記錄器會把一個被批准的記錄請求傳給本身的全部輸出器。
而記錄器的可加性在於,默認狀況下,記錄器將會上傳被它批准的消息給本身的祖輩們。祖輩們不作審查地將這則消息交給本身的全部輸出器。向上傳遞消息的過程直到遇到一個可加性爲false的記錄器才得以中止,而那個記錄器自己仍接受改消息。
chapter 2
Parameterized logging
Given that loggers in logback-classic implement the SLF4J's Logger interface, certain printing methods admit more than one parameter. These printing method variants are mainly intended to improve performance while minimizing the impact on the readability of the code.
這裏說日誌記錄器Logger
的打印(輸出)方法(所謂'certain printing methods',在超連接過去的API文檔中搜索print,根本沒有。結合下文才能反應過來,其實在日誌系統或者說slf4j或者說logback中,所謂的print就是指info(..)
,debug(...)
這樣的方法。)實現了SLF4J要求的能夠接收處理多個參數的能力。這一能力能夠提升性能並儘量不破壞可讀性,怎麼回事呢,繼續往下看。
For some Logger logger, writing,
logger.debug("Entry number: " + i + " is " + String.valueOf(entry[i]));incurs the cost of constructing the message parameter, that is converting both integer i and entry[i] to a String, and concatenating intermediate strings. This is regardless of whether the message will be logged or not.
One possible way to avoid the cost of parameter construction is by surrounding the log statement with a test. Here is an example.
if(logger.isDebugEnabled()) { logger.debug("Entry number: " + i + " is " + String.valueOf(entry[i])); }This way you will not incur the cost of parameter construction if debugging is disabled for logger. On the other hand, if the logger is enabled for the DEBUG level, you will incur the cost of evaluating whether the logger is enabled or not, twice: once in debugEnabled and once in debug. In practice, this overhead is insignificant because evaluating a logger takes less than 1% of the time it takes to actually log a request.
Better alternative
There exists a convenient alternative based on message formats. Assuming entry is an object, you can write:Object entry = new SomeObject(); logger.debug("The entry is {}.", entry);Only after evaluating whether to log or not, and only if the decision is positive, will the logger implementation format the message and replace the '{}' pair with the string value of entry. In other words, this form does not incur the cost of parameter construction when the log statement is disabled.
The following two lines will yield the exact same output. However, in case of a disabled logging statement, the second variant will outperform the first variant by a factor of at least 30.
logger.debug("The new entry is "+entry+"."); logger.debug("The new entry is {}.", entry);A two argument variant is also available. For example, you can write:
logger.debug("The new entry is {}. It replaces {}.", entry, oldEntry);
If three or more arguments need to be passed, an Object[] variant is also available. For example, you can write:Object[] paramArray = {newVal, below, above};
logger.debug("Value {} was inserted between {} and {}.", paramArray);
實現方法就是採用佔位符形式的輸出: logger.debug("The new entry is {}.", entry)
。類比於C語言的printf("the new try is %s",s)
,其中{}
便與%s
`%d這樣的C語言中佔位符功效相同。可使用多個
{}`以對應要傳入的多個參數,即「實現了SLF4J要求的能夠接收處理多個參數的能力」,而且確實提升了可讀性。
另外,咱們已經講過,一些記錄請求的級別不夠,會被記錄器拒絕。而經過這種方式定義的消息,其會首先自查本身是否會被容許記錄,只有在被容許的狀況下才會進一步格式化組織爲目標字符串。不然不會組織,這節省了資源。
注意,從這裏日後所涉及的內容對應的官方文檔內容,均是在已經引入了配置文件這一律念以後的內容了。即從如今開始,咱們將在配置文件(通常爲logback.xml
)中配置組件。而以後的內容也大多數是配置文件中對應logger
的元素標籤裏該如何寫的內容了。
logger
的appender
chapter 3
The <logger> element may contain zero or more <appender-ref> elements; each appender thus referenced is added to the named logger. Note that unlike log4j, logback-classic does not close nor remove any previously referenced appenders when configuring a given logger.
<appender-ref>怎麼寫其實沒說明白。經過在文檔中以'appender-ref'爲關鍵詞搜索,就獲得的例子,能夠推測其使用方式如圖:
再過幾節有幾段文字再度談及了logger引用appender。
chapter 3
對應標題爲「Configuring the root logger, or the<root>
element」的整個小節。
The
<root>
element configures the root logger. It supports a single attribute, namely the level attribute. It does not allow any other attributes because the additivity flag does not apply to the root logger. Moreover, since the root logger is already named as "ROOT", it does not allow a name attribute either. The value of the level attribute can be one of the case-insensitive strings TRACE, DEBUG, INFO, WARN, ERROR, ALL or OFF. Note that the level of the root logger cannot be set to INHERITED or NULL.
注意logback.xml
的基本結構,<root>
元素標籤並不放在任一<logger>
元素標籤裏,而是單獨一個。
這裏說<root>
配置對應根記錄器配置。這個元素標籤只有惟一一個可配置的屬性level
。
chapter 3
Let us note that the basic-selection rule depends on the effective level of the logger being invoked, not the level of the logger where appenders are attached.Logback will first determine whether a logging statement is enabled or not, and if enabled, it will invoke the appenders found in the logger hierarchy, regardless of their level.
這一段和咱們以前的「記錄器的可加性(additivity)」一節有呼應關係,在那一節的筆記裏咱們談到:「記錄器將會上傳被它批准的消息給本身的祖輩們。祖輩們不作審查地將這則消息交給本身的全部輸出器。「
這段官方文檔則直接代表了這一規則。
chapter 3
Appender additivity is not intended as a trap for new users. It is quite a convenient logback feature. For instance, you can configure logging such that log messages appear on the console (for all loggers in the system) while messages only from some specific set of loggers flow into a specific appender.
<configuration> <appender name="FILE" class="ch.qos.logback.core.FileAppender"> <file>myApp.log</file> <encoder> <pattern>%date %level [%thread] %logger{10} [%file:%line] %msg%n</pattern> </encoder> </appender> <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender"> <encoder> <pattern>%msg%n</pattern> </encoder> </appender> <logger name="chapters.configuration"> <appender-ref ref="FILE" /> </logger> <root level="debug"> <appender-ref ref="STDOUT" /> </root> </configuration>In this example, the console appender will log all the messages (for all loggers in the system) whereas only logging requests originating from the chapters.configuration logger and its children will go into the myApp.log file.
這一段都不講一下這個配置和什麼樣的代碼連用,真實服氣。不過連着讀文檔的話推測用文檔中的MyApp2是比較合適的。
package chapters.configuration; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import ch.qos.logback.classic.LoggerContext; import ch.qos.logback.core.util.StatusPrinter; public class MyApp2 { final static Logger logger = LoggerFactory.getLogger(MyApp2.class); public static void main(String[] args) { // assume SLF4J is bound to logback in the current environment LoggerContext lc = (LoggerContext) LoggerFactory.getILoggerFactory(); // print logback's internal status StatusPrinter.print(lc); logger.info("Entering application."); Foo foo = new Foo(); foo.doIt(); logger.info("Exiting application."); } }
package chapters.configuration; import org.slf4j.Logger; import org.slf4j.LoggerFactory; public class Foo { static final Logger logger = LoggerFactory.getLogger(Foo.class); public void doIt() { logger.debug("Did it again!"); } }
main
函數打頭四行用來輸出內部狀態信息的不用管它。倒數四行應該就是對應本節logback.xml
配置的實際動做了。final static Logger logger = LoggerFactory.getLogger(MyApp2.class);
獲得的logger
的name
應該是chapters.configuration.MyApp2
。這個logger
並無配置任何輸出器,因此它不會有任何輸出。但由於記錄器的累加性,在配置文件中被配置的<logger name="chapters.configuration">
生成的對應logger
爲它的父記錄器,收到了它批准的記錄請求,並交給了本身的輸出器去完成,因而消息變成記錄後保存到了文件。而且由於可加性繼續向上傳,<root level="debug">
對應的根記錄器也收到記錄請求,而將其交給本身的輸出器,因而記錄被輸出到控制檯。與此同時,若是一個不在chapters.configuration
包下的類調用了本身的logger
,那麼它只會有根元素的控制檯輸出的記錄,而不會產生存儲到文件裏的記錄。這就是引文第一句——Appender additivity is not intended as a trap for new users. It is quite a convenient logback feature. ——記錄器的累加性並非坑害開發者的陷阱,而是一個能夠被好好利用的特性。
chapter 3
對應標題爲」Overriding the default cumulative behaviour「的一整個小節。
<configuration> <appender name="FILE" class="ch.qos.logback.core.FileAppender"> <file>foo.log</file> <encoder> <pattern>%date %level [%thread] %logger{10} [%file : %line] %msg%n</pattern> </encoder> </appender> <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender"> <encoder> <pattern>%msg%n</pattern> </encoder> </appender> <logger name="chapters.configuration.Foo" additivity="false"> <appender-ref ref="FILE" /> </logger> <root level="debug"> <appender-ref ref="STDOUT" /> </root> </configuration>
經過這樣的設置,記錄器的累加性在chapters.configuration.Foo
處中止,至關於這個節點(以及它的子節點,雖然它只是一個類不會有子節點,但咱們能夠將這種用法放在一個以包爲名的記錄器上啊)從最終連接到以root
爲根的樹上脫離了。所以這個節點(以及它的子節點)創造了一顆新的樹,這顆新的樹中的消息的最終輸出和它脫離分裂出來的root
爲根的樹不同了。
這種分離出來,變成新樹,消息的最終結果所以不一樣的想法是頗有意義的。這能幫助咱們思考更復雜的輸出配置管理。