Log4j 2.x 相較 1.x 具備顯著優勢:
dependencies {
implementation "org.apache.logging.log4j:log4j-api:2.23.1"
implementation "org.apache.logging.log4j:log4j-core:2.23.1"
implementation "com.lmax:disruptor:3.4.4" // 非同步支援
}
<dependencies>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-api</artifactId>
<version>2.23.1</version>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-core</artifactId>
<version>2.23.1</version>
</dependency>
<dependency>
<groupId>com.lmax</groupId>
<artifactId>disruptor</artifactId>
<version>3.4.4</version>
</dependency>
</dependencies>
放置於 src/main/resources/log4j2.xml:
<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="OFF" monitorInterval="30">
<!-- 變數定義 -->
<Properties>
<Property name="logPath">/var/log/myapp</Property>
<Property name="logPattern">%d{yyyy-MM-dd HH:mm:ss.SSS} [%t] %-5level %logger{36} %L %M - %msg%xEx%n</Property>
<Property name="consolePattern">%d{HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n</Property>
</Properties>
<Appenders>
<!-- 控制台輸出 -->
<Console name="Console" target="SYSTEM_OUT">
<ThresholdFilter level="INFO" onMatch="ACCEPT" onMismatch="DENY"/>
<PatternLayout pattern="${consolePattern}"/>
</Console>
<!-- INFO/WARN 滾動檔案(按大小+時間) -->
<RollingRandomAccessFile name="InfoFile"
fileName="${logPath}/app-info.log"
filePattern="${logPath}/archive/%d{yyyy-MM}/app-info-%d{MM-dd-yyyy}-%i.log.gz"
immediateFlush="false">
<PatternLayout pattern="${logPattern}"/>
<Policies>
<TimeBasedTriggeringPolicy interval="1" modulate="true"/>
<SizeBasedTriggeringPolicy size="100 MB"/>
</Policies>
<Filters>
<ThresholdFilter level="ERROR" onMatch="DENY" onMismatch="NEUTRAL"/>
<ThresholdFilter level="INFO" onMatch="ACCEPT" onMismatch="DENY"/>
</Filters>
<DefaultRolloverStrategy max="30"/>
</RollingRandomAccessFile>
<!-- ERROR 獨立檔案 -->
<RollingRandomAccessFile name="ErrorFile"
fileName="${logPath}/app-error.log"
filePattern="${logPath}/archive/%d{yyyy-MM}/app-error-%d{MM-dd-yyyy}-%i.log.gz"
immediateFlush="false">
<PatternLayout pattern="${logPattern}"/>
<Policies>
<TimeBasedTriggeringPolicy interval="1" modulate="true"/>
<SizeBasedTriggeringPolicy size="100 MB"/>
</Policies>
<Filters>
<ThresholdFilter level="ERROR" onMatch="ACCEPT" onMismatch="DENY"/>
</Filters>
<DefaultRolloverStrategy max="30"/>
</RollingRandomAccessFile>
</Appenders>
<Loggers>
<!-- 根記錄器:非同步模式 -->
<AsyncRoot level="INFO" additivity="false">
<AppenderRef ref="Console"/>
<AppenderRef ref="InfoFile"/>
<AppenderRef ref="ErrorFile"/>
</AsyncRoot>
<!-- 特定包別級別控制 -->
<AsyncLogger name="com.myapp.dao" level="DEBUG" additivity="false">
<AppenderRef ref="Console"/>
<AppenderRef ref="InfoFile"/>
<AppenderRef ref="ErrorFile"/>
</AsyncLogger>
</Loggers>
</Configuration>
| 配置項 | 說明 |
|---|---|
status="OFF" | 關閉 Log4j2 內部除錯輸出,生產環境建議 OFF 或 ERROR |
monitorInterval="30" | 啟用熱重載,每 30 秒檢查配置變更 |
RollingRandomAccessFile | 高效能隨機存取檔案,配合 Disruptor 實現非同步寫入 |
immediateFlush="false" | 非同步模式下關閉立即刷新,由 Disruptor 批次寫入 |
AsyncRoot/AsyncLogger | 啟用非同步記錄器,**必須**有 Disruptor 依賴 |
additivity="false" | 禁止向父 Logger 傳遞,避免重複記錄 |
package com.myapp.util;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
public final class L {
private static final Logger LOG = LogManager.getLogger();
private L() { throw new UnsupportedOperationException(); }
public static Logger getLogger() { return LOG; }
public static void trace(String msg) { LOG.trace(msg); }
public static void debug(String msg) { LOG.debug(msg); }
public static void info(String msg) { LOG.info(msg); }
public static void warn(String msg) { LOG.warn(msg); }
public static void error(String msg) { LOG.error(msg); }
public static void error(String msg, Throwable t) { LOG.error(msg, t); }
// 支援佔位符
public static void info(String fmt, Object... args) { LOG.info(fmt, args); }
public static void error(String fmt, Object... args) { LOG.error(fmt, args); }
}
@RestController
public class DemoController {
@GetMapping("/log-test")
public String test() {
String msg = "Spring Boot + Log4j2 非同步日誌測試";
L.trace(msg);
L.debug(msg);
L.info(msg);
L.warn(msg);
L.error(msg);
L.info("帶參數日誌:userId={}, action={}", 1001, "LOGIN");
return "OK";
}
}
TRACE < DEBUG < INFO < WARN < ERROR < FATAL
配置 level="INFO" 時,只有 INFO、WARN、ERROR 會輸出。控制台輸出範例:
14:32:15.123 [http-nio-8080-exec-1] INFO com.myapp.DemoController - Spring Boot + Log4j2 非同步日誌測試 14:32:15.124 [http-nio-8080-exec-1] WARN com.myapp.DemoController - Spring Boot + Log4j2 非同步日誌測試 14:32:15.124 [http-nio-8080-exec-1] ERROR com.myapp.DemoController - Spring Boot + Log4j2 非同步日誌測試
/var/log/myapp/
├── app-info.log
├── app-error.log
└── archive/
└── 2024-01/
├── app-info-01-15-2024-1.log.gz
└── app-error-01-15-2024-1.log.gz
@Component
public class TraceFilter implements Filter {
@Override
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) {
String traceId = UUID.randomUUID().toString().replace("-", "");
MDC.put("traceId", traceId);
try { chain.doFilter(req, res); }
finally { MDC.remove("traceId"); }
}
}
Pattern 中加入 %X{traceId} 即可在日誌中串聯完整請求鏈路。
application.yml 中指定配置檔位置:
logging:
config: classpath:log4j2.xml
json-file 或 fluentd 驅動採集Log4j2 + Disruptor 非同步日誌是高併發系統的標配。核心要點:
log4j-api、log4j-core、disruptor 三依賴RollingRandomAccessFile + AsyncRoot/AsyncLogger官方文檔:https://logging.apache.org/log4j/2.x/