关于 Node.js 调试,你需要了解的一切
关于 Node.js 调试,你需要了解的一切
![图12: nodejs Debug关于 Node.js 调试,你需要了解的一切](https://wkee.net/techug/uploads/2023/07/everything-you-need-to-know-about-node-js-debugging74b159004e8cd4de8f9a.01.jpeg)
Node.js 是一种颇具人气的 JavaScript 运行时,与谷歌 Chrome 浏览器一样采用同款 V8 引擎。
Node.js 具备跨平台属性,目前已经成为服务器端 Web 应用程序开发、工具构建和命令行应用程序等领域的主流选项。
但体验过 Node.js 的朋友往往发现,一旦编写代码并尝试运行,往往难以轻松处理深藏其中的问题。幸运的时候,代码崩溃还能显示明确的错误信息;但如果运气不好,应用程序仍能勉强运行,只是结果与开发者预期相去甚远。
什么是调试?
所谓调试,就是修复软件缺陷的艺术。修复 bug 并不高深,大多数问题其实就是由字符错录或代码行里的小问题引发,但查找 bug 却是无缘艰难。开发人员往往得花上大量时间才能抽丝剥茧、厘清问题的根源。
以下几种方法能帮助大家有效规避错误:
-
使用高质量的代码编辑器,应具备行编号、彩色编码、代码校验、自动补全、括号匹配、参数提示等功能。
-
使用 Git 等源代码控制系统以管理代理修订工作。这些工具能帮助开发者检查更新,定位 bug 出现的方式、时间和位置。
-
采用 bug 跟踪系统,例如 Jira、FogBugz 以及 Bugzilla 等。它们能向开发者报告 bug、高亮显示重复项、记录重现步骤、确定 bug 严重性、计算优先级、分配开发人员、记录讨论线索并跟踪修复进度。
-
使用测试驱动开发(TDD)方法。TDD 是一种开发过程,鼓励开发者在编写函数之前先用编码测试该函数的运行效果。
-
尝试使用代码解释或结对编程等方法同其他开发者携手合作,对方提供的全新视角能帮助我们发现自己遗漏的问题。
但没有哪种解决方案能够直接消除所有错误,而且任何一种编程语言都免不了出现以下几种错误类型。
语法错误
如果代码内容未遵循某些语言规则,就会触发错误。常见的语法错误包括拼写错误或缺少括号等。
VS Code 等优秀代码编辑器能帮助大家在实际运行代码之前,预先检查各种常见的 Node.js 问题:
-
将有效和无效语句标记为彩色形式;
-
自动补全函数和变量名称;
-
高亮显示匹配的括号;
-
自动缩进代码块;
-
为函数、属性和方法提供参数提示;
-
检测无法访问的代码;
-
重构混乱函数。
可以使用 ESLint 等代码检查器寻找各种语法问题,或者不符合正常编码风格的情况。使用以下命令,即可将 ESLint 安装为全局 Node.js 模块:
npm i eslint -g
而后通过命令行检查 JavaScript 文件:
eslint code.js
ESLint for VS Code 扩展程序的效果更好,能在我们输入的同时对代码内容做验证:
![图0: nodejs Debug关于 Node.js 调试,你需要了解的一切](https://wkee.net/techug/uploads/2023/07/everything-you-need-to-know-about-node-js-debugging74b159004e8cd4de8f9a.02.png)
逻辑错误
逻辑错误意味着我们的代码可以运行,但却无法达成预期的效果。例如,用户无法使用有效凭证正常登录;报告中的统计信息不正确;用户数据未被保存至数据库等。引发逻辑错误的原因多种多样,包括:
-
使用了不正确的变量名称;
-
使用了不正确的条件,例如应该是 if(x>5) 而非 if(x<5);
-
使用了无效的函数、参数或算法。
我们往往需要分步执行代码,并在过程当中检查特定的运行状态点。
运行时错误
运行时错误主要影响的是应用程序的执行过程。代码执行可能并不出错,但也随时可能被无效的用户输入而意外触发。例如:
-
尝试将某个值除以零;
-
访问目前已不存在的数组项或数据库记录;
-
在不具备适当访问权限的情况下,尝试写入文件;
-
不正确的异步函数实现会引发“内存溢出”崩溃。
众所周知,运行时错误往往很难重现,所以保持良好的日志记录习惯至关重要。
Node.js 调试中的环境变量
主机操作系统中的环境变量负责控制 Node.js 应用程序的具体设置。最常见的环境变量是 NODE_ENV,一般在调试时被设定为 development、在 production 过程中则被设定为 production。
大家可以在 Linux/macOS 上这样设置环境变量:
NODE_ENV=development
在 Windows(旧版 DOS)命令行中这样设置:
set NODE_ENV=development
在 Windows Powershell 上则是这样设置:
$env:NODE_ENV="development"
应用程序可以检测环境设置,并在必要时启用调试消息,例如:
// running in development mode?
const DEVMODE = (process.env.NODE_ENV === 'development');
if (DEVMODE) {
console.log('application started in development mode');
}
NODE_DEBUG 需要使用 Node.js util.debuglog 来启用调试消息(后文中的 Node.js util.debuglog 部分将具体介绍)。另外,请注意检查主模块和框架的说明文档,了解更多日志记录选项。
使用 Node.js 命令行选项进行调试
在启动应用程序时,您可以将命令行选项传递给 node 或 nodemon 运行时。其中最有用的选项之一当数—trace-warnings,它会在无法解析或拒绝 promise 时输出栈跟踪信息:
node --trace-warnings index.js
其他选项包括:
-
–enable-source-maps: 使用 TypeScript 等转译器时,启用源映射
-
–throw-deprecation: 在使用已被弃用的功能时,抛出错误
-
–inspect: 激活 V8 检查器(具体请参阅后文中的 Node.js V8 检查器部分)
使用控制台日志进行调试
最简单的应用程序调试方法,就是在执行期间将值输出至控制台:
console.log(`myVariable: ${ myVariable }`);
有些开发者坚持认为 console.log() 这东西没有意义,因为代码本身一直在不断变更,而且还有更好的调试选项可用。话虽没错,但大家还是会经常用到 console.log(),而且任何能提高编程效率的工具都有价值。控制台日志就是这样一种快速且实用的选项,能帮助大家切实找到并修复 bug。
当然,除了标准的 console.log() 之外,大家也可以考虑其他几个选项:
![图1: nodejs Debug关于 Node.js 调试,你需要了解的一切](https://wkee.net/techug/uploads/2023/07/everything-you-need-to-know-about-node-js-debugging74b159004e8cd4de8f9a.03.png)
console.log() 支持以逗号分隔各值的列表,例如:
let x = 123;
console.log('x:', x);
// x: 123
ES6 的解构赋值能以更简洁的方式提供类似输出:
console.log({ x });
// { x: 123 }
util.inspect 能够格式化对象以方便阅读,而 console.dir() 能会帮助大家完成其他费时费力的工作:
console.dir(myObject, { depth: null, color: true });
Node.js util.debuglog
Node.js 的标准 util 模块提供 debuglog 方法,能够按特定条件将日志消息写入 STDERR:
const util = require('util');
const debuglog = util.debuglog('myapp');
debuglog('myapp debug message [%d]', 123);
当大家将 NODE_DEBUG 环境变量设置为 myapp 或通配符形式(例如或*my*)时,控制台会显示以下调试消息:
MYAPP 4321: myapp debug message [123]
其中 4321 代表 Node.js 的进程 ID。
使用日志模块进行调试
Node.js 支持各种第三方日志记录模块,我们可以根据需求具体选择消息传递级别、详细程度、排序、文件输出、分析、报告等:
-
cabin
-
loglevel
-
morgan (Express.js 中间件)
-
pino
-
signale
-
storyboard
-
tracer
-
winston
使用 Node.js V8 检查器进行调试
Node.js 是围绕 V9 JS 引擎构建的打包器。V8 引擎中包含自己的检查器和调试客户端,这里就从检查参数起步(注意,不要将其与后文中「使用 Chrome 调试 Node.js 应用程序」中提到的—inspect 标志混淆):
node inspect index.js
调试器会在第一行暂停,并显示以下 debug 提示:
$ node inspect index.js
< Debugger listening on ws://127.0.0.1:9229/b9b6639c-bbca-4f1d-99f9-d81928c8167c
< For help, see: https://nodejs.org/en/docs/inspector
<
connecting to 127.0.0.1:9229 ... ok
< Debugger attached.
<
Break on start in index.js:4
2
3 const
> 4 port = (process.argv[2] || process.env.PORT || 3000),
5 http = require('http');
6
输入 help 可查看命令列表。大家可以使用以下步骤逐步跑通应用程序:
-
cont 或 c: 继续执行
-
next 或 n: 运行下一条命令
-
step 或 s: 单步执行被调用函数
-
out 或 o: 跳出被调用函数并返回其调用者
-
pause: 暂停运行代码
还可以:
-
使用 watch(‘x’) 查看变量值;
-
使用 setBreakpoint()/sb() 命令设置断点(也可以在代码中插入 debugger; 语句);
-
restart 重启脚本;
-
.exit 退出调试器(请注意开头的. 句点)。
整个操作过程似乎不太方便,确实如此。所以除非实在没有其他方法,否则尽量不要使用内置的调试客户端。
使用 Chrome 调试 Node.js 应用
使用—inspect 标志启动 Node.js V8 检查器:
node --inspect index.js
(nodemon 也支持此标志。)
此命令会在 127.0.0.1:9229 端口上启动侦听调试器:
Debugger listening on ws://127.0.0.1:9229/4b0c9bad-9a25-499e-94ff-87c90afda461
如果大家在其他设备或 Docker 容器上运行 Node.js 应用,请确保端口 9229 可以访问,具体使用以下命令授予远程访问权限:
node --inspect=0.0.0.0:9229 index.js
与—inspect 不同,我们可以使用—inspect-brk 停止对首条语句的处理,以便逐步分步执行。
打开 Chrome 网络浏览器(或者其他基于 Chromium 内核的浏览器),并在地址栏中输入 chrome://inspect:
![图4: nodejs Debug关于 Node.js 调试,你需要了解的一切](https://wkee.net/techug/uploads/2023/07/everything-you-need-to-know-about-node-js-debugging74b159004e8cd4de8f9a.04.png)
几秒后,您的 Node.js 应用就会显示为 Remote Target。如果仍未找到,请选中 Discover network targets,而后单击 Configure 按钮为运行应用的设备添加 IP 地址和端口。
单击目标的 inspect 链接以启动 DevTools。对于熟悉在浏览器上调试客户端应用的朋友,整个操作流程应该非常顺畅。
![图2: nodejs Debug关于 Node.js 调试,你需要了解的一切](https://wkee.net/techug/uploads/2023/07/everything-you-need-to-know-about-node-js-debugging74b159004e8cd4de8f9a.05.png)
要直接从 DevTools 加载、编辑和保存文件,请打开 Sources 窗格,单击 + Add folder to workspace 向工作区添加文件夹。之后选择 Node.js 文件的位置,而后单击 Agree。现在,我们可以从左侧窗格或按 Ctrl | Cmd + P 并输入文件名。
单击任何行号以设置断点(显示为蓝色标记):
![图3: nodejs Debug关于 Node.js 调试,你需要了解的一切](https://wkee.net/techug/uploads/2023/07/everything-you-need-to-know-about-node-js-debugging74b159004e8cd4de8f9a.06.png)
这里的 breakpoint 断点,负责指定调试器应在何处暂停处理。我们可以借此检查程序状态,包括局部和全局变量。您可以定义任意数量的断点,或向代码中添加调试器语句,这些语句会在调试器开始运行时停止处理。
![图5: nodejs Debug关于 Node.js 调试,你需要了解的一切](https://wkee.net/techug/uploads/2023/07/everything-you-need-to-know-about-node-js-debugging74b159004e8cd4de8f9a.07.png)
右侧面板显示以下内容:
-
Watch 窗格中,您可以通过单击 + 图标以输入变量名称并监视变量
-
Breakpoint 窗格中,您可以查看、启用和禁用断点
-
Scope 窗格中,您可以检查所有变量
-
Call Stack 窗格中,您可以查看达到此点前所调用的所有函数
Paused on breakpoint“在断点处暂停”上方,会出现一行图标。
![图6: nodejs Debug关于 Node.js 调试,你需要了解的一切](https://wkee.net/techug/uploads/2023/07/everything-you-need-to-know-about-node-js-debugging74b159004e8cd4de8f9a.08.png)
从左至右,各图标分别对应以下操作:
-
resume execution: 继续处理至下一断点
-
step over: 执行下一条命令,但停留在当前函数内;不跳转至命令所调用的任何其他函数
-
step into: 执行下一条命令,并跳转至命令所调用的任何其他函数
-
step out: 继续处理至函数末尾,而后返回至调用命令
-
step: 与 step into 类似,但不会跳转至 async 函数中
-
deactivate all breakpoints:禁用所有断点
-
pause on exceptions: 当发生错误时,停止处理
在 Chrome 中设置条件断点
假设我们有一个运行 1000 次迭代的循环,但真正需要关注的是最后一次迭代的状态:
for (let i = 0; i < 1000; i++) {
// set breakpoint here?
}
这里我们当然无需对着 resume 单击 999 次,而是右键单击该行并选择 Add conditional breakpoint 添加条件断点,而后输入条件,例如 i=999:
![图7: nodejs Debug关于 Node.js 调试,你需要了解的一切](https://wkee.net/techug/uploads/2023/07/everything-you-need-to-know-about-node-js-debugging74b159004e8cd4de8f9a.09.png)
条件断点会显示为黄色,而非蓝色。
在 Chrome 中设置日志点
日志点为 console.log(),不涉及任何代码!执行此代码时会输出一条表达式,但与断点不同的是,处理过程不会暂停。要添加日志点,先右键单击任意行,选择 Add log point 添加日志点,而后输入表达式,例如’loop counter I’,i。
![图8: nodejs Debug关于 Node.js 调试,你需要了解的一切](https://wkee.net/techug/uploads/2023/07/everything-you-need-to-know-about-node-js-debugging74b159004e8cd4de8f9a.10.png)
使用 VS Code 调试 Node.js 应用
VS Code 支持 Node.js,而且提供内置调试客户端。在本地系统上运行 Node.js 应用时无需任何配置。只要打开启动脚本(一般为 index.js),激活 Run and Debug 窗格,点击 Run and Debug Node.js 按钮,再选择相应的 Node.js 环境。之后单击任意行即可激活断点。
如果您正在运行 Web 应用程序,可在任意浏览器中打开,VS Code 会在遇到断点或 debugger 语句时停止执行:
![图9: nodejs Debug关于 Node.js 调试,你需要了解的一切](https://wkee.net/techug/uploads/2023/07/everything-you-need-to-know-about-node-js-debugging74b159004e8cd4de8f9a.11.png)
VS Code 调试方法与 Chrome DevTools 中的 Variables、Watch、Call stack 和 Breakpoints 窗格类似。其中 Loaded Scripts 窗格会显示应用程序所加载的各脚本,也包括 Node.js 的内部脚本。
操作图标工具栏提供以下功能:
-
resume execution: 继续处理至下一断点
-
step over: 执行下一条命令,但停留在当前函数之内;不跳转至命令调用的任何函数
-
step into: 执行下一条命令,并跳转至它调用的任何其他函数
-
step out: 继续处理至函数末尾,而后返回至调用命令
-
restart:重新启动应用程序和调试器
-
stop:停止应用程序和调试器
与 Chrome DevTools 类似,我们可以右键单击任意行来添加:
-
标准断点
-
在指定条件下停止程序的条件断点,例如 x>3
-
计算花括号中表达式的日志点,例如 URL:{ reg.url }
关于更多信息,请参阅在 VS Code 中调试(https://code.visualstudio.com/docs/introvideos/debugging)。
VS Code 高级调试配置
如果希望在另一台设备或虚拟机上调试代码,或者需要使用其他替代启动选项(例如 nodemon),我们可能须进一步调整 VS Code 配置。
编辑器将启动配置存储在项目中隐藏的.vscode 文件夹内的 launch.json 文件。要生成此文件,请点击 Run and Debug 窗格上方的 create a launch.json file 创建文件,而后选择 Node.js 环境。
![图10: nodejs Debug关于 Node.js 调试,你需要了解的一切](https://wkee.net/techug/uploads/2023/07/everything-you-need-to-know-about-node-js-debugging74b159004e8cd4de8f9a.12.png)
可以使用 Add Configuration 按钮,将任意数量的配置设置对象添加至”configurations”: [] 数组当中。VS Code 能够:
-
Launch 启动 Node.js 进程本身,或者
-
Attach 附加至调试 Web Socket 服务器,该服务器可能运行在远程计算机或 Docker 容器中。
以上截屏所示,为 nodemon 的启动配置。其中 Add Configuration 按钮提供 nodemon 选项,我们可以编辑其中的 program 属性以指向入口脚本 (${workspaceFolder}/index.js)。
保存 launch.json,而后在 Run and Debug 窗格上方的下拉菜单中选择 nodemon,接着单击绿色的运行图标:
![图11: nodejs Debug关于 Node.js 调试,你需要了解的一切](https://wkee.net/techug/uploads/2023/07/everything-you-need-to-know-about-node-js-debugging74b159004e8cd4de8f9a.13.png)
nodemon 会启动我们的应用程序,之后即可正常编辑代码并设置断点或日志点。
关于更多信息,请参阅 VS Code 启动配置(https://code.visualstudio.com/docs/editor/debugging#_launch-configurations)。
VS Code 可以调试任何 Node.js 应用程序,而善用以下扩展能让调试过程更轻松:
-
Remote – Containers: 接入运行在 Docker 容器中的应用
-
Remote – SSH: 接入远程服务器上运行的应用
-
Remote – WSL: 接入运行在 Windows 上 Linux in WSL 中的应用
Node.js 的其他调试选项
参考 Node.js 调试指南:https://nodejs.org/en/docs/guides/debugging-getting-started/
主要为 Visual Studio、JetBrains、WebStorm、Gitpod 和 Eclipse 等 IDE 和编辑器提供调试建议。
ndb 提供更好的调试体验,同时具备强大功能,例如附加至子进程和能够限制文件访问的脚本黑盒。
IBM report-toolkit for Node.jshttps://github.com/ibm/report-toolkit
在 node 运行时使用 –experimental-report 选项,即可分析数据输出。
最后,LogRocket 和 Sentry.io 等商业服务可以与客户端和服务器上的实时 Web 应用程序相集成,帮助用户记录真实发生的错误。
总结
过去十年以来,JavaScript 和 Node.js 的调试已经变得愈发轻松。我们可以用各种实用工具定位问题,使用 console.log() 快速查找 bug。如果面对更复杂的问题,Chrome DevTools 或者 VS Code 可能是更合适的选项。熟悉掌握这些工具将帮助大家编写出更健壮的代码,同时显著缩短在 bug 修复上投入的时间和精力。