log/yyyy-MM-dd-HH-mm-ss.log)。package utils;
import java.io.*;
import java.text.SimpleDateFormat;
import java.util.Date;
public final class Logger {
private static String logFilePath;
private static boolean initialized = false;
private static FileWriter writer;
private static boolean printToConsole = true;
private Logger() {} // 防止實例化
private static synchronized boolean init() {
if (initialized) return true;
String dir = System.getProperty("user.dir") + File.separator + "log";
File logDir = new File(dir);
if (!logDir.exists()) logDir.mkdirs();
String fileName = new SimpleDateFormat("yyyy-MM-dd-HH-mm-ss").format(new Date()) + ".log";
logFilePath = dir + File.separator + fileName;
try {
writer = new FileWriter(logFilePath, true); // 追加模式
initialized = true;
return true;
} catch (IOException e) {
System.err.println("日誌初始化失敗:" + e.getMessage());
e.printStackTrace();
return false;
}
}
public static void log(String message) {
if (!initialized) init();
writeLog(resolveCallerClass() + " | " + message);
}
public static void log(Exception e) {
if (!initialized) init();
writeLog(resolveCallerClass() + " | " + e.toString());
}
private static String resolveCallerClass() {
// stack[0]=getStackTrace, [1]=writeLog, [2]=log, [3]=呼叫者
StackTraceElement[] stack = new Throwable().getStackTrace();
return (stack.length > 3) ? stack[3].getClassName() : "Unknown";
}
private static synchronized void writeLog(String content) {
String timestamp = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date());
String line = String.format("[%s] %s%n", timestamp, content);
if (printToConsole) System.out.print(line);
try {
writer.write(line);
writer.flush(); // 確保即時寫入磁碟
} catch (IOException e) {
System.err.println("日誌寫入失敗:" + e.getMessage());
}
}
// 可擴充:設定日誌等級、Rolling、關閉鉤子等
}
init() 與 writeLog() 皆用 synchronized,確保多執行緒環境下單一實例與寫入序列化。new Throwable().getStackTrace() 取得堆疊,索引 3 為實際呼叫 Logger.log() 的類別,避免硬編碼類名。new FileWriter(path, true),程式重啟不覆蓋舊日誌。生產環境建議加入 Runtime.getRuntime().addShutdownHook 關閉 writer。public class OrderService {
public void placeOrder(String item) {
Logger.log("開始下單:" + item);
try {
// 模擬業務邏輯
if (item == null) throw new IllegalArgumentException("商品不可為空");
Logger.log("下單成功:" + item);
} catch (Exception e) {
Logger.log(e); // 記錄異常堆疊
throw e;
}
}
}
ArrayBlockingQueue + 背景執行緒)批次落盤。這個 Logger 類別展示了日誌框架的核心雛形:時間戳、呼叫者定位、執行緒安全、持久化。閱讀並理解其原理,有助於深入掌握成熟日誌框架的設計哲學。