第 18 章

Debug、Catch、Status、Complete 與測試策略

可觀察不等於把完整訊息全部印出來。你需要先分清楚一般資料、可擷取錯誤、節點狀態與處理完成事件,再以手動、無外部 I/O 的案例驗證正常與失敗路徑。本章以 Node-RED 5.0.2 的精確行為建立一套可重複的除錯與回歸方法。

看見正確的事件,才不會誤判

同一個節點下方顯示綠色狀態,不代表它剛處理的訊息成功;Debug 出現預期 payload,也不代表後續每個節點都完成;Complete 觸發,更不代表所有 downstream 工作結束。這四種核心節點觀察不同事件,混用會產生假的成功訊號。

節點觀察什麼輸出重點不能推論
Debug實際接到的普通 msg選定 property、完整 msg 或 JSONata 結果沒有收到不等於上游必定出錯
Catchscope 內節點回報的 catchable errormsg.error 與來源資訊不是所有失敗、拒絕或狀態變化
Statusscope 內節點發出的 status eventmsg.status,本身不建立 payload狀態文字不等於訊息結果
Complete所選節點通知 runtime 已完成處理某則訊息觸發另一條 flow 路徑不是所有下游完成,也不是所有 node 都支援

本章只用手動 Inject、純 Function 與有限 Debug 模擬,不呼叫 Home Assistant、不送網路請求、不讀寫檔案,也不使用任何 credentials。要先理解同步 return、非同步 node.send/node.done,可回看第 16 章 Function 節點;架構邊界則見第 17 章。

四條訊號通道與明確 scope

普通訊息沿 wire 傳遞;error、status 與 complete 則由 runtime 事件機制交給相符的節點。Catch、Status、Complete 放在畫布上不代表自動監看全站。你必須理解「同一 Flow 分頁」、指定 nodes、Subflow 內外傳遞及節點是否支援 completion 的範圍。

通道典型欄位判讀方式
普通 messagemsg.payload、msg.topic、msg._msgid依節點輸入/輸出契約驗證
Catch errorerror.message、error.source.id/type/name先依 source 與錯誤分類路由,不把完整 msg 寫入 log
Status eventstatus.text、status.source.id/type/name狀態可能反映連線或節點自訂狀況,需配合時間與訊息測試
Complete eventcomplete.source.id/type/name 加在原訊息的 clone只針對設定的 node,且 node 必須呼叫完成 API

Node-RED runtime 在產生 Catch 訊息時會加入 msg.error。如果原訊息已有 error property,既有值會移到 msg._error。因此業務資料最好不要任意占用保留語意的 msg.error。Status node 則明確不產生 payload;下游應讀 msg.status,不能沿用一般資料路徑對 payload 的假設。

建立可重現的五步診斷

  1. 先寫可觀察的預期。

    用一句話列出輸入、預期輸出、允許的副作用(本章為無)、錯誤類別與完成定義。例如「有限數值輸入會得到兩倍結果;非數值以 INVALID_NUMBER 成為 Catch error」。

  2. 縮到手動、純資料路徑。

    複製到隔離分頁,停用外部 I/O 與運作型節點;Inject 保持手動且不在啟動時執行。只保留一個入口、受測邏輯和必要觀察點。

  3. 先 Debug 精確欄位。

    不要一開始輸出完整的 msg 物件。先看 msg.payload 或新建的 msg.testResult;為每次測試加不敏感案例名稱,以輸入矩陣逐筆手動執行。

  4. 分開加入 Catch、Status、Complete。

    三者各接自己的有限 Debug,並只選受測節點。逐一引發已知錯誤、已知狀態及 completion,確認沒有把其中一種事件當成另一種。

  5. 記錄基線並做最小部署。

    保存案例、預期與實際結果,不保存完整私密訊息。恢復正式路徑前,先審查停用狀態、外部目標與回復點,使用最小必要 Deploy scope,再跑核心回歸案例。

案例 N1:payload = 4      → result = 8,無 Catch
案例 N2:payload = 0      → result = 0,無 Catch
案例 X1:payload = "4"    → Catch: INVALID_NUMBER
案例 X2:缺少 payload     → Catch: INVALID_NUMBER
不變量:輸入 msg.topic 不被修改
外部 I/O:無

Debug:最少資料、最短時間、最低頻率

Debug node 可把選定的 message property 顯示在 Debug sidebar,也可選擇完整訊息或 JSONata 表達式結果;另可設定輸出到 runtime log,或把短內容顯示在節點 status。預設查看 msg.payload。Sidebar 的結構檢視方便展開 object/array,也能從來源資訊定位畫布節點,但這不代表應長期輸出所有資料。

完整 msg 可能包含位置、通知內容、Home Assistant entity 資料、HTTP live objects 或其他環境資訊。送到 runtime log 的內容會離開暫時 sidebar 脈絡,可能進入集中收集與保留政策。Stack trace 也可能揭露檔案路徑或敏感內容,分享前必須清理。

22.0.1/5.0.2 版本註記:Add-on bundled package 列有 [email protected],但固定原始碼不足以證明它已註冊或啟用;因此不能宣稱 5.0.2 runtime stack trace 必然獲得 source map 映射。這不改變主規則:log 與 stack trace 一律限量並先清理。
做法用途代價/風險
單一 property 到 sidebar確認型別與局部結果仍要確認 property 本身不敏感
完整 msg 到 sidebar短時間探索未知 shape序列化、複製與 UI 顯示成本較高,暴露面較大
JSONata 投影到 sidebar只取白名單欄位表達式本身要測試缺值與錯誤
輸出到 runtime log無法開啟 editor 時的有限診斷保留期、存取者與集中 log 邊界不同
節點 status 顯示極短摘要或計數長度有限,不能替代結構化驗證
// 離線 Function:建立最小診斷投影,不複製完整輸入
msg.testResult = {
    caseName: String(msg.caseName || "未命名案例"),
    payloadType: typeof msg.payload,
    hasTopic: Object.hasOwn(msg, "topic")
};
return msg;
效能與隱私:高頻事件接完整 Debug 會增加 message 格式化、傳輸與 sidebar 處理量。只在重現視窗內啟用最少 Debug,限制欄位與案例數;完成後用節點按鈕停用或移除,並依資料保留規則處理已產生的 log。

Catch 是錯誤路徑;Status 是狀態事件

Catch:只擷取 catchable errors

節點在處理訊息時若透過 runtime 的 error 機制回報,Catch 才能接到。Function 中要建立可擷取錯誤,使用 node.error(message, msg) 並帶入原訊息;只呼叫 node.error(message) 不會把錯誤關聯到該訊息供 Catch 處理。一般 JavaScript 驗證失敗應明確回報與停止,不要一邊送成功輸出、一邊發錯誤。

// 純資料驗證:沒有網路、檔案或 Home Assistant 操作
if (!Number.isFinite(msg.payload)) {
    node.error("INVALID_NUMBER", msg);
    return;
}
msg.testResult = msg.payload * 2;
return msg;

Catch 預設可擷取同一分頁中 nodes 的錯誤,也可指定特定 nodes,或只接尚未被 targeted Catch 處理的錯誤。當一個錯誤符合多個 Catch,所有 matching Catch 都會收到;因此多條錯誤處理線可能重複做事。錯誤若發生在 Subflow,先由 Subflow 內的 Catch 處理;內部沒有相符 Catch 時才向上傳到 instance 所在分頁。並非所有第三方節點失敗、連線狀態、輸出為空或業務拒絕都一定是 catchable error,必須查該 node 契約並實測。

Status:觀察 nodes 主動發布的狀態

Status node 接收同一 workspace tab 中 nodes 發布的 status message,預設範圍可涵蓋同分頁,也能指定個別 nodes。輸出包含 msg.status.text 與來源 type/id/name;它不建立 payload。某 node 顯示「connected」「waiting」或顏色變化,只是該 node 發出的狀態,不是某一則業務訊息的成功證明。

現象優先觀察原因
Function 驗證拒絕輸入Catchnode.error(..., msg) 建立 catchable error
節點顯示連線中/已連線Status這是 node status,不一定綁定單一 msg
一般資料結果不符有限 Debug+不變量不一定有錯誤或狀態事件
指定 node 處理完成Complete前提是 node 支援 completion API

在可下載範例中,Catch 與 Status 屬運作型觀察節點,因此依本站安全規格保持 d: true。不要為了看到事件而在正式 Flow 一次啟用全部觀察器;先閱讀下一節的隔離操作。

Complete:node completion API,不是全鏈路完成

Complete node 在指定的 node 告訴 runtime「已完成處理這一則訊息」時觸發。這個能力由 Node-RED 1.0 的 node completion API 引入;節點實作必須在同步/非同步工作完成時呼叫 done。不是每種 node 都支援,因此沒有 Complete event 不能直接判定失敗。

Complete 必須選取要監看的 nodes,不像 Catch 有預設處理整個 flow 的模式。它適合觀察沒有 output port、但有實作 completion 的節點,或把指定 node 的處理結束轉成另一條內部控制路徑。它只證明「被選 node 宣告完成」,不證明該 node 先前送出的所有 downstream 訊息已走完,更不證明外部系統完成最終效果。

