用SLF4j/Logback打印日志-3


在 用SLF4j/Logback打印日志-1 和 用SLF4j/Logback打印日志-2 中分別介紹了Logback記錄日志的基本原理並重點介紹了輸出源配置。本篇介紹一些性能和技巧性的東西。

性能


在查看線上業務代碼的時候有時候會發現類似這樣的代碼:

logger.debug("This " + this + " and " + that);

在對性能有要求的系統中,這種寫法是非常不利的,雖然在配置線上系統的時候不會打印 DEBUG 級別的日志,但是在進入函數之前會先計算 "This " + this + " and " + that 這個字符串造成無畏的資源浪費。

在Logback中可以采用類似這樣的API來解決拼字符串問題:

logger.debug("This {} and {}", this, that);

這種語法可以解決絕大部分由於拼字符串造成的性能問題,但是在某些情況下,比如還要計算一些數值,這樣方案就不行了。

logger.debug("This {} and {} with {} ", this, that, compute());

這段代碼雖然解決了拼字符的問題,但是調用 compute() 方法依然會造成資源浪費。在Java8之前大多數的解決方案是這樣的:

if (logger.isDebugEnabled()) { logger.debug("This {} and {} with {} ", this, that, compute()); }

在打印日志之前先判斷是否需要打印相應級別的日志,這種寫法可以解決任何由於日志打印造成的性能問題。但是代碼卻變得不夠優雅,幾乎需要在每個調用logger.debug(..) 方法之前掉一次 if (logger.isDebugEnabled()) {.. ,在Java8之后可以用lambda表達式完美解決這個問題。

logger.debug("I am logging that {} happened.", () -> compute());

這種寫法把compute() 封裝到一個匿名類里面傳遞給了debug方法,只有Debug方法內部執行的時候才會執行 compute() 。但是如果函數有多個參數,這種寫法就變得有些怪異,因為它要求每個參數都要是lambda表達式,而寫出來的代碼就會變成這樣:

logger.debug("This {} and {} with {} ", () -> this, () -> that, () -> compute());

Java8的lambda是非常低效的,如果方法參數較多這種寫法會在每次調用的時候創建3個匿名類,反而會降低程序的性能。綜合考慮,一般參數較少並且有耗時計算任務的時候考慮用java8的特性。

日志分析


通常情況下,我們會約定日志的打印格式,以便日后的分析。默認情況下,logback是用空格分隔不同的日志字段的。

%d %-5p %t %c{2} %m%n

這種約定不利於日志的機器分析,如果被打印的消息里面也包含空格,那么解析就會出錯。簡單的方案是重新約定日志的格式,比如用,號分隔日志,並保證打印的消息里面不再包含,號,類似這樣 - %d, %-5p, %t, %c{2}, %m%n。逗號分隔只是比空格分隔略好一點,畢竟逗號出現在消息體里面的機會少一些。延續這種思路可能會進入一個誤區,我們需要尋找一種盡可能稀有的分隔符來分隔日志的字段。

其實我們完全可以直接約定一種協議格式來打印日志,比如JSON。這樣就不會擔心消息和協議沖突的問題,但是也會帶來新的新能問題。綜合考慮,如果需要日志比較簡單,那么可以采用簡單的分隔符分隔日志,如果日志較多比較復雜,那么可以封裝一些API來打印特有協議的日志,而性能問題可以考慮上一節的方案。

MDC


MDCMapped Diagnostic Context 的縮寫,“映射診斷上下文”看起來高大上的樣子,其實是非常簡單的,就是一個臨時存放k-v對的容器。和普通Map的區別是它是基於ThreadLocal實現的,所以不存在資源競爭問題,可以放心的往里面放東西。

假如我們有一個類似網關的應用,同一時間有很多的請求會發送到本系統。一般情況下,為了追蹤每個請求的處理情況我們會在請求中加一個字段叫“TraceId” - 一個普通的UID用來區別每一個請求。那么怎么在日志中打印出TraceId呢,一般可以在打印消息的時候,把TraceId作為參數拼湊到日志消息中,這樣有兩個不好的地方:

  1. 需要寫額外的代碼拼湊消息
  2. 需要在每個打印消息的地方維護一個全局的TraceId變量

如果使用MDC問題就可以簡化很多,在接收到請求后,解析出TraceId然后放入MDC,在配置文件中配置打印MDC,之后所有調用打印消息的日志都會自動包含TraceId了。

// 解析請求,取出TraceId放入MDC
MDC.put("traceid", xxxxx); //配置XML
%d, %-5p, %t, %c{2}, %X{traceid}, %m%n

非常的簡單而且自然有沒有!!!

PS:如果使用的是log4j需要1.2版本以上。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM