作為 JDK 1.4 就內置的日誌框架,java.util.logging (簡稱 JUL) 無需引入第三方依賴即可使用,雖然功能不如 Log4j2 或 Logback 豐富,但在中小型項目、Android 開發、或受限於無法引入外部 JAR 的環境中,它仍是輕量級日誌記錄的首選。本文將系統講解 JUL 的核心組件:Logger 級別體系、Handler 處理器、Formatter 格式化器,並通過完整代碼示例演示控制台輸出、文件寫入及自定義格式化的實戰技巧。
JUL 定義了比 Log4j 更精細的 7 個標準級別(由高到低):
| 級別常量 | 數值 | 用途場景 |
|---|---|---|
SEVERE | 1000 | 嚴重錯誤,導致程序無法繼續運行 |
WARNING | 900 | 潛在風險,但程序可繼續運行 |
INFO | 800 | 關鍵業務流程節點信息(默認級別) |
CONFIG | 700 | 配置信息,如啟動參數、環境變量 |
FINE | 500 | 一般調試信息 |
FINER | 400 | 詳細調試信息 |
FINEST | 300 | 極度詳細的追蹤信息 |
另有兩個特殊級別:OFF (關閉所有日誌) 和 ALL (開啟所有日誌)。
使用工廠方法獲取 Logger,採用層次化命名空間(建議使用包名風格):
// 獲取/創建名為 "com.example.module" 的 Logger
Logger logger = Logger.getLogger("com.example.module");
// 帶資源包名稱的獲取方式(用於國際化)
Logger logger2 = Logger.getLogger("com.example.module", "com.example.Messages");
重要特性: 同名 Logger 全局單例,不同引用指向同一實例,配置修改將全局生效。
public class LoggerLevelDemo {
public static void main(String[] args) {
// 創建不同級別的 Logger
Logger severeLogger = Logger.getLogger("demo.severe");
severeLogger.setLevel(Level.SEVERE);
Logger warningLogger = Logger.getLogger("demo.warning");
warningLogger.setLevel(Level.WARNING);
Logger infoLogger = Logger.getLogger("demo.info");
infoLogger.setLevel(Level.INFO);
Logger configLogger = Logger.getLogger("demo.config");
configLogger.setLevel(Level.CONFIG);
// 測試日誌記錄行為
infoLogger.info("這是 INFO 級別記錄");
infoLogger.warning("低級別 Logger 能記錄高級別信息嗎?"); // 能輸出
warningLogger.warning("這是 WARNING 級別記錄");
warningLogger.severe("低級別 Logger 能記錄高級別信息嗎?"); // 能輸出
warningLogger.info("高級別 Logger 能記錄低級別信息嗎?"); // 無輸出
severeLogger.severe("這是 SEVERE 級別記錄");
severeLogger.warning("高級別 Logger 能記錄低級別信息嗎?"); // 無輸出
configLogger.config("這是 CONFIG 級別記錄"); // 控制台無輸出(低於默認 INFO)
// 驗證同名 Logger 單例特性
Logger sameLogger = Logger.getLogger("demo.info");
System.out.println("同名 Logger 是否同一實例: " + (infoLogger == sameLogger)); // true
// 動態調整級別驗證
sameLogger.setLevel(Level.SEVERE);
infoLogger.info("調整為 SEVERE 後,INFO 應不輸出"); // 無輸出
sameLogger.setLevel(Level.INFO);
infoLogger.info("恢復 INFO 級別後,INFO 應輸出"); // 有輸出
}
}
Handler 負責將日誌記錄導出到不同目標,JUL 內置以下核心 Handler:
ConsoleHandler:輸出到控制台 (System.err)FileHandler:寫入文件,支持輪轉、大小限制、追加模式SocketHandler:發送到網絡日誌服務MemoryHandler:緩存到內存,滿足條件再刷新StreamHandler:抽象基類,輸出到任意 OutputStreamFileHandler(String pattern, int limit, int count, boolean append)
// pattern: 文件名模式,支持 %h(用戶目錄)、%t(臨時目錄)、%g(輪轉索引)、%u(唯一ID)
// limit: 單個文件最大字節數,超過則輪轉
// count: 輪轉文件數量
// append: 是否追加模式
public class FileLoggingDemo {
public static void main(String[] args) throws IOException {
// 創建日誌目錄
File logDir = new File("f:/logTest", "logDir");
logDir.mkdirs();
File logFile = new File(logDir, "logTest.log");
// 配置 FileHandler:10KB 輪轉,保留 1 個備份,追加模式
FileHandler fileHandler = new FileHandler(logFile.getAbsolutePath(), 10240, 1, true);
fileHandler.setLevel(Level.ALL); // 記錄所有級別
// 創建不同級別 Logger 並添加 Handler
Logger infoLogger = Logger.getLogger("file.info");
infoLogger.setLevel(Level.INFO);
infoLogger.addHandler(fileHandler);
Logger configLogger = Logger.getLogger("file.config");
configLogger.setLevel(Level.CONFIG);
configLogger.addHandler(fileHandler);
// 測試記錄
infoLogger.info("infoLogger 的 INFO 記錄 → 會寫入文件");
infoLogger.config("infoLogger 的 CONFIG 記錄 → 不會寫入(低於 Logger 級別)");
configLogger.info("configLogger 的 INFO 記錄 → 會寫入文件");
configLogger.config("configLogger 的 CONFIG 記錄 → 會寫入文件(Logger 級別允許)");
// 注意:Handler 級別 (ALL) 低於 Logger 級別時,以 Logger 級別為準
// 只有同時滿足 Logger 級別 和 Handler 級別 才會最終輸出
}
}
JUL 默認使用 XMLFormatter 輸出 XML 格式,常用格式化器包括:
SimpleFormatter:簡單文本格式(類似傳統日誌)XMLFormatter:結構化 XML 格式(默認)Formatter:抽象基類,用於自定義格式注意區分: java.util.logging.Formatter (日誌格式化) vs java.util.Formatter (字符串格式化,類似 printf)。
// 自定義格式化器
class CompactLogFormatter extends Formatter {
@Override
public String format(LogRecord record) {
// 格式:類名: 方法名: 級別: 消息\n
return record.getSourceClassName() + ": "
+ record.getSourceMethodName() + ": "
+ record.getLevel() + ": "
+ record.getMessage() + "\n";
}
}
// 使用示例
public class CustomFormatterDemo {
public static void main(String[] args) throws IOException {
Logger logger = Logger.getLogger("custom.format");
logger.setLevel(Level.ALL);
FileHandler fh = new FileHandler("custom.log");
fh.setFormatter(new CompactLogFormatter()); // 設置自定義格式
logger.addHandler(fh);
logger.info("這是一條自定義格式的 INFO 日誌");
logger.warning("這是一條 WARNING 日誌");
}
}
輸出效果:
CustomFormatterDemo: main: INFO: 這是一條自定義格式的 INFO 日誌
CustomFormatterDemo: main: WARNING: 這是一條 WARNING 日誌
在 JRE 目錄 lib/logging.properties 或項目 classpath 下放置配置文件,實現零代碼配置:
# 默認級別
.level=INFO
# ConsoleHandler 配置
handlers=java.util.logging.ConsoleHandler
java.util.logging.ConsoleHandler.level=INFO
java.util.logging.ConsoleHandler.formatter=java.util.logging.SimpleFormatter
# FileHandler 配置
# java.util.logging.FileHandler.pattern=%h/myapp%u.log
# java.util.logging.FileHandler.limit=50000
# java.util.logging.FileHandler.count=1
# java.util.logging.FileHandler.formatter=java.util.logging.XMLFormatter
# 特定包級別控制
com.example.dao.level=FINE
com.example.web.level=WARNING
默認情況下 Logger 會向父 Logger 傳遞日誌(useParentHandlers=true),導致控制台重複輸出。解決方法:
logger.setUseParentHandlers(false); // 禁用父 Handler 傳遞
logger.addHandler(myCustomHandler); // 僅使用自定義 Handler
對於高頻調用的低級別日誌,使用 Supplier<String> 延遲消息構造(Java 8+):
// 只有當級別滿足時才會調用 lambda 構造消息
logger.fine(() -> "昂貴的調試信息構造: " + expensiveComputation());
| 場景 | 推薦方案 |
|---|---|
| 中小型項目、無第三方依賴限制 | 直接使用 JUL,配合 logging.properties |
| 高性能、異步日誌、複雜路由需求 | Log4j2 / Logback + SLF4J |
| Android 應用開發 | JUL (Android 封裝版) 或 Timber |
| 微服務分布式追蹤 | Logback + MDC + ELK/EFK 堆棧 |
掌握 JUL 核心機制不僅能應對受限環境,更能深入理解現代日誌框架的設計思想(Logger/Handler/Formatter 分離架構源於此)。合理配置級別、Handler 與 Formatter,即可構建清晰可維護的日誌體系。