前言:為什麼選擇 Log4j?
- 外部化配置:修改配置檔即可控制輸出目的地、層級、格式,無需改代碼重編譯
- 開發/生產環境隔離:開發期 DEBUG 追蹤,生產關閉或僅 ERROR,消除
System.out.println 垃圾代碼
- 統一日誌規範:全系統統一機制,利於維運與審計
一、配置檔核心組件詳解
完整配置範例
# 根記錄器:預設 DEBUG 級別,輸出到 A1 (Console)
log4j.rootLogger=DEBUG, A1
# 分層記錄器:DAO 層輸出到 A2(檔案) 和 A4(總日誌)
log4j.logger.DAO=DEBUG, A2, A4
# 業務層輸出到 A3(檔案) 和 A4(總日誌)
log4j.logger.Business=DEBUG, A3, A4
# A1: ConsoleAppender - 標準輸出
log4j.appender.A1=org.apache.log4j.ConsoleAppender
log4j.appender.A1.layout=org.apache.log4j.PatternLayout
log4j.appender.A1.layout.ConversionPattern=%-5p [%t] %37c %3x - %m%n
# A2: DailyRollingFileAppender - DAO 專用日誌,按天滾動
log4j.appender.A2=org.apache.log4j.DailyRollingFileAppender
log4j.appender.A2.File=logs/DAOLog
log4j.appender.A2.DatePattern='.'yyyy-MM-dd
log4j.appender.A2.layout=org.apache.log4j.PatternLayout
log4j.appender.A2.layout.ConversionPattern=[%-5p] %d{yyyy-MM-dd HH:mm:ss,SSS} method:%l%n%m%n
# A3: DailyRollingFileAppender - 業務層專用日誌
log4j.appender.A3=org.apache.log4j.DailyRollingFileAppender
log4j.appender.A3.File=logs/BusinessLog
log4j.appender.A3.DatePattern='.'yyyy-MM-dd
log4j.appender.A3.layout=org.apache.log4j.PatternLayout
log4j.appender.A3.layout.ConversionPattern=[%-5p] %d{yyyy-MM-dd HH:mm:ss,SSS} method:%l%n%m%n
# A4: DailyRollingFileAppender - 全量日誌匯總
log4j.appender.A4=org.apache.log4j.DailyRollingFileAppender
log4j.appender.A4.File=logs/allLog
log4j.appender.A4.DatePattern='.'yyyy-MM-dd
log4j.appender.A4.layout=org.apache.log4j.PatternLayout
log4j.appender.A4.layout.ConversionPattern=[%-5p] %d{yyyy-MM-dd HH:mm:ss,SSS} method:%l%n%m%n
二、Appender 選型指南
| Appender | 用途 | 關鍵配置 |
| ConsoleAppender | 開發除錯、容器標準輸出 | 無檔案配置 |
| FileAppender | 固定檔案持久化 | File=path,單檔無限增長需配合外部 logrotate |
| DailyRollingFileAppender | 按時間滾動(推薦生產) | DatePattern:'.'yyyy-MM-dd(每天)、'.'yyyy-MM(每月) |
| RollingFileAppender | 按大小滾動 | MaxFileSize, MaxBackupIndex |
DailyRollingFileAppender 關鍵細節
- 滾動動作發生在首次日誌寫入時檢測日期變更
- 舊檔重命名加後綴(如
DAOLog.2024-01-15),新檔繼續寫入
- 不支援壓縮、刪除舊檔,需外部腳本配合
三、Layout 與 ConversionPattern 完整參考
常用轉換字元
| 符號 | 含義 | 範例輸出 |
| %p | 優先級 | DEBUG, INFO, ERROR |
| %d{pattern} | 時間戳 | %d{yyyy-MM-dd HH:mm:ss,SSS} → 2024-01-15 14:30:45,123 |
| %t | 線程名稱 | main, http-nio-8080-exec-1 |
| %c | Logger 名稱(類別全名) | com.example.dao.UserDao |
| %l | 呼叫位置(類.方法:行號) | com.example.dao.UserDao.select:42 |
| %m | 日誌訊息 | 實際 log 內容 |
| %n | 平台換行符 | \n (Linux) / \r\n (Windows) |
| %x | NDC (巢狀診斷上下文) | 用戶會話追蹤 ID |
| %% | 字面 % 符號 | % |
三種實用 Pattern 範例
# 1. 精簡:時間 + 訊息
%d{yyyy-MM-dd HH:mm:ss,SSS} %m%n
# 2. 除錯:時間 + 呼叫位置 + 訊息
%d{yyyy-MM-dd HH:mm:ss,SSS} %l "#" %m%n
# 3. 生產標準:級別 + 時間 + 位置 + 訊息 (多行易閱讀)
[%-5p] %d{yyyy-MM-dd HH:mm:ss,SSS} method:%l%n%m%n
四、代碼整合三步驟
1. 系統啟動時初始化(僅一次)
// Web 專案:在 ServletContextListener 或 ActionServlet.init() 中
PropertyConfigurator.configure("log4j.properties");
// 或 classpath 絕對路徑
PropertyConfigurator.configure(Thread.currentThread().getContextClassLoader().getResource("log4j.properties"));
2. 取得 Logger 實例(靜態欄位,類別級)
public class UserDao {
private static final Logger log = Logger.getLogger("DAO"); // 對應配置 log4j.logger.DAO
public List<User> findAll() {
String sql = "SELECT * FROM users";
log.debug("執行查詢: {}", sql); // SLF4J 風格佔位符需 log4j 1.2.17+ 或 SLF4J 橋接
// ...
}
}
3. 使用適當級別記錄
log.debug("詳細除錯資訊"); // 開發期
log.info("關鍵業務節點"); // 生產可見
log.warn("潛在問題,需關注"); // 如重試、降級
log.error("業務失敗、異常", e); // 含異常堆疊
log.fatal("系統嚴重錯誤"); // 需立即介入
五、關鍵最佳實踐與避坑指南
✅ 正確做法
- Logger 宣告為
private static final,避免重複建立
- 使用
Logger.getLogger(ThisClass.class) 自動對應類別名,利於追蹤
- 異常記錄必傳
throwable 參數:log.error("msg", e)
- 生產環境根級別設
INFO 或 WARN,關鍵模組單獨開 DEBUG
- 配置檔放 classpath 根目錄,
configure() 無參數自動載入
❌ 常見錯誤
| 錯誤 | 後果 | 修正 |
每次建立物件時呼叫 configure() | 重複初始化、效能災難、DailyRolling 失效 | 系統啟動單次初始化 |
使用 Logger.getLogger("任意字串") 無規範 | 配置管理混亂、無法按模組控制級別 | 統一用類別名或模組名 |
| 大量字串拼接在 debug() 參數中 | 即使不輸出也執行拼接,效能損耗 | 用 SLF4J 佔位符 log.debug("id={}", id) 或 if (log.isDebugEnabled()) |
| 未配置 Additivity 導致重複輸出 | 同一日誌同時輸出到根與子 Appender | log4j.additivity.DAO=false 關閉向上傳遞 |
🔧 知名 Bug:DailyRollingFileAppender 多進程寫入
多 JVM 進程同時寫入同一日誌檔案會導致資料損毀或遺失。解決方案:
- 每進程獨立日誌檔(檔名含 PID/主機名)
- 使用
SocketAppender 集中收集
- 升級 Log4j 2.x(
RollingFileAppender 支援檔案鎖)
六、遷移建議:Log4j 1.x → Log4j 2.x / SLF4J + Logback
- Log4j 1.x 已停止維護(2015 EOL),存在安全風險(CVE-2021-44228 Log4Shell)
- 新專案直接使用 SLF4J API + Logback 實作:原生支援參數化日誌、非阻塞異步寫入、更豐富的滾動策略
- 舊專案遷移:引入
log4j-over-slf4j 橋接,逐步替換配置語法