Руководство по отладке Node.js

Node.js

сейчасNode.jsВсе больше и больше популярных, все больше и больше сценариев приложений, научитесь эффективно отлаживатьNode.jsЭто сделает ежедневную разработку более эффективной. Используйте следующееinspectorотладкаnodejsпрограмма

Node6.3+версия предоставляет два протокола для отладки:v8 Debugger Protocolа такжеv8 Inspector Protocolможно использовать сторонниеClient/IDEДождитесь мониторинга и вмешательства в запущенный процесс Node (v8) для отладки.

v8 Inspector Protocolэто недавно добавленный протокол отладки, который передается черезwebsocket(обычно используется порт 9229) сClient/IDEвзаимодействие, основанное наChrome/ChromiumбраузерdevtoolsПредоставляет графический интерфейс отладки.

1 Включить отладку

1.1 Код сервера отладки

Если ваш скрипт строитсяhttpилиnetсервер, вы можете напрямую использовать--inspect

const Koa = require('koa')
const app = new Koa()

app.use(async ctx => {
  
  let a = 0
  const longCall = () => { 
    while (a < 10e8) { 
      a++
    }
  }
  longCall()
  ctx.body = `Hello ${a}`
})

app.listen(3000, () => { 
  console.log('程序监听了3000端口')
})

использоватьnode --inspect=9229 app.jsзапустите свой скрипт,9229указанный номер порта

# 控制台会输出如下:
/usr/local/bin/node --inspect=9229 src/inspector/demo.js 
Debugger listening on ws://127.0.0.1:9229/c4f1e345-e811-47a2-b44a-65f68c0c2cc3
Debugger attached.
# 可以在浏览器里打开:http://127.0.0.1:9229/json 看到一些信息, c4f1e345-e811-47a2-b44a-65f68c0c2cc3 为uuid,不同调试面板的uuid来区分;

--inspectДля общей программы она мигает, и выполнение завершается до отправки сигнала точки останова. Точки останова вообще не работают, вы можете--inspect-brk;

1.2 Код скрипта отладки

Если вы завершите процесс сразу после запуска вашего скрипта, вам нужно использовать--inspect-brkДля запуска отладчика, чтобы скрипт мог сломаться до выполнения кода, иначе весь код догоняет сразу до конца кода, завершая процесс, и отладка вообще невозможна.

node --inspect-brk=9229 app.js

2 Доступ к инструменту отладки

2.1 VS Code

Vs CodeвстроенныйNode debugger,служба поддержкиv8 Debugger Protocolа такжеv8 Inspector ProtocolДва протокола. дляv8 Inspector Protocol, просто добавьте строку в конфигурациюAttachТип конфигурации

существуетDebugпанель управления, нажмитеsettingsзначок, открыть.vscode/launch.json. Нажмите «Node.js» для первоначальной настройки.

{
  "version": "0.2.0",
  "configurations": [
    {
      "type": "node",
      "request": "launch",
      "name": "Launch Program",
      "program": "${workspaceFolder}/app.js"
    }
  ]
}

2.2 Chrome DevTools

  • Способ 1: открыть в браузере Chromechrome://inspectнажмитеConfigureкнопку, убедитесь, что хост и порт есть в списке.

  • Способ 2: с вышеуказанного хоста и порта/json/listкопироватьdevtoolsFrontendUrlили–-inspectзапросите информацию и скопируйте в Chrome.

2.2.1 Console Panel

chromeдоступ к отладкеnodeПосле процедуры вы можетеConsoleагентNodeВесь вывод консоли в процессе обеспечивает гибкую функцию фильтрации Filter, а также может выполнять код непосредственно в контексте кода процесса Node.

2.2.2 Sources Panel

SourcesВсе загруженные скрипты можно посмотреть в , включая сторонние библиотеки иNodeОсновная библиотека, выбранные файлы можно редактировать,Ctrl + CСохранение может напрямую изменить работающий скрипт.

2.2.3 Profile Panel

ProfileДля мониторинга производительности запущенных скриптов, включая использование ЦП и памяти,CPU profile, который может записывать время процессора, занятое выполнением функций Javascript, на временной шкале.

Существует два типа временных периодов записи профиля.

  • Ручной запуск/остановка: нажмитеstartЧтобы начать запись, нажмитеstopостановить запись
  • Вставьте запуск/остановку вызовов API в кодconsole.profile('tag') console.profileEnd('tag'), допустимыйSourcesОтредактируйте и сохраните код прямо в панели, а затем обновите его, нажав F5.

профиль имеет три вида

  • chart: широко известный как график пламени, он отображает стек вызовов функций со временем в качестве горизонтальной оси. Следующий простой пример анализа

Стек вызовов функций графа пламени инвертирован, вершина — это низ стека, а низ — вершина стека. Стек — этоtick,Одинtickдолжно бытьNodeВызывается из нижнего слоя, используется в узлеprocess.nextTick(fn)а такжеsetTimeout(fn, deloy)Системный обратный вызов будет генерировать новыеtick, соответствующий созданию нового стека вызовов.

Функции вызываются в порядке от нижней части стека к вершине стека. Первый стек на рисунке вышеparserOnHeadersCompleteвызывается нижним слоем,parserOnHeadersCompleteназывается вparserOnIncoming,parserOnIncomingназывается вemit...И так далее.

Ширина стека вызовов — это время, необходимое для выполнения функции. Время выполнения функции включает в себя время выполнения ее внутренних вызовов других функций, поэтому время вызова функции, расположенной относительно близко к низу стека, должно быть больше, чем время вызова функции, близкой к вершине стека. . Исключая время выполнения внутренних вызовов других функций, это время выполнения текущей функции.

Нажмите на функцию, чтобы перейти кSourcesРасположение определения функции на панели.

При наведении курсора на функцию отображаются ее имя и данные:

Следующее объяснение взято издокументация по chrome-devtools

  • Name: Имя функции.

  • Self time: время, необходимое для завершения текущего вызова функции, включая только объявление самой функции, а не какие-либо функции, вызываемые функцией.

  • Total time: время, необходимое для завершения текущего вызова этой функции и любых функций, которые она вызывает.

  • URL: расположение определения функции в формате file.js:100 , где file.js — это имя файла, в котором определена функция, а 100 — номер строки определения.

  • Aggregated self time: общее время всех вызовов функции в записи, исключая функции, вызываемые этой функцией.

  • Aggregated total time: общее время всех вызовов функции, за исключением функций, вызываемых этой функцией.

  • Not optimized: Если анализатор обнаружил возможную оптимизацию функции, она будет указана здесь.

  • heavy(Bottom Up): Статистика снизу вверх, снизу относится к нижней части графика пламени.

  • tree(Top Down): Статистика сверху вниз, сверху относится к вершине графика пламени.

Видно, что большая часть времени программы уходит наlongCallПри вызове этой функции;

2.2.4 Memory profile

Профилировщик кучи может отображать распределение памяти по объектам JavaScript страницы и связанным узлам DOM (см. также Дерево хранения объектов). С помощью анализатора можно стрелятьJS 堆快照,分析内存图,比较快照так же как查找内存泄漏.

3. Реализация прокси-сервера Node Inspector

пройти черезnode inspectorОтладка точки останова — очень распространенный метод отладки. Но было несколько проблем с предыдущей отладкой, которые сделали нашу отладку менее эффективной.

  • существуетvscodeотладка, вinspectorизменение порта илиwebsocket idПовторно подключитесь после изменения.
  • существуетdevtoolsотладка, вinspectorизменение порта илиwebsocket idПовторно подключитесь после изменения.

Как инспектор узлов решает две вышеупомянутые проблемы?

По первому вопросу вvscode, он будет называть себя/jsonинтерфейс для получения последнихwebsocket id, затем используйте новыйwebsocket idСоединен сnode inspectorоказание услуг. Таким образом, решение состоит в том, чтобы реализоватьtcpФункция прокси может выполнять пересылку данных.

По второму вопросу, посколькуdevtoolsне будет автоматически приобретать новыеwebsocket id, поэтому нам нужно сделать динамическую замену, поэтому решение состоит в том, чтобы проксировать службу на/jsonПолучатьwebsocket id, затем вwebsocketпри рукопожатииwebsocket idДелайте динамические замены в заголовках запросов.

Нарисуйте блок-схему:

3.1 TCP-прокси

Во-первых, реализоватьtcpФункция агента на самом деле очень проста, т. е. черезnodeизnetмодуль для создания прокси-портаTcp Server, а затем, когда есть соединение, создайте соединение с целевым портом, после чего данные могут быть перенаправлены.

Простая реализация выглядит следующим образом:

const net = require('net');
const proxyPort = 9229;
const forwardPort = 5858;

net.createServer(client => {
  const server = net.connect({
    host: '127.0.0.1',
    port: forwardPort,
  }, () => {
    client.pipe(server).pipe(client);
  });
  // 如果真要应用到业务中,还得监听一下错误/关闭事件,在连接关闭时即时销毁创建的 socket。
}).listen(proxyPort);

Вышеприведенное реализует относительно простую прокси-службу черезpipeМетод связывает данные двух сервисов.clientКогда данные будут доступны, они будут отправлены наserverсередина,serverКогда есть данные, они также будут отправлены наclientсередина.

когда это будет сделаноTcpПосле функции прокси ее уже можно реализоватьvscodeпотребности в отладкеvscodeниже среднегоlaunch.jsonУказанный порт является портом прокси вconfigurationsдобавить конфигурацию

{
  "type": "node",
  "request": "attach",
  "name": "Attach",
  "protocol": "inspector",
  "restart": true,
  "port": 9229
}

Затем, когда приложение перезапустится, или заменитеinspectпорт,vscodeможет автоматически повторно передать порт проксиattachк вашему приложению.

3.2 Получить идентификатор веб-сокета

В начале этого шага необходимо решитьdevtoolsможно переустановить без изменения ссылкиattachПроблема в том, что при запускеnode inspector serverкогда,inspectorСервис также предоставляет/jsonизhttpинтерфейс для полученияwebsocket id.

Это довольно просто, просто отправьте его напрямуюhttpзапрос к порту назначения/json, вы можете получить данные:

[ { description: 'node.js instance',
    devtoolsFrontendUrl: '...',
    faviconUrl: 'https://nodejs.org/static/favicon.ico',
    id: 'e7ef6313-1ce0-4b07-b690-d3cf5274d8b0',
    title: '/Users/wanghx/Workspace/larva-team/vscode-log/index.js',
    type: 'node',
    url: 'file:///Users/wanghx/Workspace/larva-team/vscode-log/index.js',
    webSocketDebuggerUrl: 'ws://127.0.0.1:5858/e7ef6313-1ce0-4b07-b690-d3cf5274d8b0' } ]

Поле id в приведенных выше данных — это то, что нам нужноwebsocket id.

3.3 Агент-инспектор

понятноwebsocket idПосле этого вы можетеtcpделать через проксиwebsocket idДинамическая замена , сначала нам нужна фиксированная ссылка, поэтому сначала установите прокси-ссылку, например, мой порт прокси-сервиса9229, тогда прокси-ссылка для chrome devtools:

chrome-devtools://devtools/bundled/inspector.html?experiments=true&v8only=true&ws=127.0.0.1:9229/__ws_proxy__

кроме последнегоws=127.0.0.1:9229/__ws_proxy__Остальные зафиксированы, а последний сразу видно, что онwebsocketссылка на. в__ws_proxy__Он используется для занятия пространства, используется вchrome devtoolsИнициировать эту прокси-ссылкуwebsocketКогда делается запрос на рукопожатие,__ws_proxy__заменитьwebsocket idзатем впередnodeизinspectorоказание услуг.

к вышеизложенномуtcpчерез проксиpipeЛогический код можно немного изменить.

const through = require('through2')

client
    .pipe(through.obj((chunk, enc, done) => {
        if (chunk[0] === 0x47 && chunk[1] === 0x45 && chunk[2] === 0x54) {
          const content = chunk.toString();
          if (content.includes('__ws_proxy__')) {
            return done(null, Buffer.from(content.replace('__ws_proxy__', websocketId)));
          }
        }
        done(null, chunk);
      }))
    .pipe(server)
    .pipe(client)

пройти черезthrough2Создаватьtransformstream для внесения изменений в передаваемые данные.

Просто судитьchunkПервые три байтаGET,еслиGETуказать, что это может бытьhttpпросьба, которая может бытьwebsocketзапрос на обновление протокола. Печать заголовков запроса выглядит так:

GET /__ws_proxy__ HTTP/1.1
Host: 127.0.0.1:9229
Connection: Upgrade
Pragma: no-cache
Cache-Control: no-cache
Upgrade: websocket
Origin: chrome-devtools://devtools
Sec-WebSocket-Version: 13

затем поместите в него путь/__ws_proxyзаменить на соответствующийwebsocketId, затем впередnodeизinspector serverна завершениеwebsocketрукопожатие, следующийwebsocketКоммуникация не требует обработки данных, просто пересылает их напрямую.

Далее, даже если перезапустить различные приложения или заменитьinspectorпорты не требуют заменыdebugссылка, просто пере-inspector serverПри перезапуске во всплывающем окне ниже

Один щелчок Reconnect DevTools восстановит отладку.

Ссылаться на:Реализация прокси-сервера Node Inspector