调试技巧
本教程共 76 篇 · 第 64 篇 · 更新于 2026-07-25 · 约 7 分钟阅读
64. 调试技巧
本节目标:-
--inspect/--inspect-brk/--inspect-wait的区别与用法 - 用 Chrome DevTools 连上 Node 进程,设置断点、单步、看变量 - VS Code 的launch.json断点调试 -debug模块做条件式日志 - 异步代码调试的几个要点与安全隐患提醒
console.log 是每个人的第一行调试代码,但面对异步调用栈、内存泄漏、只在特定时序下出现的 race condition,console.log 就显得力不从心了。Node.js 内置了 V8 Inspector 协议,能让你用 Chrome DevTools 或 VS Code 像调试前端一样,打断点、单步执行、实时看变量。这一章把这些调试手段理一遍。
Inspector 是什么
Node.js 内置了一个叫 Inspector 的调试代理(agent)。启动时带上 --inspect,它就在 127.0.0.1:9229 监听一个调试客户端。这个客户端可以是任何实现了 V8 Inspector 协议的工具,最常见的是 Chrome DevTools 和 VS Code。
最基础的启动:
node --inspect app.js
进程照常运行你的代码,同时开放了调试端口。默认绑定 127.0.0.1(本机),不对外暴露,这点很重要,等下讲安全时再说。
几个相关开关,别搞混:
| 标志 | 行为 |
|---|---|
--inspect | 正常启动,同时开调试端口 |
--inspect-brk | 在用户代码第一行之前暂停,等你连上再继续 |
--inspect-wait | 启动后等待调试器连上才继续执行 |
--inspect=host:port | 自定义绑定的地址和端口 |
--inspect-brk 是我最常用的一招:它让程序「悬」在第一行,你连上去设好断点,再让它在入口处继续。新手最容易犯的错是 --inspect 一启动程序就跑完了,你还没来得及断点,进程已经退出了。-brk 完美避开这个坑。
Tip想在已经运行的进程上开调试?给进程发
SIGUSR1信号(Linux/macOS):kill -SIGUSR1 <pid>。它会当场激活 Inspector。注意 Windows 不支持这个信号。
用 Chrome DevTools 调试
步骤很简单:
- 终端跑
node --inspect-brk app.js,你会看到一行提示,说 Inspector 监听在ws://127.0.0.1:9229/...。 - 打开 Chrome 浏览器,地址栏输入
chrome://inspect。 - 点「Configure」确认目标 host:port 在列表里,稍等片刻,你的 Node 进程会出现在 Remote Target 列表。
- 点进程下方的「inspect」,弹出 DevTools 窗口,停在入口处。
连上之后,Sources 面板就是主战场:
- 断点:在代码行号左边点一下,运行到那行就停。
- 单步:Step Over 跨过、Step Into 进函数、Step Out 跳出。
- Scope:暂停时看当前作用域里的局部变量、全局变量,实时值一目了然。
- Call Stack:当前调用栈,包括异步调用链(开启 Async 后能看到
await的前世今生)。 - Console:在暂停点直接敲表达式求值,比如
user.name、JSON.stringify(data)。
Note勾上暂停按钮旁边那个「Pause on caught exceptions」(带波浪线的圆圈),程序一抛错就自动停,省得你手动在出错位置找断点。排查偶发异常特别好用。
在 VS Code 里断点调试
VS Code 对 Node 调试是一等公民,体验比切到浏览器顺手。核心是一个 .vscode/launch.json:
{
"version": "0.2.0",
"configurations": [
{
"type": "node",
"request": "launch",
"name": "启动程序",
"program": "${workspaceFolder}/app.js",
"skipFiles": ["<node_internals>/**"]
},
{
"type": "node",
"request": "attach",
"name": "附加到进程",
"port": 9229
}
]
}
launch 是「启动并调试」:点运行按钮,VS Code 自己带 --inspect 起进程,断点直接生效。attach 是「你已经用 node --inspect 起了进程,VS Code 再连上去」,适合调试已经在跑的服务。
VS Code 还有几个杀手锏:
- 条件断点:右键断点,设个条件,比如
userId === 3,只有满足时才停,避免在循环里每次都断。 - Logpoints:右键断点选「转为 Logpoint」,填一段表达式,命中时往控制台打印,但不改代码、不暂停。相当于临时
console.log又不留痕。 - Watch:把
user.age、cache.size这类表达式加进 Watch 面板,单步时实时看它们变化。
Tip嫌配
launch.json麻烦?VS Code 有个「JavaScript Debug Terminal」:开一个这种终端,在里面node app.js跑起来,它会自动附加调试器,断点直接生效,连配置都不用写。
debug 模块:更聪明的日志
console.log 满天飞的问题在于:上线时你得一个个删,或者它们一直刷屏。Node 生态里有个老牌的 debug 模块,让你按「命名空间」开关节志:
npm install debug
import debug from 'debug';
const logServer = debug('app:server');
const logDb = debug('app:database');
logServer('服务启动在 %d 端口', 3000);
logDb('连上数据库 %s', 'mongodb://localhost');
默认这些日志不输出。想看哪个,用环境变量打开:
# 只看 server 和 auth 两个命名空间
DEBUG=app:server,app:auth node app.js
# 看 app: 下所有
DEBUG=app:* node app.js
# 全开但排除 database
DEBUG=app:*,-app:database node app.js
每个命名空间有独立颜色,还带时间戳和间隔毫秒数。比满屏 console.log 清爽太多,而且生产环境不设这个变量就零开销。
Warning
debug输出里可能带敏感信息(用户 ID、token 片段)。生产环境除非排查问题,否则别开着DEBUG=*全量打印,日志里泄密比你想的常见。
异步代码调试要点
Node.js 的异步是调试里最容易晕的部分。几个实用建议:
- 用
async/await代替深层.then嵌套,调用栈是线性的,断点也好打。 - 在 DevTools / VS Code 里开启「Async stack traces」,能看清一个
await是从哪条异步链路过来的,不再是孤零零的一帧。 - 顶层一定要监听
unhandledRejection和uncaughtException,否则 Promise 悄悄 reject 了你根本不知道:
process.on('unhandledRejection', (reason) => {
console.error('未处理的 Promise 拒绝:', reason);
});
- 跨异步边界追踪时,给请求加个 correlation id(关联 ID),日志里串起同一次请求的所有步骤。
安全:别把调试端口暴露出去
最后这点很多教程不提,但很关键。Inspector 对进程有完全控制权——连上的人能执行任意代码。所以:
- 默认
--inspect只绑127.0.0.1,本机外连不上,这是安全的。 - 千万别把它绑到
0.0.0.0或公网 IP,否则任何人都能连进来跑代码,等于把服务器大门敞开。 - 需要远程调试时,正确做法是走 SSH 隧道:远端照常
node --inspect app.js(只绑本机),本地用ssh -L 9221:localhost:9229 user@remote把远端 9229 映射到本地 9221,然后本地 DevTools 连localhost:9221。这样流量走加密隧道,不直接暴露端口。
Note即便绑在
127.0.0.1,本机其他程序也能连上 Inspector,这是设计如此(方便本地调试器接入)。所以调试端口不要和不可信的本地程序共处同一台机器跑敏感服务。
排查套路小结
遇到诡异 bug,我的常规顺序是:先 --inspect-brk 起进程,在可疑函数前设断点,单步看变量;异步问题开 Async 栈、加 correlation id;日志太多就换 debug 模块按命名空间开关。真到了内存或 CPU 层面,再上 --prof 和堆快照(这部分放性能与诊断相关章节细讲)。工具到位了,调试从「玄学」变成「按图索骥」。