Главная → Академия → Node-RED: установка, flows, msg/JSON, отладка → Нода `Catch`: централизованный перехват ошибок в потоке

Нода `Catch`: централизованный перехват ошибок в потоке

Введение в обработку ошибок: почему Debug не всегда достаточно

В процессе разработки и отладки потоков в Node-RED, нода `Debug` является незаменимым инструментом. Она позволяет инспектировать содержимое объекта `msg`, отслеживать ход выполнения логики и быстро выявлять проблемы на этапе создания сценария. Однако, как только поток переходит из стадии разработки в производственную эксплуатацию на объекте, роль и ограничения ноды `Debug` становятся очевидными.

> 🔗 Связанный материал: Подробно о работе с нодой `Debug` и её возможностями для пошаговой отладки мы говорили в уроке COURSE-06-M04-L01 "Нода Debug и панель отладки".

Основное ограничение ноды `Debug` заключается в ее предназначении: она является инструментом для интерактивной отладки инженером, а не для автоматизированного управления ошибками в реальном времени. Рассмотрим ключевые отличия:

  • Реактивный характер: Нода `Debug` требует активного наблюдения со стороны инженера. Ошибки или неожиданные значения, выводимые в панель отладки, не вызывают никаких автоматических действий. В производственной среде, где контроллер работает автономно 24/7, нет возможности постоянно следить за логом отладки.
  • Отсутствие централизации: Если в сложном проекте с десятками потоков на разных вкладках необходимо отслеживать потенциальные сбои, потребуется разместить множество нод `Debug` в "подозрительных" местах. Это загромождает поток и усложняет его читаемость. Более того, сообщения об ошибках будут разрознены и смешаны с обычными отладочными данными.
  • Непреднамеренные сбои: Нода `Debug` полезна для анализа потока данных, но она не способна перехватить исключения (exceptions), которые генерируют другие ноды. Например, если нода `Modbus-Read` не сможет связаться с устройством из-за обрыва шины RS-485, она сгенерирует ошибку. Эта ошибка остановит выполнение текущей ветки потока, и если она не будет обработана, система может перейти в непредсказуемое состояние. Нода `Debug`, стоящая после сбойного узла, просто никогда не получит сообщение.
  • Именно для решения этих проблем в Node-RED существует концепция централизованной обработки ошибок. Ее суть — создать единую точку в проекте, которая будет автоматически "ловить" все сбои, независимо от того, в какой части потока они произошли. Такой подход позволяет:

    • Гарантировать надежность: Ни одна критическая ошибка не останется незамеченной.
    • Упростить обслуживание: Вместо поиска десятков `Debug`-узлов, инженер может анализировать единый, структурированный лог ошибок.
    • Реализовать автоматические реакции: При возникновении сбоя система может не просто залогировать его, но и отправить уведомление администратору, попытаться выполнить операцию повторно или переключиться на резервное оборудование.

    Основным инструментом для реализации этого подхода является нода `Catch`. Это специальная нода, которая не имеет входов, но способна перехватывать ошибки, сгенерированные другими нодами в определенной области действия. Она действует как "сеть безопасности" для ваших потоков, гарантируя, что любое исключение будет поймано и обработано согласно заданной вами логике.

    ---

    Нода Catch: Конфигурация и область действия (Scope)

    Нода `Catch` находится в палитре "common" и легко узнаваема по иконке с ладонью. В отличие от большинства нод, она не имеет входного порта, так как ее задача — не обрабатывать проходящие сообщения, а реагировать на события-ошибки внутри среды выполнения Node-RED.

    Панель конфигурации

    Двойной клик по ноде открывает ее окно настроек, которое содержит один ключевой параметр — Scope (Область действия). Этот параметр определяет, ошибки каких нод будет перехватывать данный экземпляр ноды `Catch`.

    | Область действия (Scope) | Описание | Преимущества | Недостатки |

    | ---------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------- |

    | All nodes on current tab (По умолчанию) | Перехватывает ошибки от абсолютно всех нод, расположенных на той же вкладке (flow), что и сама нода `Catch`. | Простота настройки, гарантирует базовую отказоустойчивость для всей логики на вкладке. | Не позволяет создать специфическую логику обработки для разных типов ошибок на одной вкладке. |

    | Selected nodes | Перехватывает ошибки только от тех нод, которые вы вручную выберете. После выбора этой опции появляется кнопка, открывающая список всех нод на вкладке. | Позволяет создавать целевую обработку для критически важных или нестабильных узлов (например, HTTP-запросы, Modbus-опросы). | Требует ручной конфигурации. Если вы добавите новую ноду в поток, нужно не забыть добавить ее в список. |

    | All nodes | Глобальный перехватчик. Перехватывает ошибки со всех нод во всем проекте (на всех вкладках). | Обеспечивает максимальное покрытие, "ловит" ошибки даже на тех вкладках, где забыли разместить `Catch`. | Крайне не рекомендуется, так как создает "невидимую" связь между потоками, усложняя отладку. Может привести к путанице. |

    > 💡 Подсказка: Начните с правила `All nodes on current tab` для общей надежности потока и добавляйте специфичные `Catch` ноды для критически важных участков, где требуется особая логика обработки (например, Modbus-запросы или API-вызовы). Размещение одной общей ноды `Catch` на каждой вкладке является хорошей практикой и обязательным требованием для сертификации.

    Опция "Catch unhandled errors in 'Done' callbacks"

    Эта опция, доступная при выборе области `Selected nodes`, относится к продвинутому сценарию работы с асинхронными нодами. Некоторые ноды (например, `Exec` для выполнения команд в ОС) могут выполнять длительные операции в фоновом режиме. После завершения они могут вызвать так называемую функцию `done()`. Если в процессе этой фоновой работы произошла ошибка, и она не была обработана внутри самой ноды, эта опция позволит ноде `Catch` ее перехватить. Для большинства стандартных задач эту опцию можно оставить выключенной.

    Как это работает на практике?

    Представьте поток, опрашивающий Modbus-устройство:

    `[Inject]` -> `[Modbus Getter]` -> `[Function]`

    Если `Modbus Getter` не сможет получить ответ от устройства (например, обрыв связи), он сгенерирует ошибку. В этот момент произойдет следующее:

  • Выполнение обычной цепочки сообщения прервется. Нода `Function` никогда не получит `msg`.
  • Среда Node-RED "транслирует" событие ошибки.
  • Нода `Catch`, размещенная на этой же вкладке и настроенная на `All nodes on current tab`, "услышит" это событие.
  • Нода `Catch` сгенерирует новое сообщение, содержащее всю информацию об ошибке, и отправит его на свой единственный выход.
  • Теперь мы можем подключить к выходу `Catch` другую цепочку нод, которая, например, запишет информацию о сбое в базу данных MySQL и отправит уведомление по MQTT. Таким образом, мы создаем отдельный, независимый поток для управления нештатными ситуациями.

    ---

    Структура объекта ошибки: msg.error

    труктура объекта ошибки: msg.error

    Ключевой момент в работе с нодой `Catch` — понимание структуры сообщения, которое она генерирует. Когда `Catch` перехватывает ошибку, она не просто передает дальше исходное сообщение, вызвавшее сбой. Вместо этого она создает совершенно новый объект `msg` со своим уникальным `_msgid`. В этот новый объект помещается исчерпывающая информация о произошедшей ошибке.

    Главным свойством этого нового сообщения является `msg.error`. Это объект, который содержит в себе все детали инцидента.

    > ℹ️ Информация: Оригинальное сообщение, которое обрабатывалось в момент сбоя, не теряется. Его копия сохраняется, и ссылка на него (его `_msgid`) доступна. Это позволяет при необходимости восстановить контекст, в котором произошла ошибка.

    Рассмотрим типовую структуру объекта, который выходит из ноды `Catch`:

    {
    

    "_msgid": "a987b654.321fed",

    "error": {

    "message": "connect ECONNREFUSED 127.0.0.1:1884",

    "source": {

    "id": "f123e456.789abc",

    "type": "mqtt out",

    "name": "Отправить статус",

    "count": 1

    },

    "stack": "Error: connect ECONNREFUSED 127.0.0.1:1884\n at TCPConnectWrap.afterConnect [as oncomplete] (node:net:1187:16)"

    }

    }

    Глубокий анализ `msg.error`

    Давайте разберем каждое свойство объекта `msg.error`, чтобы понять, как его использовать для создания умной системы логирования.

    • `msg.error.message` (string)
    Это самое важное свойство — текстовое описание ошибки. Именно его вы бы увидели в панели `Debug`, если бы ошибка вывелась туда. Например: `ReferenceError: my_variable is not defined`. Это сообщение предназначено в первую очередь для человека.
    • `msg.error.source` (object)
    Этот вложенный объект описывает ноду-источник:

    * `source.id`: Уникальный идентификатор ноды. Самый надежный способ программно идентифицировать источник проблемы.

    * `source.type`: Тип ноды (например, `function`, `http request`).

    * `source.name`: Имя, которое вы задали ноде вручную. Идеально подходит для формирования понятных логов.

    • `msg.error.stack` (string, опционально)
    Для ошибок внутри нод `Function`. Содержит трассировку (stack trace), показывающую конкретную строку кода, где произошел сбой.

    Спецификация для системы аудита (Audit Log)

    При построении профессиональных систем мониторинга все ошибки должны записываться в единый журнал (Audit Log). Ниже представлена таблица обязательных полей, которые необходимо извлекать из `msg.error` для корректного ведения журнала:

    | Поле в журнале | Источник в Node-RED | Описание |

    | :--- | :--- | :--- |

    | Timestamp | `new Date()` | Точное время возникновения ошибки. |

    | Node ID | `msg.error.source.id` | Уникальный ID ноды для быстрого поиска в редакторе. |

    | Node Name | `msg.error.source.name` | Человекочитаемое имя процесса/датчика. |

    | Error Message| `msg.error.message` | Текст ошибки для диагностики. |

    | Flow Context | `msg._msgid` | Идентификатор транзакции для связи с другими логами. |

    Как использовать эту структуру?

    Имея эти данные, мы можем построить очень гибкую логику. Например, в ноде `Function`, подключенной к `Catch`, мы можем подготовить данные для записи в БД или файл:

    let sourceNodeName = msg.error.source.name || "Неизвестная нода";
    

    let errorMessage = msg.error.message;

    let timestamp = new Date().toISOString();

    // Формируем объект для Audit Log

    msg.audit = {

    time: timestamp,

    node: msg.error.source.id,

    label: sourceNodeName,

    details: errorMessage

    };

    // Формируем текстовую строку для лог-файла

    msg.payload = `${timestamp} | ERROR | [${msg.error.source.id}] (${sourceNodeName}): ${errorMessage}`;

    return msg;

    Таким образом, мы извлекаем из объекта `msg.error` всю необходимую информацию согласно спецификации и используем ее для создания разных форматов сообщений для разных каналов оповещения.

    Практический пример: логирование и оповещение об ошибках

    рактический пример: логирование и оповещение об ошибках

    Теперь давайте создадим полноценный поток, который демонстрирует централизованный перехват, логирование и оповещение. Наша задача — создать "сеть безопасности" для потока, которая будет ловить любые сбои, записывать их в файл на контроллере и отправлять сигнал тревоги по MQTT.

    Сценарий: У нас есть нода `Function`, которая по какой-то причине (например, из-за ошибки в коде) генерирует исключение. Мы должны это исключение перехватить. Архитектура потока (ASCII):
    //========= Основной поток (может быть любым) ===========
    
    

    [Inject] ----> [Function: "Проблемная логика"] ----> [Debug: "Успех"]

    //========= Поток обработки ошибок на этой же вкладке ===========

    [Catch] ----> [Function: "Форматировать ошибку"] --+-- (в лог) --> [File: "errors.log"]

    |

    +-- (в MQTT) --> [MQTT Out: "hi/system/alerts"]

    Шаг 1: Создание потока, генерирующего ошибку

  • Разместите на холсте ноды `Inject`, `Function` и `Debug`.
  • Соедините их последовательно: `Inject` -> `Function` -> `Debug`.
  • Назовите нода `Function` "Проблемная логика". Внутри нее напишите код, который намеренно вызовет ошибку. Для этого мы используем встроенный метод `node.error()`.
  •     // Код для ноды "Проблемная логика"

    // Имитируем получение данных

    let temperature = "не число"; // Некорректные данные

    // Пытаемся обработать

    let calculatedValue = parseFloat(temperature) * 10;

    if (isNaN(calculatedValue)) {

    // Мы обнаружили проблему. Генерируем ошибку.

    // Первый аргумент - текст ошибки.

    // Второй (опциональный) - оригинальное сообщение, которое будет доступно в Catch.

    node.error("Ошибка преобразования данных: значение не является числом", msg);

    // Важно! После node.error() нужно остановить поток, вернув null.

    return null;

    }

    msg.payload = calculatedValue;

    return msg;

  • Назовите ноду `Debug` "Успех".
  • Шаг 2: Добавление ноды Catch и потока обработки

    Для соответствия стандартам промышленного аудита (Audit Log), мы должны извлекать из `msg.error` строго определенный набор полей.

    Спецификация полей для журнала событий:

    | Поле в msg.error | Описание | Обязательность |

    | :--- | :--- | :--- |

    | `source.id` | Уникальный ID ноды, где произошел сбой | Да |

    | `source.name` | Пользовательское имя ноды | Да (или 'default') |

    | `source.type` | Тип ноды (Function, MQTT и т.д.) | Да |

    | `message` | Текст ошибки (человекочитаемый) | Да |

    | `stack` | Стек вызовов (для отладки JS) | Нет |

  • Разместите на той же вкладке ноду `Catch`. Оставьте ее настройки по умолчанию (`Scope: All nodes on current tab`).
  • Добавьте ноду `Function` и назовите ее "Форматировать ошибку". Подключите ее к выходу `Catch`.
  • Внутри "Форматировать ошибку" напишем код, формирующий сообщение согласно спецификации.
  • > ⚠️ Внимание: Убедитесь, что у системного пользователя `nodered` есть права на запись в директорию `/var/log/hi/`. Можно настроить командой `sudo chown nodered:nodered /var/log/hi/`. Иначе возникнет рекурсивная ошибка при записи лога.

        // Код для ноды "Форматировать ошибку"

    // Извлекаем данные согласно спецификации Audit Log

    const timestamp = new Date().toISOString();

    const nodeName = msg.error.source.name || 'безымянная нода';

    const nodeType = msg.error.source.type;

    const nodeId = msg.error.source.id;

    const errorMessage = msg.error.message;

    // 1. Формируем строку для записи в файл лога (Audit String)

    const logString = `${timestamp} | ERROR | ID: ${nodeId} | Node: "${nodeName}" (${nodeType}) | Message: ${errorMessage}\n`;

    // 2. Формируем JSON-объект для отправки по MQTT (Контракт сообщения)

    const alertPayload = {

    ts: Date.now(),

    level: "error",

    source: {

    id: nodeId,

    name: nodeName,

    type: nodeType

    },

    message: errorMessage

    };

    msg.logPayload = logString;

    msg.payload = alertPayload;

    return msg;

  • Добавьте ноды `File` и `MQTT Out`.
  • Подключите выход "Форматировать ошибку" к обеим этим нодам.
  • Настройте ноду `File`:
  • * Filename: `/var/log/hi/nodered_errors.log`

    * Action: `Append to file`

    * Payload property: `msg.logPayload`

  • Настройте ноду `MQTT Out`:
  • * Broker: Выберите ваш MQTT-брокер.

    * Topic: `hi/system/alerts`

    * QoS: `1`

    Теперь разверните проект и снова нажмите `Inject`. В результате:

    • Нода `Catch` перехватит ошибку.
    • В файле лога появится детализированная запись, включающая `node.id`.
    • В MQTT-топик уйдет структурированный JSON для системы мониторинга.

    Вы создали надежную и централизованную систему мониторинга состояния ваших потоков.

    Итоги и лучшие практики

    Нода `Catch` — это не просто еще один инструмент отладки, а фундаментальный компонент для построения надежных и отказоустойчивых систем автоматизации на платформе HI. В отличие от `Debug`, предназначенной для инженера на этапе разработки, `Catch` служит автоматическим стражем системы в производственной среде.

    Ключевые выводы урока:

    • Ошибки в Node-RED могут быть явно сгенерированы (`node.error()`) или возникать как исключения при сбое работы узлов (например, при сетевых таймаутах).
    • Нода `Catch` позволяет централизованно перехватывать эти ошибки, предотвращая неконтролируемое прерывание потоков.
    • Параметр `Scope` определяет, за какими нодами "наблюдает" `Catch`. Лучшей практикой является использование `All nodes on current tab` на каждой вкладке.
    • Перехваченная ошибка доставляется в виде нового `msg`, где вся информация о сбое упакована в объект `msg.error`. Этот объект содержит текст ошибки, информацию о ноде-источнике и (иногда) стек вызовов.
    • Комбинируя `Catch` с нодами `Function`, `File`, `MQTT Out` и другими, можно создавать гибкие системы логирования, оповещения и даже автоматического восстановления после сбоев.

    Рекомендации для построения надежных потоков:

  • Обязательный `Catch`: Размещайте как минимум одну ноду `Catch` на каждой вкладке вашего проекта. Это ваш первый и самый важный рубеж обороны.
  • Структурированное логирование: Не просто выводите `msg.error.message`. Используйте ноду `Function` для форматирования логов, включая метку времени, имя и тип ноды-источника. Это многократно ускорит будущую диагностику.
  • Разделяйте каналы: Создавайте разные форматы сообщений для разных целей. Подробный лог — в файл или базу данных MySQL, краткое тревожное оповещение — в MQTT или Telegram.
  • Думайте о восстановлении: Перехват ошибки — это только половина дела. Профессиональный подход подразумевает и логику реакции. Для некритичных ошибок (например, временный сбой HTTP-запроса) можно реализовать механизм повторных попыток с помощью ноды `Trigger` или `Delay`. Для критических — активировать безопасный режим работы оборудования (`safe-state`) или переключиться на резервный канал связи.
  • Что дальше:

    В этом уроке мы научились строить централизованный обработчик ошибок. В следующем уроке мы рассмотрим, как инкапсулировать повторяющуюся логику, включая обработку ошибок, в переиспользуемые компоненты с помощью субпотоков (subflows). Это позволит нам создавать стандартные, проверенные блоки для работы с устройствами, которые будут отказоустойчивыми "из коробки".