敘述是否可由 Complete 單獨證明補充
指定 node 已呼叫完成 API可以仍以該 node 實作語意為準
指定 node 的輸出已被下游接收不一定completion 與 downstream 執行是不同範圍
整條 Flow 所有 nodes 都完成不可以沒有自動的全圖 join 語意
外部服務已永久完成工作不可以需要該協定的明確 acknowledgement/查證
未收到 Complete 就代表 node 失敗不可以node 可能未實作 completion

Function 的同步路徑可直接 return msg;非同步模式通常用 node.send(msg) 後在真正結束時呼叫 node.done()。Function 還有 On Start/On Stop lifecycle,可在部署與停止邊界初始化或清理資源;這些 lifecycle 程式也必須有錯誤、逾時與清理策略,不能當成無限制背景工作。本章不提供 timer 或外部 I/O 範例,避免把非同步示範變成殘留工作。

下載範例的可重現 Complete 練習

11-debug-catch-status.json 精確包含一個 Complete「觀察 Function 完成」,scope 只選核心 Function「產生有限診斷事件」;Complete 及 Catch、Status 都以 d:true 保持停用。Complete 接到自己的 Debug「檢視 complete」,該 Debug 只投影 msg.complete,與投影 msg.error、msg.status 的兩個 Debug 完全分開。

  1. 先以文字確認範例共 12 個 nodes、三個 Inject 都是 once=false 且 repeat/crontab 為空,Function 沒有 lifecycle、module、timer 或 I/O。
  2. 匯入隔離副本,只短暫啟用 Complete;Catch、Status 維持停用。按一次「手動測試正常輸出」,不要改用啟動或排程 Inject。
  3. 正常 Debug 精確收到 {"caseName":"normal","doubled":8};Complete Debug 精確收到 {"source":{"id":"b000000000000005","type":"function","name":"產生有限診斷事件"}}。這是 msg.complete 投影,不是完整訊息,也不代表下游 Debug 已完成。
  4. 測完立即把 Complete 恢復 d:true。若要測另外兩條路徑,一次只啟用對應觀察器:Catch Inject 讓 error Debug 收到 message 為 TEST_ERROR、source 指向同一 Function 的 msg.error;Status Inject 讓 status Debug 收到 fill blue、shape dot、text TEST_STATUS、source 指向同一 Function 的 msg.status。兩者都沒有正常 payload 輸出。
判讀邊界:正常輸出與 Complete 是兩條獨立觀察線;Catch 仍只讀 msg.error,Status 仍只讀 msg.status。不要同時啟用所有運作型觀察器,也不要把 completion 當成端到端 acknowledgement。

分階段測試、mock、不變量與負面案例

測試 Flow 不必從正式裝置開始。先用 mock message(模擬訊息)固定輸入 shape,再逐步增加 Context、Subflow 或 Link 邊界。Mock 的目的不是仿造所有環境資料,而是用最少欄位證明邏輯;未知欄位、缺值、邊界值與錯誤型別都要成為明確案例。

階段內容通過條件
1. 純轉換手動 Inject → 純 Function/Change → 有限 Debug正常、邊界與錯誤型別符合預期
2. 錯誤契約指定 Catch 接錯誤投影 Debug每個拒絕案例只進預期錯誤路徑
3. 狀態/完成指定 Status、Complete,分開記錄不把 status 或 node completion 當成端到端成功
4. 架構邊界兩個 Subflow instance 或 Link Call timeout狀態隔離、return 與逾時契約成立
5. 整合前檢查仍停用外部 I/O,審查設定、目標與回復由變更程序另行核准後才可進整合環境
6. 回歸核心案例+本次修正案例+相鄰流程 smoke cases無預期外的 message shape、錯誤或副作用

建立不變量,而不只比對 payload

  • 輸入 msg.topic、msg._msgid 或指定 metadata 不被非預期覆寫。
  • 錯誤案例不會同時送往成功輸出。
  • 每個輸入最多產生契約允許的輸出數,不因多個 Catch 重複處理。
  • Context key 數量與內容有界;重新部署後狀態符合設計。
  • 任何 timeout、重試或 buffer 都有上限;本章 mock 不建立 queue、timer 或外部 I/O。

負面案例清單

至少測缺少 payload、null、字串代替數字、NaN/無限值、超出允許範圍、額外欄位、重複訊息與錯誤順序。對 Link Call 再測沒有 return;對 Subflow 測兩個 instance 交錯;對 Catch 測既有 msg.error;對 Status 測下游錯誤地讀 payload;對 Complete 測未選 node 與不支援 completion 的 node。

