首頁 > 軟體

最牛逼的 Java 日誌框架,效能無敵,橫掃所有對手……

2021-05-31 10:00:59

「來源: |GitHub大本營 ID:githubcode」

關注「Github大本營」,選擇「設為星標」

回覆「資源」,贈送 價值12800元 程式設計資料一份~

Logback算是JAVA 裡一個老牌的日誌框架,從06年開始第一個版本,迭代至今也十幾年了。不過logback最近一個穩定版本還停留在 2017 年,好幾年都沒有更新;logback的兄弟 slf4j 最近一個穩定版也是2017年,有點涼涼的意思。

而且 logback的非同步效能實在拉跨,功能簡陋,配置又繁瑣,遠不及Apache 的新一代日誌框架 - Log4j2

目前來看,Log4j2 就是王者,其他日誌框架都不是對手!

Log4j2簡介

Apache Log4j 2是 Log4j(1) 的升級版,比它的祖先 Log4j 1. x 有了很大的改進,和logback對比有很大的改進。除了內部設計的調整外,主要有以下幾點的大升級:

更簡化的配置更強大的參數格式化最誇張的非同步效能Log4j 2中,分為API(log4j-api)實現(log4j-core) 兩個模組。API 和slf4j 是一個類型,屬於日誌抽象/門面,而實現部分,才是Log4j 2的核心。

org.apache.logging.log4j log4j-apiorg.apache.logging.log4j log4j-core最牛逼的效能

最強的非同步效能

這個特性,算是Log4j2最強之處了。

log4j2 在目前JAVA中的日誌框架裡,非同步日誌的效能是最高的,沒有之一。

先來看一下,幾種日誌框架benchmark對比結果(log4j2官方測試結果):

從圖上可以看出,log4j2的非同步(全非同步,非混合模式)下的效能,遠超log4j1和logback,簡直吊打。壓力越大的情況下,吞吐上的差距就越大。

在64執行緒測試下,log4j2的吞吐達到了180w+/s,而logback/log4j1只有不到20w,相差近十倍

零GC(Garbage-free)

從2.6版本開始(2016年),log4j2 預設就以零GC模式運行了。什麼叫零GC呢?就是不會由於log4j2而導致GC。

log4j2 中各種Message物件,字元串陣列,位元組陣列等全部複用,不重複創建,大大減少了無用物件的創建,從而做到「零GC」。

更高效能 I/O 寫入的支援

log4j 還提供了一個MemoryMappedFileAppender,I/O 部分使用MemoryMappedFile來實現,可以得到極高的I/O效能。

不過在使用MemoryMappedFileAppender之前,得確定你足夠了解MemoryMappedFile的相關知識,否則不要輕易使用呦。

更強大的參數格式化

API模組和slf4j相比,提供了更豐富的參數格式化功能。

使用{}佔位符格式化參數

在slf4j裡,我們可以用{}的方式來實現「format」的功能(參數會直接toString替換佔位符),像下面這樣:

logger.debug("Logging in user {} with birthday {}", user.getName(), user.getBirthdayCalendar());使用String.format的形式格式化參數

log4j2 中除了支援{}的參數佔位符,還支援String.format的形式:

publicstatic Logger logger = LogManager.getFormatterLogger("Foo");logger.debug("Logging in user %s with birthday %s", user.getName(), user.getBirthdayCalendar());logger.debug("Logging in user %1$s with birthday %2$tm %2$te,%2$tY", user.getName(), user.getBirthdayCalendar());logger.debug("Integer.MAX_VALUE = %,d", Integer.MAX_VALUE);logger.debug("Long.MAX_VALUE = %,d", Long.MAX_VALUE);注意,如果想使用String.format的形式,需要使用LogManager.getFormatterLogger而不是LogManager.getLogger

使用logger.printf格式化參數

log4j2 的 Logger介面中,還有一個printf方法,無需創建LogManager.getFormatterLogger,就可以使用String.format的形式

logger.printf(Level.INFO, "Logging in user %1$s with birthday %2$tm %2$te,%2$tY", user.getName(), user.getBirthdayCalendar());logger.debug("Opening connection to {}...", someDataSource);「惰性」打日誌(lazy logging)

這個功能雖然小,但非常實用。

在某些業務流程裡,為了留根或追溯問題,需要完整的列印入參,一般是把入參給用JSON/XML序列化後用debug級別列印:

logger.debug("入參報文:{}",JSON.toJSONString(policyDTO));如果需要追溯問題時,會將系統的日誌級別調到debug/trace,這樣就可以列印。但是這裡有個問題,雖然在info級別下debug不會輸出內容,但JSON.toJSONString()這個序列化的程式碼一定會執行,嚴重影響正常流程下的執行效率。

我們期望的結果是info級別下,連序列化都不執行。這裡可以通過isDebugEnable來判斷當前配置下debug級別是否可以輸出:

if(logger.isDebugEnabled()){ logger.debug("入參報文:{}",JSON.toJSONString(policyDTO));}這樣雖然可以避免不必要的序列化,但每個地方都這麼寫還是有點難受的,一行變成了三行。

log4j2 的 logger 物件,提供了一系列lambda的支援,通過這些介面可以實現「惰性」打日誌:

voiddebug(String message, Supplier<?>... paramSuppliers);voidinfo(String message, Supplier<?>... paramSuppliers);voidtrace(String message, Supplier<?>... paramSuppliers);voiderror(String message, Supplier<?>... paramSuppliers);//等同於下面的先判斷,後列印logger.debug("入參報文:{}",() -> JSON.toJSONString(policyDTO));if(logger.isDebugEnabled()){logger.debug("入參報文:{}",JSON.toJSONString(policyDTO));}這種 Supplier + Lambda 的形式,等同於上面的先判斷 isDebugEnable 然後列印,三行的程式碼變成了一行。嗯,真香。不會 Lambda?可以關注公眾號Java技術棧,在後臺回覆:java,可以獲取我整理的 Java 8+ 系列教程,非常齊全。

更簡化的配置

Log4j 2 同時支援XML/JSON/YML/Properties 四種形式的配置檔案,不過最主流的還是XML的方式,最直觀。Spring Boot 日誌整合,推薦看下。

來看一下logback和log4j2的配置檔案對比,同樣功能的配置下:

logback.xml

<?xml version="1.0" encoding="UTF-8"?><configuration><appendername = "File"class= "ch.qos.logback.core.rolling.RollingFileAppender"><file>logs/app.log</file><rollingPolicyclass="ch.qos.logback.core.rolling.TimeBasedRollingPolicy"><fileNamePattern>logs/archives/app-%d{yyyy-MM-dd}.log.gz</fileNamePattern><!--一天內大於size就單獨分隔--><maxFileSize>1 GB</maxFileSize></rollingPolicy></appender><rootlevel="info"><appender-refref="File"/></root></configuration>log4j2.xml

<?xml version="1.0" encoding="UTF-8"?><Configurationxmlns:xi="http://www.w3.org/2001/XInclude"status="warn"name="XInclude"><Appenders><RollingFilename="File"fileName="logs/app.log"filePattern="logs/archives/app-%d{yyyy-MM-dd}-%i.log.gz"><PatternLayoutpattern="%d{yyyy-MM-dd HH:mm:ss.SSS} %5p [%t] %-40.40c{1.} : %m%n"/><Policies><TimeBasedTriggeringPolicy /><!--一天內大於size就單獨分隔--><SizeBasedTriggeringPolicysize="1 GB"/></Policies></RollingFile></Appenders><Loggers><Rootlevel="INFO"><AppenderRefref="File"/></Root></Loggers></Configuration>在log4j2中,appender的配置從使用 Appender 實現名即標籤名的形式,語法上更簡潔一些:

<RollingFilename="File"><!-- 等同於logback中的 --><appendername = "File"class= "ch.qos.logback.core.rolling.RollingFileAppender">與其他日誌抽象/門面適配

log4j2 由於拆分為 API 和 實現兩部分,所以可能也需要和其他日誌框架進行適配,詳細的日誌框架適配方案請參考我的另一篇文章。

