講武德,你們要的高性能日志工具 Log4j2,來了
作者:沉默王二
來源:SegmentFault 思否社區(qū)
Log4j?介紹過了,SLF4J?介紹過了,Logback?也介紹過了,你以為到此終結(jié)了?
不不不,我告訴你,還有一個 Log4j 2,顧名思義,它就是 Log4j 的升級版,就好像手機里面的 Pro 版。我作為一個寫文章方面的工具人,或者叫打工人,怎么能不寫這最后一篇。
Log4j、SLF4J、Logback 是一個爹——Ceki Gulcu,但 Log4j 2 卻是例外,它是 Apache 基金會的產(chǎn)品。
SLF4J 和 Logback 作為 Log4j 的替代品,在很多方面都做了必要的改進,那為什么還需要 Log4j 2 呢?我只能說 Apache 基金會的開發(fā)人員很閑,不,很拼,要不是他們這種精益求精的精神,這個編程的世界該有多枯燥,畢竟少了很多可以用“拿來就用”的輪子啊。
上一篇也說了,老板下死命令要我把日志系統(tǒng)切換到 Logback,我順利交差了,老板很開心,夸我這個打工人很敬業(yè)。為了表達對老板的這份感謝,我決定偷偷摸摸地試水一下 Log4j 2,盡管它還不是個成品,可能會會項目帶來一定的隱患。但誰讓咱是一個敬崗愛業(yè)的打工人呢。

01、Log4j 2 強在哪
1)在多線程場景下,Log4j 2 的吞吐量比 Logback 高出了 10 倍,延遲降低了幾個數(shù)量級。這話聽起來像吹牛,反正是 Log4j 2 官方自己吹的。
Log4j 2 的異步 Logger 使用的是無鎖數(shù)據(jù)結(jié)構(gòu),而 Logback 和 Log4j 的異步 Logger 使用的是 ArrayBlockingQueue。對于阻塞隊列,多線程應(yīng)用程序在嘗試使日志事件入隊時通常會遇到鎖爭用。
下圖說明了多線程方案中無鎖數(shù)據(jù)結(jié)構(gòu)對吞吐量的影響。Log4j 2 隨著線程數(shù)量的擴展而更好地擴展:具有更多線程的應(yīng)用程序可以記錄更多的日志。其他日志記錄庫由于存在鎖競爭的關(guān)系,在記錄更多線程時,總吞吐量保持恒定或下降。這意味著使用其他日志記錄庫,每個單獨的線程將能夠減少日志記錄。

性能方面是 Log4j 2 的最大亮點,至于其他方面的一些優(yōu)勢,比如說下面這些,可以忽略不計,文字有多短就代表它有多不重要。
2)Log4j 2 可以減少垃圾收集器的壓力。
3)支持 Lambda 表達式。
4)支持自動重載配置。
02、Log4j 2 使用示例
廢話不多說,直接實操開干。理論知識有用,但不如上手實操一把,這也是我多年養(yǎng)成的一個“不那么良好”的編程習(xí)慣:在實操中發(fā)現(xiàn)問題,解決問題,尋找理論基礎(chǔ)。
第一步,在 pom.xml 文件中添加 Log4j 2 的依賴:
????org.apache.logging.log4j
????log4j-api
????2.5
????org.apache.logging.log4j
????log4j-core
????2.5
(這個 artifactId 還是 log4j,沒有體現(xiàn)出來 2,而在 version 中體現(xiàn),多少叫人誤以為是 log4j)
第二步,來個最簡單的測試用例:
import?org.apache.logging.log4j.LogManager;
import?org.apache.logging.log4j.Logger;
public?class?Demo?{
????private?static?final?Logger?logger?=?LogManager.getLogger(Demo.class);
????public?static?void?main(String[]?args)?{
????????logger.debug("log4j2");
????}
}
運行 Demo 類,可以在控制臺看到以下信息:
ERROR?StatusLogger?No?log4j2?configuration?file?found.?Using?default?configuration:?logging?only?errors?to?the?console.
Log4j 2 竟然沒有在控制臺打印“ log4j2”,還抱怨我們沒有為它指定配置文件。在這一點上,我就覺得它沒有 Logback 好,畢竟人家會輸出。
這對于新手來說,很不友好,因為新手在遇到這種情況的時候,往往不知所措。日志里面雖然體現(xiàn)了 ERROR,但代碼并沒有編譯出錯或者運行出錯,憑什么你不輸出?
那作為編程老鳥來說,我得告訴你,這時候最好探究一下為什么。怎么做呢?
我們可以復(fù)制一下日志信息中的關(guān)鍵字,比如說:“No log4j2 configuration file found”,然后在 Intellij IDEA 中搜一下,如果你下載了源碼和文檔的話,不除意外,你會在 ConfigurationFactory 類中搜到這段話。
可以在方法中打個斷點,然后 debug 一下,你就會看到下圖中的內(nèi)容。