不要用 Debug sidebar 的肉眼印象當唯一證據。把案例表、預期欄位與結果摘要放進不含環境資料的變更紀錄;真正的 secrets、完整訊息、stack trace 和 runtime log 不應貼入一般工單。

Deploy scope 與回歸範圍

Node-RED 5.0.2 提供 Full、Modified Flows、Modified Nodes 三種 Deploy scope。Full 會重啟所有 nodes;Modified Flows 會重啟包含變更 nodes 的 flows;Modified Nodes 只重啟已修改的 nodes。範圍越小越能減少無關重啟,但不是自動安全保證:先確認改動是否影響 Subflow instances、config node 使用者、Context 或共享路徑,再選最小且足夠的 scope。

每次 Deploy 前記錄可回復版本並檢查啟動型 Inject、排程、監聽與外部副作用節點;Deploy 後先跑單一純資料 smoke case,再跑本次修正與相鄰流程回歸。若 config node 或共用 Subflow 改動會影響多個 flows,不能為了選最小標籤而漏掉受影響範圍。

故障排除

  • Catch 沒收到 Function 錯誤:確認 Function 使用 node.error("分類", msg) 並在之後停止輸出;Catch scope 要包含該 Function。只有 throw、字串警告、空輸出或 node.error 未帶 msg 都不能一概假設可被同樣擷取。
  • 同一錯誤處理了兩次:檢查是否同時符合 targeted Catch 與另一個 Catch。Node-RED 會把錯誤交給所有 matching Catch;讓責任互斥,或使用「尚未被擷取」模式作最後防線。
  • Status 下游的 payload 是 undefined:Status node 不產生 payload。改讀 msg.status.text 與 msg.status.source,並讓 status 診斷線和一般資料線分開。
  • Complete 沒有觸發:確認 Complete 已選受測 node,而且該 node 實作 completion API。不要因為沒事件就直接增加 Delay 或判定失敗;先用固定版本節點說明與最小案例查證。
  • Complete 觸發但後續工作還沒完成:這是 scope 誤解。Complete 只代表被選 node 宣告完成,不是 downstream barrier;若需要端到端完成,必須定義最後確認訊號或有限 join 契約。
  • 開啟完整 Debug 後 editor 變慢:先用 node 按鈕停用 Debug,縮小為單一 property、降低測試頻率並清空不需要的 sidebar 訊息。不要用增加 heap 取代限制 message size 與觀察量。

固定版本來源與官方文件

具體節點語意以 exact commit 為基線。官方文件提供通用操作說明;若 rolling 文件日後改變,以此處固定的 Node-RED 5.0.2 與 Add-on 22.0.1 實作為本章判定依據。

本站的離線安全範例:11-debug-catch-status.json。三個手動 Inject 以 msg.mode 選擇正常、Catch 測試或有界的 Status 測試;一般、錯誤、狀態與完成事件各接只顯示 payload、error、status、complete 的獨立 Debug。請先讀同目錄 README;Catch、Status 與 Complete 固定保持 d: true,只可在隔離副本逐一路徑短暫啟用。

常見問題

Catch 的補償流程本身也失敗時,怎麼避免錯誤迴圈?
補償線使用獨立、互斥的 Catch scope,並在訊息加入有限的錯誤階段或重試次數;到達上限就只記錄最小診斷並停止,不再回送原入口。補償不可再次觸發同一外部副作用,也不要把完整原始訊息附進錯誤文字。
Status 在短時間內反覆切換,測試要看哪一件事?
不要只截最後一個顏色。用手動、有限案例記錄狀態順序、source、時間與是否超過允許頻率;下游若要告警,先做去重與節流。Status 仍不是業務成功訊號,抖動時也不能據此重送 Action。
Complete 是否代表整條 Flow 完成?
不是。它只在所選 node 呼叫 completion API 時觸發;不等於 downstream 全部完成,也不代表外部系統最終完成。不是所有 nodes 都支援 completion。
為什麼不長期開著完整 Debug?
完整 msg 會擴大敏感資料暴露,並增加格式化、傳輸與 sidebar 負擔。優先輸出白名單 property,只在有限重現視窗啟用,完成後停用或移除。
下載範例匯入後可以直接 Deploy 嗎?
不可以。先離線讀 JSON 與 README,在隔離副本逐節點檢查。Catch、Status 與 Complete 固定為 d: true;任何啟用與部署都必須另走核准程序。
修正一個 Function 後要跑哪些回歸?
至少跑原本核心正常案例、所有既有負面案例、本次 bug 的重現案例、不變量,以及相鄰輸入/輸出 shape 的 smoke cases。再依最小 Deploy scope 驗證,避免不必要重啟其他 flows。