Node.js 调试与观测:Inspector、日志与性能剖析
线上「跑不起来」往往比「写不出来」更费时间;Node 又是异步优先,栈信息一截就断。
这一篇想先把观测手段摆清楚:调试器(Inspector)在什么场景用、日志与结构化输出怎么接、以及性能问题该从哪几类工具下手,而不是一上来就加console.log扫雷。
调试器:Inspector 协议与「能停住」的价值
Node 内置对 Chrome DevTools 协议(Inspector) 的支持,常见用法:
- 启动时加
--inspect(或--inspect-brk在第一行就停住),用浏览器连上 调试端口; - 在 VS Code / WebStorm 里直接 Attach 到进程,设断点、看调用栈、检查闭包变量。
和「print 调试」的差别在于:
- 断点让你在「时间真的停在这一刻」时看状态,而不是事后猜日志顺序;
- 异步栈(在较新工具链里)能帮你看到 Promise/async 背后丢掉的上下文——这对 Node 特别关键。
实践上:
- 本地开发:优先用 IDE 断点 + 条件断点;
- 短时附着线上:要谨慎(端口暴露、性能开销),通常配合 只读安全策略 和 短窗口开启。
console 与日志:够用、但要分层
console.log / console.error 在脚本里很快,在服务端里容易失控:
- 输出格式不统一,难以检索;
- 高频路径上字符串拼接本身也是成本;
- 敏感信息容易顺手打出去。
更稳的习惯是引入 结构化日志(例如 pino、winston 等):
- 每条日志带 级别(error / warn / info / debug)、时间戳、请求 id / 链路 id;
- 输出 JSON 行,方便丢进 ELK、Loki、CloudWatch 里查。
心智模型可以记一句:
- 日志是「可查询的事实流」,不是「给人看的 println」。
错误与未捕获异常:别让它悄悄退出
和观测强相关的有两类钩子:
process.on('uncaughtException'):未捕获的同步异常;process.on('unhandledRejection'):未处理的 Promise 拒绝。
工程含义:
- 长期依赖「全局吞掉一切继续跑」很危险,容易进入 不确定状态;
- 更常见的做法是:记录 + 告警 + 按策略退出(让进程管理器重启),而不是假装没事。
也就是说,观测里要包含「进程为什么挂」这一条,而不是只看业务指标。
性能剖析:先分清 CPU、I/O 与事件循环
遇到「慢」或「卡」,大致先归三类:
- CPU 热点:纯计算、加密、序列化过重 → 看 CPU profile(Inspector 里也能采);
- I/O 等待:外部服务慢、连接池打满 → 看 延迟分布、依赖的 trace、连接数;
- 事件循环滞后:回调排队、
nextTick滥用、同步阻塞 → 看event loop lag指标、诊断报告。
Node 自带的 --prof / V8 采样、以及 clinic.js 一类工具,适合在复现环境里跑一轮,看 火焰图 上到底是哪段 JS 在吃时间。
避免一上来就:
- 盲目加缓存;
- 把异步全改成同步「求稳」——往往适得其反。
诊断报告:heap snapshot 与内存泄漏
内存持续上涨时:
heap snapshot对比两个时间点的对象分布,找 泄漏类型(闭包挂着大对象、全局 Map 只增不减等);- 配合 GC 日志(启动参数打开,版本间略有差异,以当前 Node 文档为准)看回收是否正常。
这类工作通常不在热路径上天天做,但属于 后端必备技能清单 里的一项。
小结:观测是闭环,不是「多打几行 log」
- Inspector 解决「停在真实时刻看状态」,适合复杂异步与断点调试;
- 结构化日志 + 错误钩子 解决「线上可追溯」,要和部署、告警一起设计;
- 性能与内存工具 解决「慢在哪、胀在哪」,先分类再下刀。
把这套手段备齐,后面接 HTTP 服务、进程拆分、微服务链路时,才有地方下手查问题。