Node.js 调试与错误处理
代码写完不等于能跑——一定会有 bug。这一章讲 Node 的调试工具箱:从最基础的 console.log,到 Chrome DevTools 可视化断点,再到 VS Code 的图形化调试器,以及生产环境的错误处理和日志方案。学会高效调试,你的开发效率会成倍提升。
1. console:最基础也最常用
别小看 console.log——它解决 80% 的日常问题。但 console 家族远不止 log,用好它的兄弟姐妹能让调试事半功倍:
// console 不只是 console.log,还有一堆兄弟
console.log("普通日志");
console.info("信息(和 log 几乎一样)");
console.warn("警告(黄色)");
console.error("错误(红色)");
// 打印对象(树状展开,看嵌套最直观)
const user = { name: "小明", age: 20, addr: { city: "北京" } };
console.log(user);
console.dir(user, { depth: null, colors: true });
// 带标签的分组输出
console.group("用户详情");
console.log("姓名:", user.name);
console.log("年龄:", user.age);
console.groupEnd();
// 计时:看一段代码执行多久
console.time("loop");
for (let i = 0; i < 1000000; i++) {}
console.timeEnd("loop");
// 输出: loop: 3.245ms
// 打印堆栈(看调用链)
console.trace("我在哪被调用的?");几个实战技巧:
- 打印对象用
%o:console.log("用户 %o", user),在终端会展开。 - 打印多个值加标签:别只
console.log(x),写成console.log("x =", x)更清楚。 - 生产前删 log:大量 log 会拖慢性能、泄露敏感信息。用专门的日志库替代。
2. Node inspector:Chrome DevTools 调试
当 console 不够用时(复杂逻辑、异步时序问题),就要上真正的断点调试。Node 内置 inspector,可以用 Chrome DevTools 可视化调试:
# Node 内置 inspector:用 Chrome DevTools 调试 Node
# 启动时加 --inspect 参数
node --inspect server.js
# 监听 127.0.0.1:9229,等待调试器连接
# 想在第一行就断下(适合调试启动过程)
node --inspect-brk server.js
# 然后:
# 1. 打开 Chrome 浏览器
# 2. 地址栏输入 chrome://inspect
# 3. 点 "inspect" 链接,会弹出 DevTools
# 4. 在 Sources 面板可以打断点、看变量、单步执行
# 远程调试(调试服务器上的 Node)
node --inspect=0.0.0.0:9229 server.js
# 警告:0.0.0.0 暴露到公网有安全风险,只在受信网络用DevTools 里你能做:
- 断点:点行号设断点,代码运行到这暂停。
- 条件断点:右键行号,设"当 x === 5 时断",排查特定 case。
- Watch:监视变量值,实时变化。
- Call Stack:看当前调用栈,追溯怎么走到这里的。
- Scope:看当前作用域所有变量。
3. VS Code 调试(推荐新手)
VS Code 内置了 Node 调试器,不用切换浏览器,直接在编辑器里按 F5 调试。这是日常开发最方便的方式:
// VS Code 内置 Node 调试器,图形化操作最适合新手
// 1. 打开要调试的文件
// 2. 在行号左边点一下,设置红色断点
// 3. 按 F5(或 Run 面板 → 启动调试)
// 也可以在 .vscode/launch.json 里配置
{
"version": "0.2.0",
"configurations": [
{
"type": "node",
"request": "launch",
"name": "调试当前文件",
"program": "${workspaceFolder}/index.js",
"skipFiles": ["<node_internals>/**"]
},
{
"type": "node",
"request": "attach",
"name": "附加到进程",
"port": 9229
}
]
}
// launch:VS Code 自己启动 Node 并附加调试器
// attach:Node 已经在跑(可能用 --inspect 启动),VS Code 连上去
// 调试时:看变量、条件断点、log point、调用栈、watch 都能用VS Code 调试的几个高级功能:
- Log Point:右键行号选"log point",不暂停代码,只打印一条信息。比
console.log干净(不用改代码,删调试代码)。 - Conditional Breakpoint:条件断点,只满足条件才暂停。
- Attach to Process:附加到已经在跑的 Node 进程(用
--inspect启动的),调试线上问题。
4. debugger 语句:代码里的断点
在代码里写 debugger; 语句,等于手动插入一个断点。配合 inspector 模式,执行到这行会暂停:
// 代码里写 debugger 语句,等于手动打断点
// --inspect 模式下,执行到这行会暂停
function calculate(a, b) {
const result = a * b + 42;
debugger; // 调试器在这里会暂停
return result;
}
calculate(3, 4);
// 配合 node --inspect 使用,排查具体函数逻辑很方便好处是不用去 DevTools 里找文件设断点,直接在代码里标记。但发布前要删掉,不然用户调试模式会卡住。
5. 错误堆栈与自定义错误
JS 的 Error 对象带堆栈信息(stack trace),告诉你"错误从哪抛出、经过哪些函数调用"。读懂堆栈是高级开发的基本功:
// 错误对象带堆栈信息(stack trace)
// 它告诉你"错误从哪抛出、经过哪些函数调用"
function level3() {
throw new Error("出错了!");
}
function level2() {
level3();
}
function level1() {
level2();
}
try {
level1();
} catch (err) {
console.error("错误消息:", err.message);
console.error("堆栈:");
console.error(err.stack);
// Error: 出错了!
// at level3 (app.js:2:11)
// at level2 (app.js:6:3)
// at level1 (app.js:10:3)
// at Object.<anonymous> (app.js:13:3)
}
// 自定义错误:带更多上下文
class HttpError extends Error {
constructor(status, message) {
super(message);
this.name = "HttpError";
this.status = status;
}
}
throw new HttpError(404, "用户不存在");堆栈从上到下是"由内到外"——最上面是抛出错误的地方,往下是调用链。读到 app.js:2:11 这种"文件名:行号:列号",直接定位到出问题的代码。自定义 Error 类能携带更多业务信息(如 HTTP 状态码),让错误处理更精细。
6. 新手常见错误速查
// Node 新手最常遇到的几种错误
// 1. Cannot find module 'xxx'
// 原因:模块名写错,或忘了 npm install
// 解决:检查拼写,跑 npm install xxx
// 2. EADDRINUSE: address already in use :::3000
// 原因:3000 端口被别的进程占了
// 解决:lsof -i:3000 查占用的进程,kill 掉;或换个端口
// 3. ECONNREFUSED
// 原因:连的服务器(数据库、API)没起,或端口/地址错
// 解决:确认目标服务在跑,端口对得上
// 4. undefined is not a function / Cannot read property 'x' of undefined
// 原因:变量是 undefined,你却当对象用
// 解决:用可选链 obj?.x,或先判 if (obj)
// 5. Maximum call stack size exceeded
// 原因:无限递归,函数一直调用自己
// 解决:检查递归的终止条件
// 6. Promises must have a catch
// 原因:Promise 没 catch,失败时漂浮
// 解决:链尾加 .catch,或用 async/await + try/catch遇到不认识的错误,直接复制错误信息去 Google/Stack Overflow,99% 都有人遇到过。学会读错误信息的关键词(EADDRINUSE、Cannot find module、ECONNREFUSED),排查速度会快很多。
7. 生产环境日志
console.log 开发时方便,但生产环境必须用专业日志库——能分级别、写文件、结构化(JSON)、易于收集分析:
// 生产环境不能靠 console.log,要用专业日志库
// 主流选择:
// winston:最经典,功能全
// pino:主打性能,JSON 格式,极快
// log4js:类似 Java log4j,配置灵活
// pino 示例(推荐,性能最好)
const pino = require("pino");
const logger = pino({
level: "info", // 最低级别
transport: {
target: "pino-pretty",
options: { colorize: true }
}
});
logger.info("服务启动");
logger.warn({ mem: process.memoryUsage() }, "内存占用");
logger.error({ err }, "请求失败");
// 日志级别(从低到高):
// debug → info → warn → error → fatal
// 开发环境用 debug,生产用 info 或 warn
// 配合 ELK / Loki / Datadog 等日志系统集中收集分析日志是生产排查问题的唯一线索。出 bug 时,好的日志能让你快速定位;差的日志(或没日志)只能盲猜。原则:
- 结构化:用 JSON 格式,方便机器解析和检索。
- 带上下文:不只记"出错了",要记"哪个用户、什么请求、什么参数"。
- 分级别:debug 开发用,info 关键流程,warn 异常但可恢复,error 报警。
- 集中收集:多机部署时,日志要汇总到一个地方(ELK、Loki、云日志服务)。
- 敏感信息脱敏:密码、token、身份证号别明文记进日志。
8. 进程管理与崩溃恢复
线上 Node 进程崩了怎么办?直接 node app.js 跑的进程崩了就没了。生产环境用进程管理器自动重启:
# PM2:最流行的 Node 进程管理器
# 全局安装
npm install -g pm2
# 启动应用(崩溃自动重启)
pm2 start app.js --name "my-api"
# 常用命令
pm2 list # 查看所有应用
pm2 logs my-api # 看日志
pm2 restart my-api # 重启
pm2 stop my-api # 停止
pm2 delete my-api # 删除
# 开机自启(生成系统服务)
pm2 startup
pm2 save
# 集群模式(利用多核)
pm2 start app.js -i max # 启动 CPU 核心数个进程
# 或者用 Docker + 容器编排(Kubernetes)
# 容器崩了编排系统自动拉起新容器PM2 的核心价值:崩溃自动重启、集群模式利用多核(Node 单进程只用一个核,PM2 起多份分担)、日志管理、零停机重载(pm2 reload)。生产部署 Node 几乎必用。
9. 性能分析(进阶)
服务慢,怎么找出瓶颈?Node 内置性能分析工具:
- CPU profile:DevTools 的 Performance 面板,录制一段,看哪个函数耗时最多。
- --prof:
node --prof app.js生成 v8 日志,分析热点函数。 - clinic.js:Node 官方的诊断工具集,可视化分析 CPU/内存/事件循环。
- 内存快照:DevTools Memory 面板,抓堆快照找内存泄漏。
性能优化的黄金法则:先测量再优化。不要凭感觉优化,用工具找到真正的瓶颈(80% 的时间往往花在 20% 的代码上)。
小结
调试是开发的日常。记住几条:console 解决 80% 问题;VS Code 按 F5 是最方便的图形化调试;读懂错误堆栈是基本功;生产环境用日志库 + 进程管理器。学会高效调试,你写代码的速度会快得多——因为不用再"瞎猜"。
← 上一篇 Node.js Express 框架
返回 Node.js 教程目录