日誌系統是保障產品質量與服務穩定性的關鍵基礎設施。JDK 1.4 引入的 java.util.logging 雖提供了標準化日誌功能,但其靜態日誌級別機制在實際生產環境中暴露出明顯短板:日誌級別需在啟動前確定,若設定過高會錯過關鍵診斷信息;設定過低則產生海量無效日誌,佔用磁盤 I/O 甚至成為性能瓶頸。本文將深入探討如何利用 MemoryHandler 實現智能日誌緩存,在嚴重錯誤發生時自動輸出上下文日誌,並結合 logging.properties 動態配置管理,構建高可用的日誌解決方案。
JDK 將日誌級別劃分為 9 級(OFF、SEVERE、WARNING、INFO、CONFIG、FINE、FINER、FINEST、ALL)。靜態配置導致兩大問題:
理想方案是:平時緩存所有級別日誌,僅在嚴重錯誤(SEVERE)觸發時輸出緩存內容。這正是 MemoryHandler 的設計初衷。
理解 MemoryHandler 需先掌握日誌處理流程:
LogRecord 的鏈式處理器,決定輸出目標(控制台、文件、內存等)。
MemoryHandler 繼承自 Handler,內部維護一個固定大小的循環緩存隊列(LogRecord[] buffer),採用「註冊-通知」觀察者模式:
logger.addHandler(handler))。publish(LogRecord) 寫入緩存。pushLevel,如 SEVERE)時,調用 push() 將緩存轉傳給目標 Handler(如 FileHandler),並清空緩存。
size:緩存容量(默認 1000 條)。pushLevel:觸發轉储的級別閾值(默認 SEVERE)。target:目標 Handler(如文件、網絡)。
// 直接使用 JDK MemoryHandler
int bufferSize = 1000;
FileHandler fileHandler = new FileHandler("app.log");
MemoryHandler memoryHandler = new MemoryHandler(fileHandler, bufferSize, Level.SEVERE);
Logger logger = Logger.getLogger("com.example.app");
logger.addHandler(memoryHandler);
// 自定義 Filter:僅緩存含異常的記錄
memoryHandler.setFilter(record -> record.getThrown() != null);
反射訪問私有字段(適用於低頻訪問):
Field sizeField = getClass().getSuperclass().getDeclaredField("size");
sizeField.setAccessible(true);
sizeField.setInt(this, configuredSize);
完全重寫存儲結構(適用於高性能/特殊數據結構需求):
public class MyMemoryHandler extends Handler {
private int size = 1000;
private LogRecord[] buffer;
// 實現 publish、flush、close 等方法
}
Logger、Handler、LogRecord 三層 Level 過濾疊加,可能導致預期外的日誌丟失。setUseParentHandlers(true) 會將日誌複製到父 Logger 的 Handler,若父子 Level 不一致,輸出會不一致。建議:明確配置每個 Logger 的 Level 與 Handler,避免依賴繼承。

在 Handler 內部記錄錯誤若直接調用 Logger 會導致無限遞歸。JDK 提供 ErrorManager 機制:
public class MyHandler extends Handler {
public MyHandler() {
setErrorManager(new ErrorManager() {
public void error(String msg, Exception ex, int code) {
System.err.println("Handler Error: " + msg + ex.getMessage());
}
});
}
public void publish(LogRecord record) {
if (!isLoggable(record)) return;
try {
// 可能拋出異常的操作
} catch (Exception e) {
reportError("Publish failed", e, ErrorManager.WRITE_FAILURE);
}
}
}
實現 PropertyChangeListener,注冊到 LogManager,可實時捕獲配置增刪改:
LogManager.getLogManager().addPropertyChangeListener(event -> {
if (event.getSource() instanceof LogManager) {
LogManager manager = (LogManager) event.getSource();
// 比對 oldProps、newProps,提取 changedProps
// 執行業務邏輯(如記錄審計日志、熱更新 Handler 配置)
}
});
在 execute() 中解析自定義屬性,動態設置 Handler 行為:
if (prop.endsWith(".useParentLevel")) {
String loggerName = prop.substring(0, prop.lastIndexOf("."));
Logger logger = Logger.getLogger(loggerName);
for (Handler h : logger.getHandlers()) {
if (h instanceof TestMemoryHandler) {
((TestMemoryHandler) h).setUseParentLevel(newVal);
((TestMemoryHandler) h).addLogger(logger); // 保存 Logger 引用以便轉發
break;
}
}
}
readConfiguration() 會重置整個 Log 系統,導致 MemoryHandler 實例重建、緩存丟失。三種應對策略:
LogManager 覆寫 readConfiguration(),保留特定 Handler 實例並僅更新屬性。通過 MemoryHandler 的循環緩存與條件觸發轉儲機制,結合動態配置管理與自定義擴展,可構建「平時靜默、故障全文」的智能日誌系統:
此方案純基於 JDK 標準 API,零第三方依賴,適合對穩定性要求極高的企業級應用。