首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> Java 程式設計 >> Java 相關知識

手寫輕量級 Java 日誌工具類:Logger 實作解析

     編輯:Java 相關知識

設計目標

  • 零依賴,僅使用 JDK 標準庫。
  • 自動建立以日期時間命名的日誌檔(log/yyyy-MM-dd-HH-mm-ss.log)。
  • 支援字串與異常兩種記錄方式。
  • 執行緒安全寫入。
  • 可選擇同步輸出至 Console。

核心原始碼

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、關閉鉤子等
}

關鍵設計細節解析

  1. 雙重檢查鎖定:init() 與 writeLog() 皆用 synchronized,確保多執行緒環境下單一實例與寫入序列化。
  2. 呼叫者類名解析:利用 new Throwable().getStackTrace() 取得堆疊,索引 3 為實際呼叫 Logger.log() 的類別,避免硬編碼類名。
  3. 時間格式統一:檔名用精確到秒避免衝突;日誌行用標準 ISO-like 格式便於解析。
  4. 資源管理:採用追加模式 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;
        }
    }
}

局限與改進方向

  • 無日誌等級(DEBUG/INFO/WARN/ERROR),所有訊息同等對待。
  • 無檔案滾動策略,長時間運行檔案會無限增大。
  • 同步寫入可能成為高併發瓶頸;可改用非同步佇列(如 ArrayBlockingQueue + 背景執行緒)批次落盤。
  • 建議正式專案直接採用 SLF4J + Logback/Log4j2,功能完備且效能優異。

總結

這個 Logger 類別展示了日誌框架的核心雛形:時間戳、呼叫者定位、執行緒安全、持久化。閱讀並理解其原理,有助於深入掌握成熟日誌框架的設計哲學。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved