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 = 0const 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
来启动调试器,这样使得脚本可以代码执行之前break,否则,整个代码直接运行到代码结尾,结束进程,根本无法进行调试。
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:在Chrome浏览器打开
chrome://inspect
点击Configure
按钮,确定host和端口在列表中。
- 方法2:从上述host和端口
/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和内存的使用,CPU profile
,可以记录时间线上 Javascript 函数执行时占用的 CPU 时间.
profile 记录时间段有两种
- 手动开始/停止:单击
start
开始记录,单击stop
停止记录 - 在代码中插入开始/停止的 API 调用
console.profile('tag')
console.profileEnd('tag')
,可以在Sources
面板中直接编辑保存代码,然后 F5 刷新一下。
profile有三种视图
- chart:俗称火焰图,以时间为横轴显示函数调用栈。下面简单举例分析
火焰图的函数调用栈是倒置的,最上面为栈底,最下面为栈顶。一个栈是一个 tick
,一个 tick
一定是由 Node
底层开始调用的,在 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
来进行断点调试是一个很常用的 debug 方式。但是以前的调试中有几个问题会导致我们的调试效率降低。
- 在
vscode
中调试,在inspector
端口变更或者websocket id
变更后要重连。 - 在
devtools
中调试,在inspector
端口变更或者websocket id
变更后要重连。
那 node inspector是如何解决上述两个问题呢?
对于第一个问题,在 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 获取 websocketId
这一步开始,就是为了解决 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 Inspector 代理
拿到了 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
创建一个 transform
流来对传输的数据进行一下更改。
简单判断一下 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 即可恢复 debug。
参考:Node Inspector 代理实现
Node.js调试指南相关推荐
- ts 打开sourcemap_调试篇 – Source Map - Node.js 调试指南
4.1.1 什么是 Source Map? 对于 Source Map,想必大家并不陌生,在前端开发中通常要压缩 JavaScript,CSS,以减小体积,加快网页显示.但带来的后果是如果出现错误,就 ...
- 《Node.js开发指南》书评汇总
刚查了下库存,发现订阅<Node.js开发指南>的读者大增,这是为什么呢?看了下近期本书在豆瓣的评论,口碑很好,现将豆瓣的书评汇总如下: ------------------------- ...
- 《写给PHP开发者的Node.js学习指南》一2.2 预定义的PHP变量
本节书摘来自异步社区<写给PHP开发者的Node.js学习指南>一书中的第2章,第2.1节,作者[美]Daniel Howard,更多章节内容可以访问云栖社区"异步社区" ...
- 《Node.js开发指南》读书笔记
继续学学node.js.翻开书首先被惊到=.=:作者BYVoid是清华大学2010级本科--同样是2010级本科,我真是无语凝噎,大学浪费了好多时间.不过过去的已经过去了,接下来好好努力提高才好,加油 ...
- node.js调试 BY:色拉油啊油
用了几天node.js感觉很新奇,但是调试问题实在是愁煞人,开始的时候懒的学习调试方法,看看异常内容就可以了,但随着代码复杂程度的上升,并不是所有错误都是语法错误了,不调试搞不定了,只好搜搜资料,学习 ...
- nodec mysql_Node.js 连接 MySQL 并进行数据库操作 –node.js 开发指南
Node.js是一套用来编写高性能网络服务器的JavaScript工具包 通常在NodeJS开发中我们经常涉及到操作数据库,尤其是 MySQL ,作为应用最为广泛的开源数据库则成为我们的首选,本篇就来 ...
- Node.js 连接 MySQL 并进行数据库操作 –node.js 开发指南
Node.js是一套用来编写高性能网络服务器的JavaScript工具包 通常在NodeJS开发中我们经常涉及到操作数据库,尤其是 MySQL ,作为应用最为广泛的开源数据库则成为我们的首选,本篇就来 ...
- 《node.js开发指南》读后感
<node.js开发指南>这部只有180多页的书,我花了一个多月的业余时间算是粗略看完了.中间因为公司项目的加班,中断了几次.大大拖累进度,现在空出来时间,写一点自己的小小感想吧. 先从缺 ...
- 《Node.js开发指南》MicroBlog项目的问题汇总
重要说明:本博已迁移到 石佳劼的博客,有疑问请到 文章新地址 留言!!! 最近对Node产生了点兴趣,就看了<Node.js开发指南>一书,按照书中的例子敲完了所有代码.书是好书,非常适合 ...
最新文章
- ZStack源码剖析之核心库鉴赏——FlowChain
- web服务器测试web bench
- 移动端各种小技巧及优化体验(网上看到记录一下省的总结了)
- pycharm打开脚本报错Gtk-Message: Failed to load module canberra-gtk-module
- docker删除所有容器和镜像
- 华为NP课程笔记22-防火墙
- 计算机内码和国际码的转换,汉字机内码、国标码和区位码之间转换关系图
- docker 内安装字体
- MacOs 查看本地IP和Mac地址
- Excel表格按行数拆分为多个文件
- win11右键,默认就是显示更多怎么调整 右键 默认右键 右 右
- 打开Visual Studio Community 2017 报出“许可证已过期”
- I2C器件的从设备地址的设置(以AT24C02为例)
- 【目标检测】K-means计算anchors
- 我学设计模式 之 原型模型模式
- Graphics的平移与旋转
- 语句摘抄——第27周
- 古墓里出土的那些奇怪文物,能否证明穿越的存在?
- 我的新博客地址https://xmmup.com
- 基于电压型磁链观测器的异步电机矢量控制学习