博客 / 詳情

返回

程序出錯瞎找?教你寫“會説話”的錯誤日誌,秒定位原因

前言

排查程序問題時,錯誤日誌就是最靠譜的“地圖”:寫得好的日誌,能直接帶你找到“問題地點”;寫得差的日誌,卻像一張模糊的塗鴉,讓你在代碼裏繞來繞去還找不到北。今天就從“錯誤怎麼來的”講到“日誌怎麼寫”,來講講錯誤日誌的妙用。

一、錯誤不是“憑空冒出來”的,這3類場景最容易出問題

程序出錯從來不是“突然襲擊”,而是有明確來源的。就像生活裏的麻煩事,要麼是別人給的(上層傳錯參數),要麼是和別人打交道出的(下層交互故障),要麼是自己沒做好(本層處理疏漏),具體可以分為3類:

1. 上層系統“遞錯了料”:非法參數引入錯誤

比如你寫了個“計算兩數相加”的功能,上層系統卻傳了“abc”這種非數字參數。這類錯誤能通過“參數校驗”提前攔截,比如用正則表達式過濾非法輸入,給用户提示“請輸入數字”。

2. 下層系統“沒配合好”:交互過程出故障

和下層系統(比如數據庫、其他服務)打交道時,容易出兩種問題:

  • 通信斷了,數據沒同步:下層其實處理完了,但信號沒傳回來(比如網絡斷了),導致兩邊數據對不上;
  • 通信通了,處理錯了:信號傳過去了,但下層沒處理好(比如數據庫返回“表不存在”),這時候要和下層開發溝通,按返回的錯誤碼做處理,比如提示“請檢查數據庫表配置”。

不管哪種情況,都要默認“下層可能不靠譜”,提前做好預案。

3. 本層系統“自己出了岔子”:處理邏輯有疏漏

這是最常見的錯誤來源:

  • 手滑疏忽:把&&寫成&==寫成=,或者邊界判斷錯(比如“大於等於”寫成“大於”)——就像打字時漏了個鍵,解決辦法是用代碼靜態分析工具(比如SonarQube)、寫單元測試覆蓋邏輯;
  • 異常沒考慮全:計算相加時沒考慮“溢出”,輸入時沒過濾“非法字符”——比如算10億+10億,結果超出整數範圍,解決辦法是寫功能後多問自己:“如果傳錯值、算錯數,該怎麼提示?”;
  • 邏輯像“一團亂麻”:函數寫了200行,各種邏輯纏在一起,改一個地方牽一髮全身——解決辦法是拆成短函數(最好不超過50行),像“拆快遞”一樣把複雜邏輯分開,每個函數只幹一件事;
  • 空指針“找上門”:對象沒初始化就用(比如user.getName()usernull)——解決辦法是用之前先檢查:“這個對象是不是空的?”,配置對象還要確認“有沒有加載成功”;
  • 配置“裝睡”:啓動時配置沒加載(比如數據庫地址寫錯),卻沒提示——解決辦法是啓動時打印INFO日誌,確認“所有配置都讀對了”,比如“數據庫地址:jdbc:mysql://xxx”。

二、核心技巧:錯誤日誌這麼寫,排查問題快10倍

很多人寫日誌只寫“xxx失敗”,比如log.error("插入IP失敗")——這種日誌就像“地圖上只標了‘有寶藏’,沒標具體在哪”。真正有用的日誌,要滿足6個原則,每個原則都配“反面例子+正面例子”:

原則1:儘可能完整——把“時間、場景、原因、辦法”説全

反面

log.error("control ip insert failed", ex);

(沒説哪個IP失敗,不知道為啥失敗)

正面

log.error("[插入控制IP] 插入失敗,失敗IP:{},可能原因:IP格式錯誤/數據庫連接超時,建議:檢查IP是否為xxx格式/查看數據庫狀態", ip, ex);

一句話講清“在做什麼時失敗、哪個參數錯了、可能為啥、該咋辦”。

原則2:儘可能具體——別用“通用詞”,要“精準到細節”

反面

log.error("zone storage type not support, zone: " + zone.getZoneId() + ", storageType: " + storageType.name());

(沒説支持啥類型)

正面

og.error("[檢查zone存儲] zone不支持該存儲類型,zoneID:{},傳入類型:{},支持類型:dfs1/dfs2(需搭配io3/io4),建議:修改zone存儲配置", zone.getZoneId(), storageType.name());

連“正確的配置是什麼”都説明白,不用再查代碼。

原則3:儘可能直接——讓人“一眼懂”,不用“猜半天”

反面

`log.error("aliMonitorReporter is null!");`

(為啥為null?咋解決?)

正面

`log.error("[初始化監控] aliMonitorReporter為null,可能是配置文件xxx沒加載,建議:檢查xxx.conf裏的monitor配置是否正確");`  

原則4:集成經驗——把“踩過的坑”寫進日誌

比如“Jackson解析JSON新增字段報錯”,解決後在日誌里加提示:

log.error("[JSON解析] 新增字段導致解析失敗,建議:在實體類加@JsonIgnoreProperties(ignoreUnknown = true)註解,參考之前解決的#123問題");

讓後來人不用再踩同樣的坑。

原則5:格式統一——別像“隨筆”,要像“表格”

亂糟糟的日誌看着頭疼,建議套用固定格式:

log.error("[接口名/操作名] [錯誤現象],[相關參數],[可能原因],[解決建議]");

比如:

log.error("[刪除NC] 無法刪除NC,NCID:{},未銷燬VM:{},可能原因:VM還在運行,建議:先銷燬VM再刪除NC", ncId, vmNames);

原則6:突出關鍵字——時間、ID、操作名要顯眼

日誌裏必須包含“時間(精確到秒)、實體ID(比如NCID、VM名)、操作名”,比如:

2024-10-01 14:30:00 [刪除NC] 無法刪除NC,NCID:nc001,未銷燬VM:vm001、vm002...

定位時用“時間+NCID”搜,比用requestId快多了。

三、QA

1. 用String.format寫日誌會影響性能嗎?

不會!錯誤日誌本來就少(正常情況下不會頻繁報錯),String.format的調用頻率低,對程序沒影響,放心用。

2. 開發忙的時候,沒時間寫詳細日誌咋辦?

套固定格式!把[接口名] [錯誤] [參數] [原因] [建議]存在記事本里,寫日誌時填內容就行,比如:
[新增VM] 新增失敗,VM名:vm001,可能原因:CPU資源不足,建議:檢查NC資源
比瞎寫快,還規範。

3. info、warn、error該怎麼分?

  • info:正常狀態(比如“初始化成功,配置:xxx”),用來“追蹤流程”;
  • warn:小問題不影響運行(比如“緩存過期,已自動刷新”);
  • error:大問題沒法完成操作(比如“刪除NC失敗”),必須處理。

四、錯誤日誌是“反思的鏡子”,更是“傳承的文檔”

解決完錯誤後,要回頭看日誌:“當時漏了什麼信息?下次怎麼寫更清楚?”。好的錯誤日誌不僅能幫你快速排查問題,還能成為團隊的“知識庫”,記錄下所有“不合法的運行用例”,讓新人少走彎路。

user avatar
0 位用戶收藏了這個故事!

發佈 評論

Some HTML is okay.