現代 Java 應用普遍採用 日誌門面(Facade)+ 日誌實現(Implementation) 的雙層架構:
主流組合推薦: SLF4J + Log4j2。Log4j2 是 Log4j 1.x 與 Logback 的繼任者,性能更強(基於 LMAX Disruptor 的異步日誌)、功能更豐富(插件化架構、無垃圾回收壓力)、配置更靈活(支持 JSON、YAML、XML 多種格式)。
<dependencies>
<!-- SLF4J API 門面 -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>2.0.9</version>
</dependency>
<!-- Log4j2 核心實現 + SLF4J 綁定 -->
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-slf4j2-impl</artifactId>
<version>2.20.0</version>
</dependency>
<!-- 兼容舊版 Log4j 1.x API (可選) -->
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-1.2-api</artifactId>
<version>2.20.0</version>
</dependency>
<!-- 異步日誌核心依賴:LMAX Disruptor -->
<dependency>
<groupId>com.lmax</groupId>
<artifactId>disruptor</artifactId>
<version>3.4.4</version>
</dependency>
</dependencies>
注意:Log4j2 2.17.0 之後修復了 Log4Shell (CVE-2021-44228) 等嚴重漏洞,生產環境務必升級至 2.17.0+,建議使用最新穩定版。
配置文件通常置於 src/main/resources 目錄下。核心組件三大件:
<Console name="Console" target="SYSTEM_OUT">
<PatternLayout pattern="%d{yyyy-MM-dd HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n"/>
</Console>
<File name="FileLog" fileName="logs/app.log" append="true">
<PatternLayout pattern="%d{yyyy-MM-dd HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n"/>
</File>
<RollingFile name="RollingFile" fileName="logs/app.log"
filePattern="logs/$${date:yyyy-MM}/app-%d{MM-dd-yyyy}-%i.log.gz">
<PatternLayout pattern="%d{yyyy-MM-dd HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n"/>
<Policies>
<TimeBasedTriggeringPolicy/> <!-- 每天滾動 -->
<SizeBasedTriggeringPolicy size="100MB"/> <!-- 或超過 100MB 滾動 -->
</Policies>
<DefaultRolloverStrategy max="30"/> <!-- 最多保留 30 個歸檔 -->
</RollingFile>
| 佔位符 | 含義 |
|---|---|
| %d{pattern} | 時間,如 %d{yyyy-MM-dd HH:mm:ss.SSS} |
| %t | 線程名稱 |
| %-5level | 日誌級別,左對齊寬度 5 |
| %logger{36} | Logger 名稱,最多 36 字符精簡 |
| %msg 或 %m | 日誌消息 |
| %ex 或 %xEx | 異常堆棧信息 |
| %L | 行號(慎用,影響性能) |
| %M | 方法名(慎用,影響性能) |
<Loggers>
<!-- 根 Logger,默認級別 ERROR,輸出到 Console 和 RollingFile -->
<Root level="ERROR">
<AppenderRef ref="Console"/>
<AppenderRef ref="RollingFile"/>
</Root>
<!-- 業務包 DEBUG 級別,additivity=false 表示不繼承 Root 的 Appender,避免重複輸出 -->
<Logger name="com.myapp.business" level="DEBUG" additivity="false">
<AppenderRef ref="Console"/>
<AppenderRef ref="RollingFile"/>
</Logger>
<!-- 第三方庫僅輸出 WARN 以上 -->
<Logger name="org.springframework" level="WARN"/>
<Logger name="org.hibernate" level="WARN"/>
</Loggers>
關鍵屬性: additivity="false" 可阻斷向父 Logger 傳遞事件,防止日誌重複打印;includeLocation="true" 會啟用行號/方法名解析,極大降低性能,生產環境建議關閉。
同步日誌下,業務線程需等待日誌寫入磁盤/網絡才能返回,嚴重拖慢響應。Log4j2 提供兩種異步模式:
ArrayBlockingQueue 緩存日誌事件。<Async name="AsyncConsole">
<AppenderRef ref="Console"/>
<!-- bufferSize 默認 128,高併發建議調大,如 8192 -->
<Property name="bufferSize">8192</Property>
</Async>
disruptor 依賴。啟動參數添加:-DLog4jContextSelector=org.apache.logging.log4j.core.async.AsyncLoggerContextSelector
配置文件無需修改,自動生效。
<Configuration status="WARN">
<Appenders>
<RollingFile name="AsyncFile" fileName="logs/async.log" ...>
<PatternLayout pattern="%d %p %c{1.} [%t] %m %ex%n"/>
</RollingFile>
</Appenders>
<Loggers>
<!-- 特定包使用 AsyncLogger -->
<AsyncLogger name="com.myapp.highperf" level="DEBUG" includeLocation="false">
<AppenderRef ref="AsyncFile"/>
</AsyncLogger>
<Root level="ERROR">
<AppenderRef ref="AsyncFile"/>
</Root>
</Loggers>
</Configuration>
| 優勢 | 劣勢/注意事項 |
|---|---|
| 極高吞吐量,微秒級延遲 | 異常丟失:業務線程無法感知日誌寫入失敗,需配置 ExceptionHandler |
| 降低 GC 壓力 (無垃圾模式) | 可變對象風險:若日志消息包含可變對象 (如 MapMessage) 且在異步處理前被修改,輸出可能不一致。建議:僅傳遞不可變數據或基本類型,或使用 lambda 延遲計算。 |
| 應對流量洪峰緩衝能力強 | 位置信息 (%L, %M, %class 等) 獲取極慢,異步模式下性能損耗放大 4-20 倍,生產環境嚴禁開啟 includeLocation。 |
slf4j-api,代碼中使用 LoggerFactory.getLogger(),禁止直接依賴 Log4j2 API。INFO 或 WARN,核心業務包開啟 DEBUG,第三方庫限制在 WARN/ERROR。JsonLayout 輸出 JSON 格式,便於 ELK/Splunk 等日誌平台解析檢索。%L、%M、%class 等位置佔位符。logger.debug("User {} logged in from {}", userId, ip) 而非字符串拼接,避免不必要的字符串構建開銷。合理配置日誌框架,不僅能保障系統可觀測性,更能在高併發場景下避免日誌成為性能瓶頸。Log4j2 + AsyncLogger + Disruptor 是當前 Java 生態中性能與功能平衡最優的選擇。