CodeGym /課程 /JAVA 25 SELF /日誌格式與層級:best practices

日誌格式與層級:best practices

JAVA 25 SELF
等級 63, 課堂 1
開放

1. 日誌訊息的結構

讓我們把日誌想像成不只是程式的「意識流」,而是一份有價值的紀錄,過了一個月或一年之後,你或你的同事仍能從中找到問題的答案:「這裡到底發生了什麼事?」。為了做到這點,每一則日誌訊息都應該是有結構的。通常(這也是大多數函式庫的標準)每則訊息包含:

  • 事件時間 — 何時發生。
  • 層級 — 重要程度(INFO、ERROR 等)。
  • logger 名稱 — 通常是類別或元件名稱。
  • 訊息內容 — 具體發生了什麼。
  • 堆疊追蹤(若有錯誤) — 用來了解發生位置與原因。

以下是格式良好的日誌行(以 Log4j/SLF4J 為例):

2024-06-16 18:42:07,123 INFO  com.example.MainApp - 使用者已登入系統: username=vasya

若發生錯誤:

2024-06-16 18:42:10,456 ERROR com.example.LoginService - 使用者驗證錯誤: vasya
java.lang.IllegalArgumentException: 密碼不正確
    at com.example.LoginService.checkPassword(LoginService.java:42)
    ...

為什麼這很重要?
當應用程式運作時間一長,日誌可能佔用數個 GB。若訊息沒有結構化,要找問題就會變成「從風扇噪音猜旋律」等級的難題。

2. 訊息格式化

為什麼不該這樣做:

logger.info("使用者 " + username + " 已登入系統");

看似簡單,但有個陷阱:即使目前的日誌層級設為 ERROR,括號內的字串仍會被組合(字串連接仍會執行),這會造成額外的資源消耗。在大型系統中,每秒上千行日誌,可能導致實際的延遲。

正確作法:樣板與參數

現代的函式庫(例如 SLF4J 與 Log4j 2)支援帶參數的樣板:

logger.info("使用者 {} 已登入系統", username);

這樣只有在日誌層級允許輸出時才會建立字串。例如現在層級是 WARN,則連字串的計算都不會發生——節省資源與心力。

加分: 若傳入多個參數,它們會依序套入:

logger.info("使用者 {} 對物件 {} 執行了動作 {}", username, action, objectId);

記錄例外(stacktrace)

如果你捕捉到例外,別手動把堆疊追蹤加到訊息裡:

// 不要這樣:
logger.error("錯誤: " + ex.getMessage() + "\n" + Arrays.toString(ex.getStackTrace()));

正確作法:

logger.error("處理請求時發生錯誤", ex);

SLF4J 和 Log4j 會自動將堆疊追蹤漂亮地加入日誌。

範例:兩種做法的比較

// 不佳(字串連接永遠會執行)
logger.debug("物件: " + expensiveToString(obj));

// 佳(延遲形成)
logger.debug("物件: {}", obj);

3. 選擇日誌層級

如果你的日誌全都在 ERROR 層級,那就不是日誌,而是一盞紅色警示燈;如果全都在 DEBUG,你會淹沒在細節中。來看看各層級應該何時使用。

層級 用途 訊息範例
ERROR
關鍵性故障,導致系統運作不正常或完全無法運作 「資料庫連線錯誤」
WARN
重要但非致命的警告,需要留意 「找不到使用者,改用 guest」
INFO
反映應用程式正常運作的一般事件 「使用者已註冊:vasya」
DEBUG
除錯用的詳細資訊,生產環境不需要 「呼叫方法 checkPassword,參數為 ...」
TRACE
最細的資訊,通常用於深入診斷 「開始處理迴圈:i=0」

常見訊息範例

  • ERROR — 無法寫入檔案、捕捉到未處理的例外、服務不可用。
  • WARN — 已淘汰的 API、使用者的可疑行為、超出嘗試次數上限。
  • INFO — 使用者登入/登出、訂單處理完成、應用程式啟動。
  • DEBUG — 請求參數、變數值、中間計算結果。
  • TRACE — 進入/離開方法、內部迴圈、演算法的細節。

建議:
在生產環境通常只開啟 INFO 以上,有時包含 WARN 與 ERROR。DEBUG 與 TRACE 僅在追查棘手的 bug 時啟用。

4. Best practices(日誌記錄最佳實務)

不要記錄敏感資料

密碼、token、信用卡號——都不該出現在日誌中。即使你覺得「日誌檔只有我會看」,也請想想 GDPR 與那位可能不小心把日誌丟到群組聊天的同事。

// 不佳:
logger.info("使用者 {} 以密碼 {} 登入", username, password);

// 佳:
logger.info("使用者 {} 已登入系統", username);

不要濫用 ERROR 層級

如果你什麼都用 logger.error 記錄,真正發生災難時沒人會注意到——大家早就對「紅燈」麻木了。只有在應用程式真的無法繼續運作或商業邏輯被破壞時,才使用 ERROR。

記錄例外要包含完整堆疊

不要只寫 ex.getMessage(),否則你永遠不知道錯誤究竟在哪裡發生。請把例外當作第二個參數傳給 logger。

logger.error("處理請求時發生錯誤", ex);

使用唯一識別碼(事件關聯)

在大型系統中,替每個請求、使用者或操作指派唯一識別碼很有用。這能幫助你「串起」系統不同部分的事件。

logger.info("開始處理訂單: orderId={}", orderId);
logger.info("訂單已成功處理: orderId={}", orderId);

不要把所有東西都寫進日誌

如果日誌量太大——它就會變得毫無用處。不要記錄每一行程式,否則將無法找到真正需要的資訊。

讓訊息易於理解

請以半年後讀日誌的人也能理解的方式撰寫訊息。避免縮寫、難以理解的簡寫與「圈內人才懂的玩笑」。

5. 實作:設定日誌格式與層級

Log4j2(log4j2.xml)格式設定範例

<Configuration>
  <Appenders>
    <Console name="Console" target="SYSTEM_OUT">
      <PatternLayout pattern="%d{yyyy-MM-dd HH:mm:ss.SSS} %-5level %logger{36} - %msg%n"/>
    </Console>
  </Appenders>
  <Loggers>
    <Root level="info">
      <AppenderRef ref="Console"/>
    </Root>
  </Loggers>
</Configuration>

這代表什麼?

  • %d{...} — 事件時間。
  • %-5level — 層級(ERROR、INFO 等)。
  • %logger{36} — logger 名稱(通常是類別)。
  • %msg — 訊息本身。

含不同日誌層級的程式碼範例(SLF4J)

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class LogDemo {
    private static final Logger logger = LoggerFactory.getLogger(LogDemo.class);

    public static void main(String[] args) {
        logger.info("應用程式已啟動");
        logger.debug("變數 x 的值: {}", 42);

        try {
            throw new IllegalArgumentException("哎呀!");
            // ...
        } catch (Exception ex) {
            logger.error("啟動時發生錯誤", ex);
        }
    }
}

示範不同層級的差異

如果在 logger 設定中選擇 INFO 層級,那麼 DEBUG 與更低的訊息將不會輸出。試著在設定中把層級改成 debug —— 你會看到更詳細的資訊。

6. 常見錯誤

錯誤 №1: 在日誌中進行字串連接。 新手很常這樣寫:

logger.debug("使用者: " + user.getName() + ", 角色: " + user.getRole());

結果即使關閉 DEBUG 層級,這些字串仍會被建立,造成額外負擔。請改用參數化!

錯誤 №2: 記錄例外時沒有堆疊。 只寫訊息:

logger.error("錯誤: " + ex.getMessage());

結果日誌裡沒有發生位置的資訊。請把 exception 當作第二個參數傳入!

錯誤 №3: 把所有東西都記在 ERROR 層級。 如果一切都是紅色——就沒有真正的紅色。請依照用途選擇層級,否則重要錯誤會淹沒在「小事」之中。

錯誤 №4: 記錄敏感資料。 千萬不要把密碼、token、卡號寫入日誌。就算你覺得不會有人看到,現實常常出乎意料。

錯誤 №5: 訊息含糊不清。 如果日誌訊息看起來像「ERR42: fail」,一個月後你自己也記不得是什麼意思。請寫得清楚且詳盡。

錯誤 №6: 缺少唯一識別碼。 在複雜系統中,沒有 orderId、userId 等識別碼,你就無法「串起」事件並理解某個使用者或訂單究竟發生了什麼。

1
任務
JAVA 25 SELF, 等級 63, 課堂 1
上鎖
架構師
架構師
1
任務
JAVA 25 SELF, 等級 63, 課堂 1
上鎖
社群網站
社群網站
留言
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION