每一個java程式員都知道日志對于任何一個java應用程式,尤其是服務端程式是至關重要的,而很多程式員也已經熟悉各種不同的日志庫如java.util.logging、apache log4j、logback。但如果你還不知道slf4j(simple logging facade for java)的話,那麼是時候去在你項目中學習使用slf4j了。
slf4j不同于其他日志類庫,與其它日志類庫有很大的不同。slf4j(simple logging facade for java)不是一個真正的日志實作,而是一個抽象層( abstraction layer),它允許你在背景使用任意一個日志類庫。如果是在編寫供内外部都可以使用的api或者通用類庫,那麼你真不會希望使用你類庫的用戶端必須使用你選擇的日志類庫。
如果一個項目已經使用了log4j,而你加載了一個類庫,比方說 apache active mq——它依賴于于另外一個日志類庫logback,那麼你就需要把它也加載進去。但如果apache active mq使用了slf4j,你可以繼續使用你的日志類庫而無語忍受加載和維護一個新的日志架構的痛苦。
總的來說,slf4j使你的代碼獨立于任意一個特定的日志api,這是一個對于開發api的開發者很好的思想。雖然抽象日志類庫的思想已經不是新鮮的事物而且apache commons logging也已經在使用這種思想了,但現在slf4j正迅速成為java世界的日志标準。讓我們再看看幾個使用slf4j而不是log4j、logback或者java.util.logging的理由。
正如我之前說的,在你的代碼中使用slf4j寫日志語句的主要出發點是使得你的程式獨立于任意特定的日志類庫,依賴于特定類可能需要不同與你已有的配置,并且導緻更多元護的麻煩。但除此之外,還要一個slf4j api的特性使得我堅持使用slf4j而抛棄我長期間鐘愛的lof4j的理由,是被稱為占位符(place holder),在代碼中表示為“<code>{}</code>”的特性。占位符是一個非常類似于在<code>string</code>的<code>format()</code>方法中的<code>%s</code>,因為它會在運作時被某個提供的實際字元串所替換。這不僅降低了你代碼中字元串連接配接次數,而且還節省了建立的string對象。即使你可能沒需要那些對象,但這個依舊成立,取決于你的生産環境的日志級别,例如在debug或者info級别的字元串連接配接。因為string對象是不可修改的并且它們建立在一個string池中,它們消耗堆記憶體( heap memory)而且大多數時間他們是不被需要的,例如當你的應用程式在生産環境以error級别運作時候,一個string使用在debug語句就是不被需要的。通過使用slf4j,你可以在運作時延遲字元串的建立,這意味着隻有需要的string對象才被建立。而如果你已經使用log4j,那麼你已經對于在if條件中使用debug語句這種變通方案十分熟悉了,但slf4j的占位符就比這個好用得多。
這是你在log4j中使用的方案,但肯定這一點都不有趣并且降低了代碼可讀性因為增加了不必要的繁瑣重複代碼(boiler-plate code):
另一方面,如果你使用slf4j的話,你可以得到在極簡潔的格式的結果,就像以下展示的一樣:
在slf4j,我們不需要字元串連接配接而且不會導緻暫時不需要的字元串消耗。取而代之的,我們在一個以占位符和以參數傳遞實際值的模闆格式下寫日志資訊。你可能會在想萬一我有很個參數怎麼辦?嗯,那麼你可以選擇使用變量參數版本的日志方法或者用以object數組傳遞。這是一個相當的友善和高效方法的打日志方法。記住,在生産最終日志資訊的字元串之前,這個方法會檢查一個特定的日志級别是不是打開了,這不僅降低了記憶體消耗而且預先降低了cpu去處理字元串連接配接指令的時間。這裡是使用slf4j日志方法的代碼,來自于slf4j-log4j12-1.6.1.jar中的log4j的擴充卡類log4jloggeradapter。
同時,我們也很值得知道打日志是對應用程式的性能有着很大影響的,在生産環節上隻進行必要的日志記錄是我們所建議的。
假設現有如下程式:
可以使用以下兩種方式對日志系統的輸出格式、記錄級别、輸出方式等進行配置。
log4j.properties:
輸出結果為:
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!
首先pom中添加如下依賴:
logback.xml
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!
error 為嚴重錯誤 主要是程式的錯誤
warn 為一般警告,比如session丢失
info 為一般要顯示的資訊,比如登入登出
debug 為程式的調試資訊
log4j.appender.appendername=?? org.apache.log4j.consoleappender(控制台) org.apache.log4j.fileappender(檔案) org.apache.log4j.dailyrollingfileappender(每天産生一個日志檔案) org.apache.log4j.rollingfileappender(檔案大小到達指定尺寸的時候産生一個新的檔案) org.apache.log4j.writerappender(将日志資訊以流格式發送到任意指定的地方)
log4j.appender.appendername.layout = ?? org.apache.log4j.htmllayout(以html表格形式布局) org.apache.log4j.patternlayout(可以靈活地指定布局模式) org.apache.log4j.simplelayout(包含日志資訊的級别和資訊字元串) org.apache.log4j.ttcclayout(包含日志産生的時間、線程、類别等等資訊)
threshold=debug:指定日志消息的輸出最低層次。 immediateflush=true:預設值是true,意謂着所有的消息都會被立即輸出。 target=system.err:預設情況下是system.out,指定輸出控制台
file=mylog.txt:指定消息輸出到mylog.txt檔案。 append=false:預設值是true,即将消息增加到指定檔案中,false指将消息覆寫指定的檔案内容。
maxfilesize=100kb: 字尾可以是kb, mb 或者是 gb. 在日志檔案到達該大小時,将會自動滾動,即将原來的内容移到mylog.log.1檔案。 maxbackupindex=2:指定可以産生的滾動檔案的最大數。
-x号: x資訊輸出時左對齊; %p: 輸出日志資訊優先級,即debug,info,warn,error,fatal, %d: 輸出日志時間點的日期或時間,預設格式為iso8601,也可以在其後指定格式,比如:%d{yyy mmm dd hh:mm:ss,sss},輸出類似:2002年10月18日 22:10:28,921 %r: 輸出自應用啟動到輸出該log資訊耗費的毫秒數 %c: 輸出日志資訊所屬的類目,通常就是所在類的全名 %t: 輸出産生該日志事件的線程名 %l: 輸出日志事件的發生位置,相當于%c.%m(%f:%l)的組合,包括類目名、發生的線程,以及在代碼中的行數。舉例:testlog4.main (testlog4.java:10) %x: 輸出和目前線程相關聯的ndc(嵌套診斷環境),尤其用到像java servlets這樣的多客戶多線程的應用中。 %%: 輸出一個”%”字元 %f: 輸出日志消息産生時所在的檔案名稱 %l: 輸出代碼中的行号 %m: 輸出代碼中指定的消息,産生的日志具體資訊 %n: 輸出一個回車換行符,windows平台為”\r\n”,unix平台為”\n”輸出日志資訊換行
一個示例配置檔案
過濾器
過濾器,執行一個過濾器會有傳回個枚舉值,即deny,neutral,accept其中之一。傳回deny,日志将立即被抛棄不再經過其他過濾器;傳回neutral,有序清單裡的下個過濾器過接着處理日志;傳回accept,日志會被立即處理,不再經過剩餘過濾器。
過濾器被添加到<code><appender></code> 中,為<code><appender></code> 添加一個或多個過濾器後,可以用任意條件對日志進行過濾。<code><appender></code> 有多個過濾器時,按照配置順序執行。
下面是幾個常用的過濾器:
levelfilter: 級别過濾器,根據日志級别進行過濾。如果日志級别等于配置級别,過濾器會根據onmath 和 onmismatch接收或拒絕日志。有以下子節點:
<code><level></code>:設定過濾級别
<code><onmatch></code>:用于配置符合過濾條件的操作
<code><onmismatch></code>:用于配置不符合過濾條件的操作
thresholdfilter: 臨界值過濾器,過濾掉低于指定臨界值的日志。當日志級别等于或高于臨界值時,過濾器傳回neutral;當日志級别低于臨界值時,日志會被拒絕。
例如:過濾掉所有低于info級别的日志。
evaluatorfilter: 求值過濾器,評估、鑒别日志是否符合指定條件。有一下子節點:
<code><evaluator></code>:
鑒别器,常用的鑒别器是janinoeventevaluato,也是預設的鑒别器,它以任意的java布爾值表達式作為求值條件,求值條件在配置檔案解釋過成功被動态編譯,布爾值表達式傳回true就表示符合過濾條件。evaluator有個子标簽<code><expression></code>,用于配置求值條件。
求值表達式作用于目前日志,logback向求值表達式暴露日志的各種字段:
name
type
description
event
loggingevent
與記錄請求相關聯的原始記錄事件,下面所有變量都來自event,例如,event.getmessage()傳回下面”message”相同的字元串
message
string
日志的原始消息,例如,設有logger mylogger,”name”的值是”aub”,對于 mylogger.info(“hello {}”,name); “hello {}”就是原始消息。
formatedmessage
日志被各式話的消息,例如,設有logger mylogger,”name”的值是”aub”,對于 mylogger.info(“hello {}”,name); “hello aub”就是格式化後的消息。
logger
logger 名。
loggercontext
loggercontextvo
日志所屬的logger上下文。
level
int
級别對應的整數值,是以 level > info 是正确的表達式。
timestamp
long
建立日志的時間戳。
marker
與日志請求相關聯的marker對象,注意“marker”有可能為null,是以你要確定它不能是null。
mdc
map
包含建立日志期間的mdc所有值得map。通路方法是:mdc.get(“mykey”) 。mdc.get()傳回的是object不是string,要想調用string的方法就要強轉,例如,((string) mdc.get(“k”)).contains(“val”) .mdc可能為null,調用時注意。
throwable
java.lang.throwable
如果沒有異常與日志關聯”throwable” 變量為 null. 不幸的是, “throwable” 不能被序列化。在遠端系統上永遠為null,對于與位置無關的表達式請使用下面的變量throwableproxy
throwableproxy
ithrowableproxy
與日志事件關聯的異常代理。如果沒有異常與日志事件關聯,則變量”throwableproxy” 為 null. 當異常被關聯到日志事件時,”throwableproxy” 在遠端系統上不會為null
例如:過濾掉所有日志消息中不包含“billing”字元串的日志。
參考來源: