4、Node.js调试:使用--inspect标志、Chrome DevTools调试Node、VS Code调试器配置、ndb工具
调试Node.js,说实话,很多新手第一反应就是console.log一把梭。我以前也这么干,直到有一次在生产环境排查一个内存泄漏,打印了上百行日志还是找不到根因。从那以后,我老老实实把调试工具学了个遍。今天聊的这几个,是我日常工作中最常用的。
4.1 --inspect标志:开启调试的钥匙
Node.js内置了V8 Inspector协议。你只需要在启动时加一个参数:
node --inspect app.js
运行后,终端会输出类似这样的信息:
Debugger listening on ws://127.0.0.1:9229/xxx-xxx-xxx
这表示调试器已经启动,监听在9229端口。说白了,就是Node.js开了一个WebSocket服务,等着调试客户端连上来。
我个人习惯用--inspect-brk代替--inspect。区别在哪?--inspect-brk会在第一行代码处暂停,等你连上调试器再继续。这对调试启动阶段的逻辑特别有用。
--inspect=0.0.0.0:9229,否则默认只监听127.0.0.1,外部连不上。
4.2 Chrome DevTools调试Node:可视化断点
有了--inspect,接下来就是连上Chrome。打开Chrome,地址栏输入chrome://inspect,你会看到一个设备列表。点击「Open dedicated DevTools for Node」,就能看到熟悉的DevTools界面了。
嗯,这里要注意:有时候Chrome找不到你的Node进程。别慌,检查一下Remote Target那里有没有勾选「Discover network targets」。如果还不行,手动添加localhost:9229。
在Chrome DevTools里调试Node,体验和调试前端代码几乎一样。你可以:
- 在Sources面板打断点
- 查看调用栈和变量
- 在Console里执行表达式
- 使用Profile面板分析性能
我记得有一次排查一个异步回调地狱的问题,用Chrome DevTools的异步调用栈追踪,一眼就看出是哪个Promise没有正确resolve。要是靠console.log,估计得折腾半天。
4.3 VS Code调试器配置:一键启动
如果你和我一样,主力编辑器是VS Code,那内置调试器绝对是最顺手的。不需要开浏览器,直接在编辑器里打断点、看变量、走单步。
配置方式很简单。在项目根目录创建.vscode/launch.json文件,然后写一个配置:
{
"version": "0.2.0",
"configurations": [
{
"type": "node",
"request": "launch",
"name": "启动程序",
"skipFiles": ["<node_internals>/**"],
"program": "${workspaceFolder}/app.js"
}
]
}
这里有几个关键字段:
| 字段 | 说明 |
|---|---|
| type | 调试器类型,Node就是"node" |
| request | "launch"表示启动新进程,"attach"表示连接到已有进程 |
| skipFiles | 跳过Node内部文件,避免看到一堆底层代码 |
| program | 入口文件路径 |
配置好之后,按F5就能启动调试。我个人习惯在launch.json里加一个"restart": true,这样修改代码后自动重启调试,省得每次手动停掉再启动。
skipFiles配置把node_modules也跳过了。如果你要调试第三方库,记得把skipFiles里的路径去掉。
4.4 ndb工具:专为Node打造的调试器
ndb是Google Chrome Labs出品的一个工具。它本质上是对Chrome DevTools的封装,但做了很多针对Node的优化。
安装很简单:
npm install -g ndb
使用也简单:
ndb app.js
它会自动打开一个独立的Chrome窗口,里面已经连好了调试器。你想想看,省去了手动打开chrome://inspect的步骤。
ndb有几个让我眼前一亮的功能:
- 自动检测入口文件:从
package.json的scripts里找启动命令 - 脚本面板:直接显示所有加载的模块,包括node_modules里的
- Worker调试:对Worker线程的调试支持更好
不过说实话,ndb现在用得少了。因为VS Code的调试器已经足够强大,而且ndb偶尔会有兼容性问题。但如果你是第一次接触Node调试,ndb的零配置体验确实很友好。
4.5 调试实战:一个常见场景
假设你有一个Express应用,某个接口返回了错误的数据。你会怎么调试?
我的做法是:
- 在路由处理函数的第一行打上断点
- 发送请求,触发断点
- 单步执行,观察每个变量的值
- 如果发现某个中间件修改了数据,就在那个中间件里也打上断点
- 用调用栈面板查看执行路径
整个过程不需要写一行console.log。而且断点可以随时添加或删除,比打印日志灵活得多。
为什么会有人坚持用console.log?我觉得可能是习惯了。但一旦你体验过断点调试的精准和高效,就再也回不去了。