Debug、Catch、Status、Complete 與測試策略
可觀察不等於把完整訊息全部印出來。你需要先分清楚一般資料、可擷取錯誤、節點狀態與處理完成事件,再以手動、無外部 I/O 的案例驗證正常與失敗路徑。本章以 Node-RED 5.0.2 的精確行為建立一套可重複的除錯與回歸方法。
看見正確的事件,才不會誤判
同一個節點下方顯示綠色狀態,不代表它剛處理的訊息成功;Debug 出現預期 payload,也不代表後續每個節點都完成;Complete 觸發,更不代表所有 downstream 工作結束。這四種核心節點觀察不同事件,混用會產生假的成功訊號。
| 節點 | 觀察什麼 | 輸出重點 | 不能推論 |
|---|---|---|---|
| Debug | 實際接到的普通 msg | 選定 property、完整 msg 或 JSONata 結果 | 沒有收到不等於上游必定出錯 |
| Catch | scope 內節點回報的 catchable error | msg.error 與來源資訊 | 不是所有失敗、拒絕或狀態變化 |
| Status | scope 內節點發出的 status event | msg.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 的範圍。
| 通道 | 典型欄位 | 判讀方式 |
|---|---|---|
| 普通 message | msg.payload、msg.topic、msg._msgid | 依節點輸入/輸出契約驗證 |
| Catch error | error.message、error.source.id/type/name | 先依 source 與錯誤分類路由,不把完整 msg 寫入 log |
| Status event | status.text、status.source.id/type/name | 狀態可能反映連線或節點自訂狀況,需配合時間與訊息測試 |
| Complete event | complete.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 的假設。
建立可重現的五步診斷
- 先寫可觀察的預期。
用一句話列出輸入、預期輸出、允許的副作用(本章為無)、錯誤類別與完成定義。例如「有限數值輸入會得到兩倍結果;非數值以
INVALID_NUMBER成為 Catch error」。 - 縮到手動、純資料路徑。
複製到隔離分頁,停用外部 I/O 與運作型節點;Inject 保持手動且不在啟動時執行。只保留一個入口、受測邏輯和必要觀察點。
- 先 Debug 精確欄位。
不要一開始輸出完整的 msg 物件。先看
msg.payload或新建的msg.testResult;為每次測試加不敏感案例名稱,以輸入矩陣逐筆手動執行。 - 分開加入 Catch、Status、Complete。
三者各接自己的有限 Debug,並只選受測節點。逐一引發已知錯誤、已知狀態及 completion,確認沒有把其中一種事件當成另一種。
- 記錄基線並做最小部署。
保存案例、預期與實際結果,不保存完整私密訊息。恢復正式路徑前,先審查停用狀態、外部目標與回復點,使用最小必要 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 也可能揭露檔案路徑或敏感內容,分享前必須清理。
[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;
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 驗證拒絕輸入 | Catch | node.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 完全分開。
- 先以文字確認範例共 12 個 nodes、三個 Inject 都是
once=false且 repeat/crontab 為空,Function 沒有 lifecycle、module、timer 或 I/O。 - 匯入隔離副本,只短暫啟用 Complete;Catch、Status 維持停用。按一次「手動測試正常輸出」,不要改用啟動或排程 Inject。
- 正常 Debug 精確收到
{"caseName":"normal","doubled":8};Complete Debug 精確收到{"source":{"id":"b000000000000005","type":"function","name":"產生有限診斷事件"}}。這是msg.complete投影,不是完整訊息,也不代表下游 Debug 已完成。 - 測完立即把 Complete 恢復
d:true。若要測另外兩條路徑,一次只啟用對應觀察器:Catch Inject 讓 error Debug 收到 message 為TEST_ERROR、source 指向同一 Function 的msg.error;Status Inject 讓 status Debug 收到 fillblue、shapedot、textTEST_STATUS、source 指向同一 Function 的msg.status。兩者都沒有正常 payload 輸出。
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 實作為本章判定依據。
- Node-RED 5.0.2 固定提交:Debug、Catch、Status、Complete、Function lifecycle、runtime 與 Deploy scopes。
- Home Assistant Community App: Node-RED 22.0.1 固定提交:內嵌 dependencies 與執行包裝邊界。
- Node-RED 官方文件:Handling errors。
- Node-RED 官方文件:Catchable errors。
- Node-RED 官方文件:Node status。
- Node-RED 官方文件:Writing Functions。
本站的離線安全範例:11-debug-catch-status.json。三個手動 Inject 以 msg.mode 選擇正常、Catch 測試或有界的 Status 測試;一般、錯誤、狀態與完成事件各接只顯示 payload、error、status、complete 的獨立 Debug。請先讀同目錄 README;Catch、Status 與 Complete 固定保持 d: true,只可在隔離副本逐一路徑短暫啟用。
常見問題
Catch 的補償流程本身也失敗時,怎麼避免錯誤迴圈?
Status 在短時間內反覆切換,測試要看哪一件事?
Complete 是否代表整條 Flow 完成?
為什麼不長期開著完整 Debug?
下載範例匯入後可以直接 Deploy 嗎?
d: true;任何啟用與部署都必須另走核准程序。