概覽
我們提供的服務(wù)有:網(wǎng)站制作、網(wǎng)站建設(shè)、微信公眾號(hào)開發(fā)、網(wǎng)站優(yōu)化、網(wǎng)站認(rèn)證、屏山ssl等。為1000多家企事業(yè)單位解決了網(wǎng)站和推廣的問題。提供周到的售前咨詢和貼心的售后服務(wù),是有科學(xué)管理、有技術(shù)的屏山網(wǎng)站制作公司
簡單地說,Logback 是一個(gè) Java 領(lǐng)域的日志框架。它被認(rèn)為是 Log4J 的繼承人。
Logback 主要由三個(gè)模塊組成:
logback-core 是其它模塊的基礎(chǔ)設(shè)施,其它模塊基于它構(gòu)建,顯然,logback-core 提供了一些關(guān)鍵的通用機(jī)制。logback-classic 的地位和作用等同于 Log4J,它也被認(rèn)為是 Log4J 的一個(gè)改進(jìn)版,并且它實(shí)現(xiàn)了簡單日志門面 SLF4J;而 logback-access 主要作為一個(gè)與 Servlet 容器交互的模塊,比如說 tomcat 或者 jetty,提供一些與 HTTP 訪問相關(guān)的功能。
目前 Logback 的使用很廣泛,很多知名的開源軟件都使用了 Logback作為日志框架,比如說 Akka,Apache Camel 等。
Logback 與 Log4J
實(shí)際上,這兩個(gè)日志框架都出自同一個(gè)開發(fā)者之手,Logback 相對(duì)于 Log4J 有更多的優(yōu)點(diǎn)
快速上手
想在 Java 程序中使用 Logback,需要依賴三個(gè) jar 包,分別是 slf4j-api,logback-core,logback-classic。其中 slf4j-api 并不是 Logback 的一部分,是另外一個(gè)項(xiàng)目,但是強(qiáng)烈建議將 slf4j 與 Logback 結(jié)合使用。要引用這些 jar 包,在 maven 項(xiàng)目中引入以下3個(gè) dependencies
org.slf4j slf4j-api 1.7.5 ch.qos.logback logback-core 1.0.11 ch.qos.logback logback-classic 1.0.11
第一個(gè)簡單的例子
package io.beansoft.logback.demo.universal; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * * * @author beanlam * @date 2017年2月9日 下午11:17:53 * @version 1.0 * */ public class SimpleDemo { private static final Logger logger = LoggerFactory.getLogger(SimpleDemo.class); public static void main(String[] args) { logger.info("Hello, this is a line of log message logged by Logback"); } }
以上代碼的運(yùn)行結(jié)果是:
23:19:41.131 [main] INFO i.b.l.demo.universal.SimpleDemo - Hello, this is a line of log message logged by Logback
注意到這里,代碼里并沒有引用任何一個(gè)跟 Logback 相關(guān)的類,而是引用了 SLF4J 相關(guān)的類,這邊是使用 SLF4J 的好處,在需要將日志框架切換為其它日志框架時(shí),無需改動(dòng)已有的代碼。
LoggerFactory 的 getLogger() 方法接收一個(gè)參數(shù),以這個(gè)參數(shù)決定 logger 的名字,這里傳入了 SimpleDemo 這個(gè)類的 Class 實(shí)例,那么 logger 的名字便是 SimpleDemo 這個(gè)類的全限定類名:io.beansoft.logback.demo.universal.SimpleDemo
讓 Logback 打印出一些它自身的內(nèi)部消息
package io.beansoft.logback.demo.universal; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import ch.qos.logback.classic.LoggerContext; import ch.qos.logback.core.util.StatusPrinter; /** * * * @author beanlam * @date 2017年2月9日 下午11:31:55 * @version 1.0 * */ public class LogInternalStateDemo { private static final Logger logger = LoggerFactory.getLogger(LogInternalStateDemo.class); public static void main(String[] args) { logger.info("Hello world"); //打印 Logback 內(nèi)部狀態(tài) LoggerContext lc = (LoggerContext) LoggerFactory.getILoggerFactory(); StatusPrinter.print(lc); } }
除了打印正常的日志信息,還打印出了 Logback 自身的內(nèi)部狀態(tài)信息
23:33:19.340 [main] INFO i.b.l.d.u.LogInternalStateDemo - Hello world
23:33:19,265 |-INFO in ch.qos.logback.classic.LoggerContext[default] - Could NOT find resource [logback.groovy]
23:33:19,265 |-INFO in ch.qos.logback.classic.LoggerContext[default] - Could NOT find resource [logback-test.xml]
23:33:19,265 |-INFO in ch.qos.logback.classic.LoggerContext[default] - Could NOT find resource [logback.xml]
23:33:19,266 |-INFO in ch.qos.logback.classic.LoggerContext[default] - Setting up default configuration.
Logger,Appenders 與 Layouts
在 logback 里,最重要的三個(gè)類分別是
Logger 類位于 logback-classic 模塊中, 而 Appender 和 Layout 位于 logback-core 中,這意味著, Appender 和 Layout 并不關(guān)心 Logger 的存在,不依賴于 Logger,同時(shí)也能看出, Logger 會(huì)依賴于 Appender 和 Layout 的協(xié)助,日志信息才能被正常打印出來。
分層命名規(guī)則
為了可以控制哪些信息需要輸出,哪些信息不需要輸出,logback 中引進(jìn)了一個(gè) 分層 概念。每個(gè) logger 都有一個(gè) name,這個(gè) name 的格式與 Java 語言中的包名格式相同。這也是前面的例子中直接把一個(gè) class 對(duì)象傳進(jìn) LoggerFactory.getLogger() 方法作為參數(shù)的原因。
logger 的 name 格式?jīng)Q定了多個(gè) logger 能夠組成一個(gè)樹狀的結(jié)構(gòu),為了維護(hù)這個(gè)分層的樹狀結(jié)構(gòu),每個(gè) logger 都被綁定到一個(gè) logger 上下文中,這個(gè)上下文負(fù)責(zé)厘清各個(gè) logger 之間的關(guān)系。
例如, 命名為 io.beansoft 的 logger,是命名為 io.beansoft.logback 的 logger 的父親,是命名為 io.beansoft.logback.demo 的 logger 的祖先。
在 logger 上下文中,有一個(gè) root logger,作為所有 logger 的祖先,這是 logback 內(nèi)部維護(hù)的一個(gè) logger,并非開發(fā)者自定義的 logger。
可通過以下方式獲得這個(gè) logger :
Logger rootLogger = LoggerFactory.getLogger(org.slf4j.Logger.ROOT_LOGGER_NAME);
同樣,通過 logger 的 name,就能獲得對(duì)應(yīng)的其它 logger 實(shí)例。
Logger 這個(gè)接口主要定義的方法有:
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í)別
logger 有日志打印級(jí)別,可以為一個(gè) logger 指定它的日志打印級(jí)別。
如果不為一個(gè) logger 指定打印級(jí)別,那么它將繼承離他最近的一個(gè)有指定打印級(jí)別的祖先的打印級(jí)別。這里有一個(gè)容易混淆想不清楚的地方,如果 logger 先找它的父親,而它的父親沒有指定打印級(jí)別,那么它會(huì)立即忽略它的父親,往上繼續(xù)尋找它爺爺,直到它找到 root logger。因此,也能看出來,要使用 logback, 必須為 root logger 指定日志打印級(jí)別。
日志打印級(jí)別從低級(jí)到高級(jí)排序的順序是:TRACE < DEBUG < INFO < WARN < ERROR
如果一個(gè) logger 允許打印一條具有某個(gè)日志級(jí)別的信息,那么它也必須允許打印具有比這個(gè)日志級(jí)別更高級(jí)別的信息,而不允許打印具有比這個(gè)日志級(jí)別更低級(jí)別的信息。
舉個(gè)例子:
package io.beansoft.logback.demo.universal; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import ch.qos.logback.classic.Level; /** * * * @author beanlam * @date 2017年2月10日 上午12:20:33 * @version 1.0 * */ public class LogLevelDemo { public static void main(String[] args) { //這里強(qiáng)制類型轉(zhuǎn)換時(shí)為了能設(shè)置 logger 的 Level ch.qos.logback.classic.Logger logger = (ch.qos.logback.classic.Logger) LoggerFactory.getLogger("com.foo"); logger.setLevel(Level.INFO); Logger barlogger = LoggerFactory.getLogger("com.foo.Bar"); // 這個(gè)語句能打印,因?yàn)?WARN > INFO logger.warn("can be printed because WARN > INFO"); // 這個(gè)語句不能打印,因?yàn)?DEBUG < INFO. logger.debug("can not be printed because DEBUG < INFO"); // barlogger 是 logger 的一個(gè)子 logger // 它繼承了 logger 的級(jí)別 INFO // 以下語句能打印,因?yàn)?INFO >= INFO barlogger.info("can be printed because INFO >= INFO"); // 以下語句不能打印,因?yàn)?DEBUG < INFO barlogger.debug("can not be printed because DEBUG < INFO"); } }
打印結(jié)果是:
00:27:19.251 [main] WARN com.foo - can be printed because WARN > INFO
00:27:19.255 [main] INFO com.foo.Bar - can be printed because INFO >= INFO
獲取 logger
在 logback 中,每個(gè) logger 都是一個(gè)單例,調(diào)用 LoggerFactory.getLogger 方法時(shí),如果傳入的 logger name 相同,獲取到的 logger 都是同一個(gè)實(shí)例。
在為 logger 命名時(shí),用類的全限定類名作為 logger name 是最好的策略,這樣能夠追蹤到每一條日志消息的來源。
Appender 和 Layout
在 logback 的世界中,日志信息不僅僅可以打印至 console,也可以打印至文件,甚至輸出到網(wǎng)絡(luò)流中,日志打印的目的地由 Appender 來決定,不同的 Appender 能將日志信息打印到不同的目的地去。
Appender 是綁定在 logger 上的,同時(shí),一個(gè) logger 可以綁定多個(gè) Appender,意味著一條信息可以同時(shí)打印到不同的目的地去。例如,常見的做法是,日志信息既輸出到控制臺(tái),同時(shí)也記錄到日志文件中,這就需要為 logger 綁定兩個(gè)不同的 logger。
Appender 是綁定在 logger 上的,而 logger 又有繼承關(guān)系,因此一個(gè) logger 打印信息時(shí)的目的地 Appender 需要參考它的父親和祖先。在 logback 中,默認(rèn)情況下,如果一個(gè) logger 打印一條信息,那么這條信息首先會(huì)打印至它自己的 Appender,然后打印至它的父親和父親以上的祖先的 Appender,但如果它的父親設(shè)置了 additivity = false,那么這個(gè) logger 除了打印至它自己的 Appender 外,只會(huì)打印至其父親的 Appender,因?yàn)樗母赣H的 additivity 屬性置為了 false,開始變得忘祖忘宗了,所以這個(gè) logger 只認(rèn)它父親的 Appender;此外,對(duì)于這個(gè) logger 的父親來說,如果父親的 logger 打印一條信息,那么它只會(huì)打印至自己的 Appender中(如果有的話),因?yàn)楦赣H已經(jīng)忘記了爺爺及爺爺以上的那些父輩了。
打印的日志除了有打印的目的地外,還有日志信息的展示格式。在 logback 中,用 Layout 來代表日志打印格式。比如說,PatternLayout 能夠識(shí)別以下這條格式:
%-4relative [%thread] %-5level %logger{32} - %msg%n
然后打印出來的格式效果是:
176 [main] DEBUG manual.architecture.HelloWorld2 - Hello world.
上面這個(gè)格式的第一個(gè)字段代表從程序啟動(dòng)開始后經(jīng)過的毫秒數(shù),第二個(gè)字段代表打印出這條日志的線程名字,第三個(gè)字段代表日志信息的日志打印級(jí)別,第四個(gè)字段代表 logger name,第五個(gè)字段是日志信息,第六個(gè)字段僅僅是代表一個(gè)換行符。
參數(shù)化打印日志
經(jīng)常能看到打印日志的時(shí)候,使用以下這種方式打印日志:
logger.debug("the message is " + msg + " from " + somebody);
這種打印日志的方式有個(gè)缺點(diǎn),就是無論日志級(jí)別是什么,程序總要先執(zhí)行 "the message is " + msg + " from " + somebody 這段字符串的拼接操作。當(dāng) logger 設(shè)置的日志級(jí)別為比 DEBUG 級(jí)別更高級(jí)別時(shí),DEBUG 級(jí)別的信息不回被打印出來的,顯然,字符串拼接的操作是不必要的,當(dāng)要拼接的字符串很大時(shí),這無疑會(huì)帶來很大的性能白白損耗。
于是,一種改進(jìn)的打印日志方式被人們發(fā)現(xiàn)了:
if(logger.isDebugEnabled()) { logger.debug("the message is " + msg + " from " + somebody); }
這樣的方式確實(shí)能避免字符串拼接的不必要損耗,但這也不是最好的方法,當(dāng)日志級(jí)別為 DEBUG 時(shí),那么打印這行消息,需要判斷兩次日志級(jí)別。一次是logger.isDebugEnabled(),另一次是 logger.debug() 方法內(nèi)部也會(huì)做的判斷。這樣也會(huì)帶來一點(diǎn)點(diǎn)效率問題,如果能找到更好的方法,誰愿意無視白白消耗的效率。
有一種更好的方法,那就是提供占位符的方式,以參數(shù)化的方式打印日志,例如上述的語句,可以是這樣的寫法:
logger.debug("the message {} is from {}", msg, somebody);
這樣的方式,避免了字符串拼接,也避免了多一次日志級(jí)別的判斷。
logback 內(nèi)部運(yùn)行流程
當(dāng)應(yīng)用程序發(fā)起一個(gè)記錄日志的請(qǐng)求,例如 info() 時(shí),logback 的內(nèi)部運(yùn)行流程如下所示
有關(guān)性能問題
關(guān)于日志系統(tǒng),人們討論得最多的是性能問題,即使是小型的應(yīng)用程序,也有可能輸出大量的日志。打印日志中的不當(dāng)處理,會(huì)引發(fā)各種性能問題,例如太多的日志記錄請(qǐng)求可能使磁盤 IO 成為性能瓶頸,從而影響到應(yīng)用程序的正常運(yùn)行。在合適的時(shí)候記錄日志、以更好的方式發(fā)起日志請(qǐng)求、以及合理設(shè)置日志級(jí)別方面,都有可能造成性能問題。
關(guān)于性能問題,以下幾個(gè)方面需要了解
logback 配置
配置須知
配置方式
logback 提供的配置方式有以下幾種:
logback 在啟動(dòng)時(shí),根據(jù)以下步驟尋找配置文件:
logback-test.xml 一般用來在測試代碼中打日志,如果是 maven 項(xiàng)目,一般把 logback-test.xml 放在 src/test/resources 目錄下。maven 打包的時(shí)候也不會(huì)把這個(gè)文件打進(jìn) jar 包里。
logback 啟動(dòng)的時(shí)候解析配置文件大概需要 100 毫秒的時(shí)間,如果希望更快啟動(dòng),可以采用 SPI 的方式。
默認(rèn)的配置
前面有提到默認(rèn)的配置,由 BasicConfiguator 類配置而成,這個(gè)類的配置可以用如下的配置文件來表示:
%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n
啟動(dòng)時(shí)打印狀態(tài)信息
如果 logback 在啟動(dòng)時(shí),解析配置文件時(shí),出現(xiàn)了需要警告的信息或者錯(cuò)誤信息,那 logback 會(huì)自動(dòng)先打印出自身的狀態(tài)信息。
如果希望正常情況下也打印出狀態(tài)信息,則可以使用之前提到的方式,在代碼里顯式地調(diào)用使其輸出:
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); ... }
也可以在配置文件中,指定 configuration 的 debug 屬性為 true
%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n
還可以指定一個(gè) Listener:
... the rest of the configuration file
重置默認(rèn)的配置文件位置
設(shè)置 logback.configurationFile 系統(tǒng)變量,可以通過 -D 參數(shù)設(shè)置,所指定的文件名必須以 .xml 或者 .groovy 作為文件后綴,否則 logback 會(huì)忽略這些文件。
配置文件自動(dòng)熱加載
要使配置文件自動(dòng)重載,需要把 scan 屬性設(shè)置為 true,默認(rèn)情況下每分鐘才會(huì)掃描一次,可以指定掃描間隔:
...
注意掃描間隔要加上單位,可用的單位是 milliseconds,seconds,minutes 和 hours。如果只指定了數(shù)字,但沒有指定單位,這默認(rèn)單位為 milliseconds。
在 logback 內(nèi)部,當(dāng)設(shè)置 scan 屬性為 true 后,一個(gè)叫做 ReconfigureOnChangeFilter 的過濾器就會(huì)被牽扯進(jìn)來,它負(fù)責(zé)判斷是否到了該掃描的時(shí)候,以及是否該重新加載配置。Logger 的任何一個(gè)打印日志的方法被調(diào)用時(shí),都會(huì)觸發(fā)這個(gè)過濾器,所以關(guān)于這個(gè)過濾器的自身的性能問題,變得十分重要。logback 目前采用這樣一種機(jī)制,當(dāng) logger 的調(diào)用次數(shù)到達(dá)一定次數(shù)后,才真正讓過濾器去做它要做的事情,這個(gè)次數(shù)默認(rèn)是 16,而 logback 會(huì)在運(yùn)行時(shí)根據(jù)調(diào)用的頻繁度來動(dòng)態(tài)調(diào)整這個(gè)數(shù)目。
輸出異常棧時(shí)也打印出 jar 包的信息
這個(gè)屬性默認(rèn)是關(guān)閉,可通過以下方式開啟:
...
也可以通過 LoggerContext 的 setPackagingDataEnabled(boolean) 方法來開啟
LoggerContext lc = (LoggerContext) LoggerFactory.getILoggerFactory(); lc.setPackagingDataEnabled(true);
直接調(diào)用 JoranConfigurator
Joran 是 logback 使用的一個(gè)配置加載庫,如果想要重新實(shí)現(xiàn) logback 的配置機(jī)制,可以直接調(diào)用這個(gè)類 JoranConfigurator 來實(shí)現(xiàn):
package chapters.configuration; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import ch.qos.logback.classic.LoggerContext; import ch.qos.logback.classic.joran.JoranConfigurator; import ch.qos.logback.core.joran.spi.JoranException; import ch.qos.logback.core.util.StatusPrinter; public class MyApp3 { final static Logger logger = LoggerFactory.getLogger(MyApp3.class); public static void main(String[] args) { // assume SLF4J is bound to logback in the current environment LoggerContext context = (LoggerContext) LoggerFactory.getILoggerFactory(); try { JoranConfigurator configurator = new JoranConfigurator(); configurator.setContext(context); // Call context.reset() to clear any previous configuration, e.g. default // configuration. For multi-step configuration, omit calling context.reset(). context.reset(); configurator.doConfigure(args[0]); } catch (JoranException je) { // StatusPrinter will handle this } StatusPrinter.printInCaseOfErrorsOrWarnings(context); logger.info("Entering application."); Foo foo = new Foo(); foo.doIt(); logger.info("Exiting application."); } }
配置文件格式
配置文件的基本結(jié)構(gòu)
根節(jié)點(diǎn)是 configuration,可包含0個(gè)或多個(gè) appender,0個(gè)或多個(gè) logger,最多一個(gè) root。
配置 logger 節(jié)點(diǎn)
在配置文件中,logger 的配置在
level 的取值可以是 TRACE, DEBUG, INFO, WARN, ERROR, ALL, OFF, INHERITED, NULL, 其中 INHERITED 和 NULL 的作用是一樣的,并不是不打印任何日志,而是強(qiáng)制這個(gè) logger 必須從其父輩繼承一個(gè)日志級(jí)別。
additivity 的取值是一個(gè)布爾值,true 或者 false。
配置 root 節(jié)點(diǎn)
配置 appender 節(jié)點(diǎn)
如果想要往一個(gè) logger 上綁定 appender,則使用以下方式:
設(shè)置 Context Name
myAppName %d %contextName [%t] %level %logger{36} - %msg%n
變量替換
在 logback 中,支持以 ${varName} 來引用變量
定義變量
可以直接在 logback.xml 中定義變量
${USER_HOME}/myApp.log %msg%n
也可以通過大D參數(shù)來定義
java -DUSER_HOME="/home/sebastien" MyApp2
也可以通過外部文件來定義
${USER_HOME}/myApp.log %msg%n
外部文件也支持 classpath 中的文件
${USER_HOME}/myApp.log %msg%n
外部文件的格式是 key-value 型。
USER_HOME=/home/sebastien
變量的作用域
變量有三個(gè)作用域
local 作用域在配置文件內(nèi)有效,context 作用域的有效范圍延伸至 logger context,system 作用域的范圍最廣,整個(gè) JVM 內(nèi)都有效。
logback 在替換變量時(shí),首先搜索 local 變量,然后搜索 context,然后搜索 system。
如何為變量指定 scope ?
/opt/${nodeId}/myApp.log %msg%n
變量的默認(rèn)值
在引用一個(gè)變量時(shí),如果該變量未定義,那么可以為其指定默認(rèn)值,做法是:
${aName:-golden}
運(yùn)行時(shí)定義變量
需要使用
round brown 24
條件化處理配置文件
logback 允許在配置文件中定義條件語句,以決定配置的不同行為,具體語法格式如下:
... ... ...
示例:
%d %-5level %logger{35} - %msg %n ${randomOutputDir}/conditional.log %d %-5level %logger{35} - %msg %n
從JNDI 獲取變量
使用
${appName} %d ${CONTEXT_NAME} %level %msg %logger{50}%n
文件包含
可以使用 ≶include> 標(biāo)簽在一個(gè)配置文件中包含另外一個(gè)配置文件,如下圖所示:
被包含的文件必須有以下格式:
"%d - %m%n"
支持從多種源頭包含
從文件中包含
從 classpath 中包含
從 URL 中包含
如果包含不成功,那么 logback 會(huì)打印出一條警告信息,如果不希望 logback 抱怨,只需這樣做:
添加一個(gè) Context Listener
LoggerContextListener 接口的實(shí)例能監(jiān)聽 logger context 上發(fā)生的事件,比如說日志級(jí)別的變化,添加的方式如下所示:
....
以上就是本文的全部內(nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持創(chuàng)新互聯(lián)。