其他的特點

非同步佇列使用高效能佇列 - LMAX DisruptorAppender豐富,有JMS/JPA/KAFKA/Http/MONGODB/CouchDB/Socket/Script等各種Appender的支援支援自定義日誌級別……基本用法

終於介紹完了Log4j2的強大,現在來介紹下Log4j2的基本使用。

引用log4j2的maven依賴

log4j-api在log4j-core中已經有依賴了,直接依賴core即可

<dependency><groupId>org.apache.logging.log4j</groupId><artifactId>log4j-core</artifactId><version>2.14.1</version></dependency>注意,引用log4j2時,需要注意項目中是否有多套日誌框架共存/衝突,需要適配的問題。具體可以參考:全網最全 Java 日誌框架適配方案,細節請參考上面的與其他日誌抽象/門面適配。

配置檔案示例

首先是配置檔案,預設的配置檔案路徑為:classpath:log4j2.xml(推薦使用xml)

<?xml version="1.0" encoding="UTF-8"?><Configurationxmlns:xi="http://www.w3.org/2001/XInclude"status="warn"name="XInclude"><Properties><Propertyname="PATTERN"value="%d{yyyy-MM-dd HH:mm:ss.SSS} %5p [%t] %-40.40c{1.} : %m%n"/></Properties><Appenders><!-- 輸出到控制檯,僅在本地開發環境使用 --><Consolename="Console"target="SYSTEM_OUT"><PatternLayoutpattern="${PATTERN}"/></Console><!--輸出到日誌檔案,滾動分割日誌檔案,自動打包gz--><RollingFilename="File"fileName="logs/app.log"filePattern="logs/archives/app-%d{yyyy-MM-dd}-%i.log.gz"><PatternLayoutpattern="${PATTERN}"/><Policies><!--預設一天一個檔案--><TimeBasedTriggeringPolicy /><!--一天內大於size就單獨分隔--><SizeBasedTriggeringPolicysize="1 GB"/></Policies></RollingFile></Appenders><Loggers><!-- 新增你的自定義logger,一般用於區分包名的日誌,不同包名不同的級別/appender --><!-- additivity 意思是,呼叫完當前appender,是否繼續呼叫parent logger appender,預設true--><Loggername="your logger/package name"level="debug"additivity="false"/><!--預設的Root Logger 級別--><Rootlevel="INFO"><!--這裡需要區分下環境(配合maven profile之類的)--><!-- 開發環境使用Console Appender,生產環境使用File Appender --><AppenderRefref="Console"/><AppenderRefref="File"/></Root></Loggers></Configuration>XML配置檔案語法

<?xml version="1.0" encoding="UTF-8"?>;<Configuration><Properties><Propertyname="name1">value</property><Propertyname="name2"value="value2"/></Properties><filter... /><Appenders><appender... ><filter... /></appender> ...</Appenders><Loggers><Loggername="name1"><filter... /></Logger> ...<Rootlevel="level"><AppenderRefref="name"/></Root></Loggers></Configuration>創建Logger

直接使用log4j2的api:

import org.apache.logging.log4j.LogManager;import org.apache.logging.log4j.Logger;Logger logger = LogManager.getLogger(Log4j2Test.class);logger.error(...);logger.warn(...);logger.info(...);logger.debug(...);logger.trace(...);如果是配合slf4j使用也是可以的,只需要按照前面說的,提前做好適配,然後使用slf4j的api即可。不過如果是新系統的話,建議直接上log4j2的api吧,可以享受所有log4j2的功能,使用slf4j之類的api時,上面說的參數格式化之類的功能就無法使用了。

全非同步配置(重要!!)

推薦配置log4j2全非同步(all async),在你的啟動指令碼中增加一個系統變數的配置:

-Dlog4j2.contextSelector=org.apache.logging.log4j.core.async.AsyncLoggerContextSelector總結

Log4j2 如今效能最強,功能最強,而且持續更新維護。

還在等什麼?是時候替換你的logback/log4j1了!

作者:空無連結:https://juejin.cn/post/6945753017878577165


IT145.com E-mail:sddin#qq.com