Alfa Brain

Способы отладки NodeJS приложений

Алексей ВечкановАлексей Вечканов   

В современном мире "Фронтенда" необходимо писать не только клиентские приложения на JavaScript, но и серверную часть на NodeJS. Чаще всего такие сервера называют аббревиатурой BFF (Back For Frontend). Часто приходится использовать сервер для рендеринга HTML. Отлаживать такие приложения иногда становится довольно проблематично. Поэтому, я хочу поделиться своим методами.

Изображение статьи

Давайте же приступим.

Inspect

Самое первое, что приходит в голову - это флаг --inspect при запуске программы на NodeJS (см. доку).

Создадим файл index.js со следующим содержимым:

setInterval(() => {
    console.count('Interval');
}, 1000);

Запустим командой node --inspect index.js

В консоли видим вывод:

Debugger listening on ws://127.0.0.1:9229/f7a0b5f7-edf1-4874-83d6-49b07c009f65
For help, see: https://nodejs.org/en/docs/inspector
Debugger attached.

Отлично! Интервал начал свой счет, а программа подключилась к дебаггеру Хрома. Если совсем просто, то при установке Google Chrome устанавливаются дополнительные модули которые слушают сообщения на специальных портах. Обычно это порты 9229 и 9222 (могут быть и другие). Получается, что запуск NodeJS программы начинает слать весь свой вывод на один из этих портов.

Теперь мы можем открыть Инспектор прямо в инструментах разработчика браузера Google Chrome. Если ваше приложение запущено с инспектором, то вы увидите иконку  NodeJS на панели devtools.

Запуск программы с инспектором
Запуск программы с инспектором

Вы можете выводить объекты, массивы и другие сложные типы данных для удобного просмотра в консоли браузера.

Подключение инспектора из дочернего процесса

Пока все супер. Пока запущенный процесс один. Проблемы начинаются когда в разработке участвуют несколько процессов. Чаще всего это запуск веб сервера, webpackDevServer, собирается это все с помощью Webpckack (это тоже процессы node и не один). А запускаются все эти процессы одним родительским, скажем, react-create-app.

В таком случае запуск react-create-app с флагом привяжет инспектор только к родительскому процессу. Но как подключить инспектор к дочерним процессам? Этого тоже можно легко добиться.

Создадим второй файл child.js и обновим содержимое обоих файлов.

index.js

const { spawn } = require('child_process');

const child = spawn('node', ['child.js']);

child.stdout.on('data', data => {
  console.log(`Child stdout:\n${data}`);
});

setInterval(() => {
  console.count('Parent');
}, 1000);

child.js

setInterval(() => {
  console.count('Child');
}, 1000);

Таймеры мы запускаем чтобы процессы NodeJS не завершались. Мы используем модуль spawn для запуска дочернего процесса. Так же подписываемся на все события data. Подробнее про дочерние процессы можете почитать здесь.

Итак, смотрим что получилось:

Создание дочернего процесса и вывод сообщений
Создание дочернего процесса и вывод сообщений

В консоли мы видим логи из родительского сервиса, и логи из дочернего.

Но! Во-первых, это неудобно - все сливается в кашу. Во-вторых, мы почти никогда не имеем доступ к коду фреймворков и сборщиков, чтобы подписаться на сообщения дочерних процессов.

Можем поступить иначе. Добавьте следующую строчку в файл child.js и запустите родительский процесс, но теперь без флага --inspect.

process._debugProcess(process.pid);

Смотрим:

Подключение инспектора прямо из процесса
Подключение инспектора прямо из процесса

Мы видим как подключился инспектор, и подключился именно к дочернему процессу, как мы и хотели. В обычном терминале видим вывод родительского процесса, а в инспекторе вывод дочернего процесса.

Это случилось благодаря методу _debugProcess у глобального объекта process.

process - глобальная переменная на весь процесс nodeJS.

_debugProcess - метод позволяющий подключиться к инспектору на горячую.

process.pid - Уникальный идентификатор текущего процесса.

It' s awesome! Теперь можно в любом месте ваших программ вставить этот вызов и процесс nodeJS подключится к инспектору.  Совсем не нужно думать, что это за процесс, сервер или сборщик. Можно даже сделать snippet для быстрой вставки команды. Я так и сделал:

Snippet для быстрого подключения к инспектору
Snippet для быстрого подключения к инспектору

Инспектор без перезагрузки процесса

В предыдущем способе все хорошо, но есть одно НО. Он требует перезагрузки процесса. Т.е. если вы вставили snippet, вам придется выполнить рестарт процесса, например сделать рестарт create-react-app, что может быть довольно затратным по времени.

Бывают случаи, когда перезагружать процесс вообще не желательно, например когда пытаешься найти баг и нужно подключить инспектор действительно "на горячую". И такой способ тоже имеется. Данный способ подойдет владельцам операционных систем UNIX Like (Такие, как MacOS или дистрибутивы Linux).

Изучим пару важных вещей: Сигнал и программу kill.

Сигнал в операционных системах семейства Unix — асинхронное уведомление процесса о каком-либо событии, один из основных способов взаимодействия между процессами. Когда сигнал послан процессу, операционная система прерывает выполнение процесса, при этом, если процесс установил собственный обработчик сигнала, операционная система запускает этот обработчик, передав ему информацию о сигнале, если процесс не установил обработчика, то выполняется обработчик по умолчанию (Выписка из вики).

На той же официальной доке про инспектор написано следующее: 

Node.js также начнет прослушивать отладочные сообщения, если получит сигнал SIGUSR1. (SIGUSR1 недоступен в Windows.) В Node.js 7 и более ранних версиях это активирует устаревший API-интерфейс отладчика. В Node.js 8 и более поздних версиях он активирует Inspector API.

Вообще сигналы SIGUSR1 и SIGUSR2  это пользовательские сигналы, которые могут быть использованы для межпроцессной синхронизации и управления. Подробнее можете изучить тему этих сигналов в этом видео.

Важно понимать, что хоть мы и пишем на JS используя NodeJS, но сама нода написана на С++.

Ок, что же нам нужно сделать? Для начала определим требования.

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

Первое, что нужно сделать, это найти нужный запущенный процесс. Сделать это можно с помощью следующей команды:

ps | grep node

ps - программа ps выводит все запущенные процессы.

| - передача текстового вывода в другую программу.

grep - программа фильтр, отбросит все ненужные строчки.

node - слово которое должно присутствовать в строке, по сути это регулярное выражение.

У меня получилось так:

Поиск нужного процесса
Поиск нужного процесса

Нужный мне процесс 17860 ttys003 0:00.05 node child.js

Отлично, процесс найден, как же послать в него сигнал SIGUSR1? Для этого используется, программа kill. Обычно эта программа "убивает" процессы (на самом деле просто шлет сигналы об их завершении), но может использоваться иначе, для отсылки любых других сигналов в запущенные процессы.

Вывод документации по программе kill
Вывод документации по программе kill

Это все! Теперь достаточно просто послать нужный сигнал на нужный процесс:

kill -usr1 17860
Отсылка сигнала SIGUSR1 нужному процессу. Номер процесса у меня уже отличается так как я запускал программы несколько раз.
Отсылка сигнала SIGUSR1 нужному процессу. Номер процесса у меня уже отличается так как я запускал программы несколько раз.

Заключение

Я показал три способа как подключить инспектор к вашему NodeJS приложению. Наверняка найдется еще способы, но это то, чем я пользуюсь повседневно. Буду рад если вы дадите обратную связь написав комментарий. Спасибо и хорошего вам дня.



Поделиться: