本文轉(zhuǎn)自云棲社區(qū)-點(diǎn)擊可以查看原文
1 基本介紹
每一個(gè)Java程序員都知道日志對(duì)于任何一個(gè)Java應(yīng)用程序,尤其是服務(wù)端程序是至關(guān)重要的,而很多程序員也已經(jīng)熟悉各種不同的日志庫如java.util.logging、Apache log4j、logback。但如果你還不知道SLF4J(Simple logging facade for Java)的話,那么是時(shí)候去在你項(xiàng)目中學(xué)習(xí)使用SLF4J了。
SLF4J不同于其他日志類庫,與其它日志類庫有很大的不同。SLF4J(Simple logging Facade for Java)不是一個(gè)真正的日志實(shí)現(xiàn),而是一個(gè)抽象層( abstraction layer),它允許你在后臺(tái)使用任意一個(gè)日志類庫。如果是在編寫供內(nèi)外部都可以使用的API或者通用類庫,那么你真不會(huì)希望使用你類庫的客戶端必須使用你選擇的日志類庫。
如果一個(gè)項(xiàng)目已經(jīng)使用了log4j,而你加載了一個(gè)類庫,比方說 Apache Active MQ——它依賴于于另外一個(gè)日志類庫logback,那么你就需要把它也加載進(jìn)去。但如果Apache Active MQ使用了SLF4J,你可以繼續(xù)使用你的日志類庫而無需忍受加載和維護(hù)一個(gè)新的日志框架的痛苦。
總的來說,SLF4J使你的代碼獨(dú)立于任意一個(gè)特定的日志API,這是一個(gè)對(duì)于開發(fā)API的開發(fā)者很好的思想。雖然抽象日志類庫的思想已經(jīng)不是新鮮的事物而且Apache commons logging也已經(jīng)在使用這種思想了,但現(xiàn)在SLF4J正迅速成為Java世界的日志標(biāo)準(zhǔn)。讓我們?cè)倏纯磶讉€(gè)使用SLF4J而不是log4j、logback或者java.util.logging的理由。
2 SLF4J對(duì)比Log4J,logback和java.util.Logging的優(yōu)勢(shì)
正如我之前說的,在你的代碼中使用SLF4J寫日志語句的主要出發(fā)點(diǎn)是使得你的程序獨(dú)立于任意特定的日志類庫,依賴于特定類可能需要不同與你已有的配置,并且導(dǎo)致更多維護(hù)的麻煩。
但除此之外,還要一個(gè)SLF4J API的特性使得我堅(jiān)持使用SLF4J而拋棄我長期間鐘愛的Lof4j的理由,是被稱為占位符(place holder),在代碼中表示為“{}”的特性。占位符是一個(gè)非常類似于在String的format()方法中的%s,因?yàn)樗鼤?huì)在運(yùn)行時(shí)被某個(gè)提供的實(shí)際字符串所替換。這不僅降低了你代碼中字符串連接次數(shù),而且還節(jié)省了新建的String對(duì)象。即使你可能沒需要那些對(duì)象,但這個(gè)依舊成立,取決于你的生產(chǎn)環(huán)境的日志級(jí)別,例如在DEBUG或者INFO級(jí)別的字符串連接。因?yàn)镾tring對(duì)象是不可修改的并且它們建立在一個(gè)String池中,它們消耗堆內(nèi)存( heap memory)而且大多數(shù)時(shí)間他們是不被需要的,例如當(dāng)你的應(yīng)用程序在生產(chǎn)環(huán)境以ERROR級(jí)別運(yùn)行時(shí)候,一個(gè)String使用在DEBUG語句就是不被需要的。通過使用SLF4J,你可以在運(yùn)行時(shí)延遲字符串的建立,這意味著只有需要的String對(duì)象才被建立。而如果你已經(jīng)使用log4j,那么你已經(jīng)對(duì)于在if條件中使用debug語句這種變通方案十分熟悉了,但SLF4J的占位符就比這個(gè)好用得多。
這是你在Log4j中使用的方案,但肯定這一點(diǎn)都不有趣并且降低了代碼可讀性因?yàn)樵黾恿瞬槐匾姆爆嵵貜?fù)代碼(boiler-plate code):
if (logger.isDebugEnabled()) {
logger.debug("Processing trade with id: " + id + " symbol: " + symbol);
}
另一方面,如果你使用SLF4J的話,你可以得到在極簡潔的格式的結(jié)果,就像以下展示的一樣:
logger.debug("Processing trade with id: {} and symbol : {} ", id, symbol);
在SLF4J,我們不需要字符串連接而且不會(huì)導(dǎo)致暫時(shí)不需要的字符串消耗。取而代之的,我們?cè)谝粋€(gè)以占位符和以參數(shù)傳遞實(shí)際值的模板格式下寫日志信息。你可能會(huì)在想萬一我有很個(gè)參數(shù)怎么辦?嗯,那么你可以選擇使用變量參數(shù)版本的日志方法或者用以O(shè)bject數(shù)組傳遞。這是一個(gè)相當(dāng)?shù)姆奖愫透咝Х椒ǖ拇蛉罩痉椒āS涀?,在生產(chǎn)最終日志信息的字符串之前,這個(gè)方法會(huì)檢查一個(gè)特定的日志級(jí)別是不是打開了,這不僅降低了內(nèi)存消耗而且預(yù)先降低了CPU去處理字符串連接命令的時(shí)間。這里是使用SLF4J日志方法的代碼,來自于slf4j-log4j12-1.6.1.jar中的Log4j的適配器類Log4jLoggerAdapter。
public void debug(String format, Object arg1, Object arg2) {
if (logger.isDebugEnabled()) {
FormattingTuple ft = MessageFormatter.format(format, arg1, arg2);
logger.log(FQCN, Level.DEBUG, ft.getMessage(), ft.getThrowable());
}
}
同時(shí),我們也有必要知道打日志是對(duì)應(yīng)用程序的性能有著很大影響的,在生產(chǎn)環(huán)節(jié)建議只是在必要的時(shí)候打日志。
3 使用配置
3.1 maven依賴
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.7.21</version>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.1.2</version>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-core</artifactId>
<version>1.1.2</version>
</dependency>
3.2 日志系統(tǒng)配置
假設(shè)現(xiàn)有如下程序:
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class Main {
private static final Logger logger = LoggerFactory.getLogger(Main.class);
public static void main(String[] args) {
int status = 0;
if (status == 0) {
logger.info("status:{}", status);
} else {
logger.info("status:{}", status);
}
logger.info("end!");
}
}
可以使用以下兩種方式對(duì)日志系統(tǒng)的輸出格式、記錄級(jí)別、輸出方式等進(jìn)行配置:
- 使用properties文件方式
- 使用xml文件方式
3.2.1 properties文件方式
log4j.properties:
log4j.rootLogger=info, ServerDailyRollingFile, stdout
log4j.appender.ServerDailyRollingFile=org.apache.log4j.DailyRollingFileAppender
log4j.appender.ServerDailyRollingFile.DatePattern='.'yyyy-MM-dd
log4j.appender.ServerDailyRollingFile.File=logs/notify-subscription.log
log4j.appender.ServerDailyRollingFile.layout=org.apache.log4j.PatternLayout
log4j.appender.ServerDailyRollingFile.layout.ConversionPattern=%d - %m%n
log4j.appender.ServerDailyRollingFile.Append=true
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%d{yyyy-MM-dd HH\:mm\:ss} %p [%c] %m%n
輸出結(jié)果為:
2016-05-12 16:08:21 INFO [club.chuxing.learn.Main] status:0
2016-05-12 16:08:21 INFO [club.chuxing.learn.Main] end!
3.2.2 xml文件方式
- 首先pom中添加如下依賴:
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-core</artifactId>
<version>1.1.7</version>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.1.7</version>
</dependency>
- 設(shè)置日志配置的xml文件 logback.xml
<?xml version="1.0" encoding="UTF-8"?>
<configuration debug="true">
<!-- 應(yīng)用名稱 -->
<property name="APP_NAME" value="logtest" />
<!--日志文件的保存路徑,首先查找系統(tǒng)屬性-Dlog.dir,如果存在就使用其;否則,在當(dāng)前目錄下創(chuàng)建名為logs目錄做日志存放的目錄 -->
<property name="LOG_HOME" value="${log.dir:-logs}/${APP_NAME}" />
<!-- 日志輸出格式 -->
<property name="ENCODER_PATTERN"
value="%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{80} - %msg%n" />
<contextName>${APP_NAME}</contextName>
<!-- 控制臺(tái)日志:輸出全部日志到控制臺(tái) -->
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
<Pattern>${ENCODER_PATTERN}</Pattern>
</encoder>
</appender>
<!-- 文件日志:輸出全部日志到文件 -->
<appender name="FILE"
class="ch.qos.logback.core.rolling.RollingFileAppender">
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>${LOG_HOME}/output.%d{yyyy-MM-dd}.log</fileNamePattern>
<maxHistory>7</maxHistory>
</rollingPolicy>
<encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
<pattern>${ENCODER_PATTERN}</pattern>
</encoder>
</appender>
<!-- 錯(cuò)誤日志:用于將錯(cuò)誤日志輸出到獨(dú)立文件 -->
<appender name="ERROR_FILE"
class="ch.qos.logback.core.rolling.RollingFileAppender">
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>${LOG_HOME}/error.%d{yyyy-MM-dd}.log</fileNamePattern>
<maxHistory>7</maxHistory>
</rollingPolicy>
<encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
<pattern>${ENCODER_PATTERN}</pattern>
</encoder>
<filter class="ch.qos.logback.classic.filter.ThresholdFilter">
<level>WARN</level>
</filter>
</appender>
<!-- 獨(dú)立輸出的同步日志 -->
<appender name="SYNC_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>${LOG_HOME}/sync.%d{yyyy-MM-dd}.log</fileNamePattern>
<maxHistory>7</maxHistory>
</rollingPolicy>
<encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
<pattern>${ENCODER_PATTERN}</pattern>
</encoder>
</appender>
<logger name="log.sync" level="DEBUG" addtivity="true">
<appender-ref ref="SYNC_FILE" />
</logger>
<root>
<level value="DEBUG" />
<appender-ref ref="STDOUT" />
<appender-ref ref="FILE" />
<appender-ref ref="ERROR_FILE" />
</root>
</configuration>
輸出結(jié)果為:
2016-05-12 17:08:32.105 [main] INFO club.chuxing.learn.Main - status:0
2016-05-12 17:08:32.114 [main] INFO club.chuxing.learn.Main - end!
3.3 日志系統(tǒng)配置說明
3.3.1 輸出級(jí)別的種類
- ERROR 為嚴(yán)重錯(cuò)誤 主要是程序的錯(cuò)誤
- WARN 為一般警告,比如session丟失
- INFO 為一般要顯示的信息,比如登錄登出
- DEBUG 為程序的調(diào)試信息
3.3.2 配置日志信息輸出目的地
log4j.appender.appenderName=??
org.apache.log4j.ConsoleAppender(控制臺(tái))
org.apache.log4j.FileAppender(文件)
org.apache.log4j.DailyRollingFileAppender(每天產(chǎn)生一個(gè)日志文件)
org.apache.log4j.RollingFileAppender(文件大小到達(dá)指定尺寸的時(shí)候產(chǎn)生一個(gè)新的文件)
org.apache.log4j.WriterAppender(將日志信息以流格式發(fā)送到任意指定的地方)
3.3.3 配置日志信息的格式
log4j.appender.appenderName.layout = ??
org.apache.log4j.HTMLLayout(以HTML表格形式布局)
org.apache.log4j.PatternLayout(可以靈活地指定布局模式)
org.apache.log4j.SimpleLayout(包含日志信息的級(jí)別和信息字符串)
org.apache.log4j.TTCCLayout(包含日志產(chǎn)生的時(shí)間、線程、類別等等信息)
3.3.4 ConsoleAppender選項(xiàng)
- Threshold=DEBUG:指定日志消息的輸出最低層次。
- ImmediateFlush=true:默認(rèn)值是true,意謂著所有的消息都會(huì)被立即輸出。
- Target=System.err:默認(rèn)情況下是System.out,指定輸出控制臺(tái)
3.3.5 FileAppender 選項(xiàng)
- Threshold=DEBUG:指定日志消息的輸出最低層次。
- ImmediateFlush=true:默認(rèn)值是true,意謂著所有的消息都會(huì)被立即輸出。
- File=mylog.txt:指定消息輸出到mylog.txt文件。
- Append=false:默認(rèn)值是true,即將消息增加到指定文件中,false指將消息覆蓋指定的文件內(nèi)容。
3.3.6 RollingFileAppender 選項(xiàng)
- Threshold=DEBUG:指定日志消息的輸出最低層次。
- ImmediateFlush=true:默認(rèn)值是true,意謂著所有的消息都會(huì)被立即輸出。
- File=mylog.txt:指定消息輸出到mylog.txt文件。
- Append=false:默認(rèn)值是true,即將消息增加到指定文件中,false指將消息覆蓋指定的文件內(nèi)容。
- MaxFileSize=100KB: 后綴可以是KB, MB 或者是 GB. 在日志文件到達(dá)該大小時(shí),將會(huì)自動(dòng)滾動(dòng),即將原來的內(nèi)容移到mylog.log.1文件。
- MaxBackupIndex=2:指定可以產(chǎn)生的滾動(dòng)文件的最大數(shù)。
3.3.7 日志信息格式中幾個(gè)符號(hào)所代表的含義
- -X號(hào): X信息輸出時(shí)左對(duì)齊;
- %p: 輸出日志信息優(yōu)先級(jí),即DEBUG,INFO,WARN,ERROR,F(xiàn)ATAL,
- %d: 輸出日志時(shí)間點(diǎn)的日期或時(shí)間,默認(rèn)格式為ISO8601,也可以在其后指定格式,比如:%d{yyy MMM dd HH:mm:ss,SSS},輸出類似:2002年10月18日 22:10:28,921
- %r: 輸出自應(yīng)用啟動(dòng)到輸出該log信息耗費(fèi)的毫秒數(shù)
- %c: 輸出日志信息所屬的類目,通常就是所在類的全名
- %t: 輸出產(chǎn)生該日志事件的線程名
- %l: 輸出日志事件的發(fā)生位置,相當(dāng)于%C.%M(%F:%L)的組合,包括類目名、發(fā)生的線程,以及在代碼中的行數(shù)。舉例:Testlog4.main (TestLog4.java:10)
- %x: 輸出和當(dāng)前線程相關(guān)聯(lián)的NDC(嵌套診斷環(huán)境),尤其用到像java servlets這樣的多客戶多線程的應(yīng)用中。
- %%: 輸出一個(gè)”%”字符
- %F: 輸出日志消息產(chǎn)生時(shí)所在的文件名稱
- %L: 輸出代碼中的行號(hào)
- %m: 輸出代碼中指定的消息,產(chǎn)生的日志具體信息
- %n: 輸出一個(gè)回車換行符,Windows平臺(tái)為”\r\n”,Unix平臺(tái)為”\n”輸出日志信息換行
一個(gè)示例配置文件
log4j.debug=true
log4j.rootLogger=DEBUG,D,E
log4j.appender.E = org.apache.log4j.DailyRollingFileAppender
log4j.appender.E.File = logs/logs.log
log4j.appender.E.Append = true
log4j.appender.E.Threshold = DEBUG
log4j.appender.E.layout = org.apache.log4j.PatternLayout
log4j.appender.E.layout.ConversionPattern = %-d{yyyy-MM-dd HH:mm:ss} [ %t:%r ] - [ %p ] %m%n
log4j.appender.D = org.apache.log4j.DailyRollingFileAppender
log4j.appender.D.File = logs/error.log
log4j.appender.D.Append = true
log4j.appender.D.Threshold = ERROR
log4j.appender.D.layout = org.apache.log4j.PatternLayout
log4j.appender.D.layout.ConversionPattern = %-d{yyyy-MM-dd HH:mm:ss} [ %t:%r ] - [ %p ] %m%n
過濾器
過濾器,執(zhí)行一個(gè)過濾器會(huì)有返回個(gè)枚舉值,即DENY,NEUTRAL,ACCEPT其中之一。返回DENY,日志將立即被拋棄不再經(jīng)過其他過濾器;返回NEUTRAL,有序列表里的下個(gè)過濾器過接著處理日志;返回ACCEPT,日志會(huì)被立即處理,不再經(jīng)過剩余過濾器。
過濾器被添加到<Appender>中,為<Appender> 添加一個(gè)或多個(gè)過濾器后,可以用任意條件對(duì)日志進(jìn)行過濾。<Appender> 有多個(gè)過濾器時(shí),按照配置順序執(zhí)行。
下面是幾個(gè)常用的過濾器:
- LevelFilter: 級(jí)別過濾器,根據(jù)日志級(jí)別進(jìn)行過濾。如果日志級(jí)別等于配置級(jí)別,過濾器會(huì)根據(jù)onMath 和 onMismatch接收或拒絕日志。有以下子節(jié)點(diǎn):
-
<level>:設(shè)置過濾級(jí)別 -
<onMatch>:用于配置符合過濾條件的操作 -
<onMismatch>:用于配置不符合過濾條件的操作
-
- ThresholdFilter: 臨界值過濾器,過濾掉低于指定臨界值的日志。當(dāng)日志級(jí)別等于或高于臨界值時(shí),過濾器返回NEUTRAL;當(dāng)日志級(jí)別低于臨界值時(shí),日志會(huì)被拒絕。
例如:過濾掉所有低于INFO級(jí)別的日志。 -
EvaluatorFilter: 求值過濾器,評(píng)估、鑒別日志是否符合指定條件。有子節(jié)點(diǎn):
<evaluator>: 鑒別器,常用的鑒別器是JaninoEventEvaluato,也是默認(rèn)的鑒別器,它以任意的Java布爾值表達(dá)式作為求值條件,求值條件在配置文件解釋過成功被動(dòng)態(tài)編譯,布爾值表達(dá)式返回true就表示符合過濾條件。evaluator有個(gè)子標(biāo)簽<expression>,用于配置求值條件。
求值表達(dá)式作用于當(dāng)前日志,logback向求值表達(dá)式暴露日志的各種字段:
| Name | Type | Description |
|---|---|---|
| event | LoggingEvent | 與記錄請(qǐng)求相關(guān)聯(lián)的原始記錄事件,下面所有變量都來自event,例如,event.getMessage()返回下面”message”相同的字符串 |
| message | String | 日志的原始消息,例如,設(shè)有l(wèi)ogger mylogger,”name”的值是”AUB”,對(duì)于 mylogger.info(“Hello {}”,name); “Hello {}”就是原始消息。 |
| formatedMessage | String | 日志被各式話的消息,例如,設(shè)有l(wèi)ogger mylogger,”name”的值是”AUB”,對(duì)于 mylogger.info(“Hello {}”,name); “Hello Aub”就是格式化后的消息。 |
| logger | String | logger 名。 |
| loggerContext | LoggerContextVO | 日志所屬的logger上下文。 |
| level | int | 級(jí)別對(duì)應(yīng)的整數(shù)值,所以 level > INFO 是正確的表達(dá)式。 |
| timeStamp | long | 創(chuàng)建日志的時(shí)間戳。 |
| marker | Marker | 與日志請(qǐng)求相關(guān)聯(lián)的Marker對(duì)象,注意“Marker”有可能為null,所以你要確保它不能是null。 |
| mdc | Map | 包含創(chuàng)建日志期間的MDC所有值得map。訪問方法是:mdc.get(“myKey”) 。mdc.get()返回的是Object不是String,要想調(diào)用String的方法就要強(qiáng)轉(zhuǎn),例如,((String) mdc.get(“k”)).contains(“val”) .MDC可能為null,調(diào)用時(shí)注意。 |
| throwable | java.lang.Throwable | 如果沒有異常與日志關(guān)聯(lián)”throwable” 變量為 null. 不幸的是, “throwable” 不能被序列化。在遠(yuǎn)程系統(tǒng)上永遠(yuǎn)為null,對(duì)于與位置無關(guān)的表達(dá)式請(qǐng)使用下面的變量throwableProxy |
| throwableProxy | IThrowableProxy | 與日志事件關(guān)聯(lián)的異常代理。如果沒有異常與日志事件關(guān)聯(lián),則變量”throwableProxy” 為 null. 當(dāng)異常被關(guān)聯(lián)到日志事件時(shí),”throwableProxy” 在遠(yuǎn)程系統(tǒng)上不會(huì)為null |
<onMatch>:用于配置符合過濾條件的操作
<onMismatch>:用于配置不符合過濾條件的操作
例如:過濾掉所有日志消息中不包含“billing”字符串的日志。