在早期的 Java 開發中,log4j 雖然功能強大,但配置複雜,特別是在 Tomcat 環境下設定日誌檔案的相對路徑常讓開發者頭痛。此外,使用 main 方法進行單元測試時,log4j 有時也無法正常運作。為了解決這些痛點,筆者決定自行實作一個輕量級的 Logger——MyLogger,提供類似 log4j 的基本介面,卻擁有更簡單的配置方式。
HashMap 快取 Logger 實例,確保同名 Logger 僅建立一次。debug、info、warn、error 四個等級。SystemPath 類別自動取得專案根目錄下的 WEB-INF/logs/ 路徑。package net.ftiger.mis.tools.log;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.Date;
import java.util.HashMap;
import net.ftiger.mis.tools.SystemPath;
public class MyLogger {
private static HashMap instances = null;
private String name;
private static String logFilePath = "d:\\"; // 預設路徑,稍後會被覆寫
private static int level = 0; // 預設記錄所有等級
private static String[] sLevel = {"debug", "info ", "warn ", "error"};
private static String[] sColor = {"#eeeeee", "green", "yellow", "red"}; // 保留欄位,可擴充 HTML 顏色
private MyLogger(String name) {
this.name = name;
}
// ... 以下省略 getter 方法
}
public synchronized static MyLogger getLogger(Class cls) {
return getLogger(cls.getName());
}
public synchronized static MyLogger getLogger(String name) {
if (instances == null) {
init();
}
Object obj = instances.get(name);
if (obj == null) {
MyLogger instance = new MyLogger(name);
instances.put(name, instance);
return instance;
}
return (MyLogger) obj;
}
private static void init() {
instances = new HashMap();
// 關鍵:自動解析專案部署路徑下的 logs 目錄
logFilePath = SystemPath.getSystemPath("\\WEB-INF\\logs\\");
}
private void log(String message, int iLevel) {
if (iLevel < level) return; // 等級過濾
Date now = new Date();
StringBuffer sLog = new StringBuffer();
sLog.append(now.toLocaleString());
sLog.append(" [" + sLevel[iLevel] + "] ");
sLog.append(message);
sLog.append("[" + name + "]:");
// 1. 輸出至 Console
System.out.println(sLog.toString());
// 2. 輸出至檔案 (HTML 格式)
if (logFilePath == null) return;
// 檔案命名規則:年_月_日log.htm
String logFileName = logFilePath + now.getYear() + "_" + (now.getMonth() + 1) + "_" + now.getDate() + "log.htm";
File logFile = new File(logFileName);
if (!logFile.exists()) {
createNewFile(logFileName); // 建立帶有基本 HTML 結構的新檔案
}
try (FileWriter writer = new FileWriter(logFileName, true);
PrintWriter out = new PrintWriter(writer)) {
out.println("<br>");
out.println(now.toLocaleString() + "<br>");
out.println(name + "<br>");
out.println("[" + sLevel[iLevel] + "]<br>");
out.println(message + "<br>");
} catch (IOException e) {
e.printStackTrace(System.out);
}
}
private void createNewFile(String fileName) {
try (FileWriter writer = new FileWriter(fileName, false);
PrintWriter out = new PrintWriter(writer)) {
out.println("<html><head><title>Log File</title></head><body>");
// 可在此加入 CSS 樣式
out.println("</body></html>");
} catch (IOException e) {
e.printStackTrace(System.out);
}
}
public void debug(String message) { log(message, 0); }
public void info(String message) { log(message, 1); }
public void warn(String message) { log(message, 2); }
public void error(String message) { log(message, 3); }
public class MyTest {
private static MyLogger log = MyLogger.getLogger(MyTest.class);
public static void main(String[] args) {
log.debug("除錯訊息");
log.info("一般資訊");
log.warn("警告訊息");
log.error("錯誤訊息");
}
}
| 優點 | 缺點 |
|---|---|
| 零配置檔案,開箱即用 | 非執行緒安全的 HashMap(高併發下建議改用 ConcurrentHashMap) |
| 自動處理相對路徑問題 | 缺乏滾動檔案、非同步寫入等進階功能 |
| HTML 輸出方便瀏覽器直接查看 | 效能未優化,頻繁 I/O 可能影響主執行緒 |
現代建議:目前專案請直接使用 SLF4J + Logback 或 Log4j 2,它們解決了上述所有問題,且支援非同步記錄、動態配置熱載入、結構化日誌(JSON)等企業級特性。本範例僅供教學參考,理解 Logger 內部運作機制。