通過源碼,你可以看得到,Log4j 2 會去尋找 4 種類型的配置文件,后綴分別是 properties、yaml、json 和 xml。前綴是 log4j2-test 或者 log4j2。
得到這個提示后,就可以進行第三步了。
第三步,在 resource 目錄下增加 log4j2-test.xml 文件(方便和 Logback 做對比),內(nèi)容如下所示:
"1.0"?encoding="UTF-8"?>
????
????????"Console"?target="SYSTEM_OUT">
????????????"%d{HH:mm:ss.SSS}?[%t]?%-5level?%logger{36}?-?%msg%n"/>
????????
????
????
????????"DEBUG">
????????????"Console"/>
????????
????
Log4j 2 的配置文件格式和 Logback 有點相似,基本的結(jié)構(gòu)為?< Configuration>?元素,包含 0 或多個?< Appenders>?元素,其后跟 0 或多個?< Loggers>?元素,里面再跟最多只能存在一個的?< Root>?元素。
1)配置 appender,也就是配置日志的輸出目的地。
有 Console,典型的控制臺配置信息上面你也看到了,我來簡單解釋一下里面 pattern 的格式:
%d{HH:mm:ss.SSS}?表示輸出到毫秒的時間 %t?輸出當前線程名稱 %-5level?輸出日志級別,-5 表示左對齊并且固定輸出 5 個字符,如果不足在右邊補空格 %logger?輸出 logger 名稱,最多 36 個字符 %msg?日志文本 %n?換行
%F?輸出所在的類文件名,如 Demo.java %L?輸出行號 %M?輸出所在方法名 %l?輸出語句所在的行數(shù), 包括類名、方法名、文件名、行數(shù) %p?輸出日志級別 %c?輸出包名,如果后面跟有?{length.}?參數(shù),比如說?%c{1.},它將輸出報名的第一個字符,如?com.itwanger?的實際報名將只輸出?c.i
10:14:04.657?[main]?DEBUG?com.itwanger.Demo?-?log4j2
"30">
...
03、Async 示例
??
????"DebugFile"?fileName="debug.log">
??????
????????%d?%p?%c?[%t]?%m%n
??????
????
????"Async">
??????"DebugFile"/>
????
??
??
????"debug">
??????"Async"/>
????
??
????
????????"Console"?target="SYSTEM_OUT">
????????????"%d{HH:mm:ss.SSS}?[%t]?%-5level?%logger{36}?-?%msg%n"/>
????????
????????"DebugFile"?fileName="debug.log">
????????????
????????????????%d?%p?%c?[%t]?%m%n
????????????
????????
????????"Async">
????????????"DebugFile"/>
????????
????
????
????????"DEBUG">
????????????"Console"/>
????????????"Async"/>
????????
????
2020-10-30?09:35:49,705?DEBUG?com.itwanger.Demo?[main]?log4j2
04、RollingFile 示例
??
????"RollingFile"?fileName="rolling.log"
?????????????????filePattern="rolling-%d{yyyy-MM-dd}-%i.log">
??????
????????%d?%p?%c{1.}?[%t]?%m%n
??????
??????
????????"1?KB"/>
??????
????
??
??
????"debug">
??????"RollingFile"/>
????
??
for?(int?i?=?1;i?20;?i++)?{
????logger.debug("微信搜索「{}」,回復(fù)關(guān)鍵字「{}」,有驚喜哦","沉默王二",?"java");
}


"RollingFileGZ"?fileName="gz/rolling.log"
?????????????filePattern="gz/%d{yyyy-MM-dd-HH}-%i.rolling.gz">
????
????????%d?%p?%c{1.}?[%t]?%m%n
????
????
????????"1?KB"/>
????
fileName 的屬性值中包含了一個目錄 gz,也就是說日志文件都將放在這個目錄下。 filePattern 的屬性值中增加了一個 gz 的后綴,這就表明日志文件要進行壓縮了,還可以是 zip 格式。


04、日志手冊


