![关于Node.js调试,你需要了解的一切](https://static001.infoq.cn/resource/image/55/c6/55e7c6474d9df3970d35e0795d9d3cc6.jpg)
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 模块:
而后通过命令行检查 JavaScript 文件:
ESLint for VS Code 扩展程序的效果更好,能在我们输入的同时对代码内容做验证:
![](https://static001.geekbang.org/wechat/images/00/0095efc1c9b27e2600eaf21f38f07385.png)
逻辑错误
逻辑错误意味着我们的代码可以运行,但却无法达成预期的效果。例如,用户无法使用有效凭证正常登录;报告中的统计信息不正确;用户数据未被保存至数据库等。引发逻辑错误的原因多种多样,包括:
使用了不正确的变量名称;
使用了不正确的条件,例如应该是 if(x>5) 而非 if(x<5);
使用了无效的函数、参数或算法。
我们往往需要分步执行代码,并在过程当中检查特定的运行状态点。
运行时错误
运行时错误主要影响的是应用程序的执行过程。代码执行可能并不出错,但也随时可能被无效的用户输入而意外触发。例如:
尝试将某个值除以零;
访问目前已不存在的数组项或数据库记录;
在不具备适当访问权限的情况下,尝试写入文件;
不正确的异步函数实现会引发“内存溢出”崩溃。
众所周知,运行时错误往往很难重现,所以保持良好的日志记录习惯至关重要。
Node.js 调试中的环境变量
主机操作系统中的环境变量负责控制 Node.js 应用程序的具体设置。最常见的环境变量是 NODE_ENV,一般在调试时被设定为 development、在 production 过程中则被设定为 production。
大家可以在 Linux/macOS 上这样设置环境变量:
在 Windows(旧版 DOS)命令行中这样设置:
在 Windows Powershell 上则是这样设置:
应用程序可以检测环境设置,并在必要时启用调试消息,例如:
NODE_DEBUG 需要使用 Node.js util.debuglog 来启用调试消息(后文中的 Node.js util.debuglog 部分将具体介绍)。另外,请注意检查主模块和框架的说明文档,了解更多日志记录选项。
使用 Node.js 命令行选项进行调试
在启动应用程序时,您可以将命令行选项传递给 node 或 nodemon 运行时。其中最有用的选项之一当数—trace-warnings,它会在无法解析或拒绝 promise 时输出栈跟踪信息:
其他选项包括:
–enable-source-maps: 使用 TypeScript 等转译器时,启用源映射
–throw-deprecation: 在使用已被弃用的功能时,抛出错误
–inspect: 激活 V8 检查器(具体请参阅后文中的 Node.js V8 检查器部分)
使用控制台日志进行调试
最简单的应用程序调试方法,就是在执行期间将值输出至控制台:
有些开发者坚持认为 console.log() 这东西没有意义,因为代码本身一直在不断变更,而且还有更好的调试选项可用。话虽没错,但大家还是会经常用到 console.log(),而且任何能提高编程效率的工具都有价值。控制台日志就是这样一种快速且实用的选项,能帮助大家切实找到并修复 bug。
当然,除了标准的 console.log() 之外,大家也可以考虑其他几个选项:
![](https://static001.geekbang.org/wechat/images/40/401d7814a6974ae5d50bd6d5a4728790.png)
console.log() 支持以逗号分隔各值的列表,例如:
ES6 的解构赋值能以更简洁的方式提供类似输出:
util.inspect 能够格式化对象以方便阅读,而 console.dir() 能会帮助大家完成其他费时费力的工作:
Node.js util.debuglog
Node.js 的标准 util 模块提供 debuglog 方法,能够按特定条件将日志消息写入 STDERR:
当大家将 NODE_DEBUG 环境变量设置为 myapp 或通配符形式(例如或*my*)时,控制台会显示以下调试消息:
其中 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 标志混淆):
调试器会在第一行暂停,并显示以下 debug 提示:
输入 help 可查看命令列表。大家可以使用以下步骤逐步跑通应用程序:
cont 或 c: 继续执行
next 或 n: 运行下一条命令
step 或 s: 单步执行被调用函数
out 或 o: 跳出被调用函数并返回其调用者
pause: 暂停运行代码
还可以:
使用 watch(‘x’) 查看变量值;
使用 setBreakpoint()/sb() 命令设置断点(也可以在代码中插入 debugger; 语句);
restart 重启脚本;
.exit 退出调试器(请注意开头的. 句点)。
整个操作过程似乎不太方便,确实如此。所以除非实在没有其他方法,否则尽量不要使用内置的调试客户端。
使用 Chrome 调试 Node.js 应用
使用—inspect 标志启动 Node.js V8 检查器:
(nodemon 也支持此标志。)
此命令会在 127.0.0.1:9229 端口上启动侦听调试器:
如果大家在其他设备或 Docker 容器上运行 Node.js 应用,请确保端口 9229 可以访问,具体使用以下命令授予远程访问权限:
与—inspect 不同,我们可以使用—inspect-brk 停止对首条语句的处理,以便逐步分步执行。
打开 Chrome 网络浏览器(或者其他基于 Chromium 内核的浏览器),并在地址栏中输入 chrome://inspect:
![](https://static001.geekbang.org/wechat/images/4c/4c64b1b9d74d4d434c6d3cfa7247b6f3.png)
几秒后,您的 Node.js 应用就会显示为 Remote Target。如果仍未找到,请选中 Discover network targets,而后单击 Configure 按钮为运行应用的设备添加 IP 地址和端口。
单击目标的 inspect 链接以启动 DevTools。对于熟悉在浏览器上调试客户端应用的朋友,整个操作流程应该非常顺畅。
![](https://static001.geekbang.org/wechat/images/38/389f0356b4b687d819979ff26b0d1eed.png)
要直接从 DevTools 加载、编辑和保存文件,请打开 Sources 窗格,单击 + Add folder to workspace 向工作区添加文件夹。之后选择 Node.js 文件的位置,而后单击 Agree。现在,我们可以从左侧窗格或按 Ctrl | Cmd + P 并输入文件名。
单击任何行号以设置断点(显示为蓝色标记):
![](https://static001.geekbang.org/wechat/images/67/67ba25127bf278cb63cac2fae1db4b0f.png)
这里的 breakpoint 断点,负责指定调试器应在何处暂停处理。我们可以借此检查程序状态,包括局部和全局变量。您可以定义任意数量的断点,或向代码中添加调试器语句,这些语句会在调试器开始运行时停止处理。
![](https://static001.geekbang.org/wechat/images/f9/f9520ae5c75a7e803ce6cdc5793ba0e9.png)
右侧面板显示以下内容:
Watch 窗格中,您可以通过单击 + 图标以输入变量名称并监视变量
Breakpoint 窗格中,您可以查看、启用和禁用断点
Scope 窗格中,您可以检查所有变量
Call Stack 窗格中,您可以查看达到此点前所调用的所有函数
Paused on breakpoint“在断点处暂停”上方,会出现一行图标。
![](https://static001.geekbang.org/wechat/images/37/376c49567e1ebb2f2031e573d94b6d3d.png)
从左至右,各图标分别对应以下操作:
resume execution: 继续处理至下一断点
step over: 执行下一条命令,但停留在当前函数内;不跳转至命令所调用的任何其他函数
step into: 执行下一条命令,并跳转至命令所调用的任何其他函数
step out: 继续处理至函数末尾,而后返回至调用命令
step: 与 step into 类似,但不会跳转至 async 函数中
deactivate all breakpoints:禁用所有断点
pause on exceptions: 当发生错误时,停止处理
在 Chrome 中设置条件断点
假设我们有一个运行 1000 次迭代的循环,但真正需要关注的是最后一次迭代的状态:
这里我们当然无需对着 resume 单击 999 次,而是右键单击该行并选择 Add conditional breakpoint 添加条件断点,而后输入条件,例如 i=999:
![](https://static001.geekbang.org/wechat/images/86/86fb2baefe219f8f3f438900a70ce001.png)
条件断点会显示为黄色,而非蓝色。
在 Chrome 中设置日志点
日志点为 console.log(),不涉及任何代码!执行此代码时会输出一条表达式,但与断点不同的是,处理过程不会暂停。要添加日志点,先右键单击任意行,选择 Add log point 添加日志点,而后输入表达式,例如’loop counter I’,i。
![](https://static001.geekbang.org/wechat/images/f2/f290e5f59c166d155720dc47be44f8c7.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 语句时停止执行:
![](https://static001.geekbang.org/wechat/images/69/69d950baf8ded2ca48605284d84e58bd.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 环境。
![](https://static001.geekbang.org/wechat/images/24/24492a8059ae21bda8e84500565a70df.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,接着单击绿色的运行图标:
![](https://static001.geekbang.org/wechat/images/78/78bf4c4eb6bc1a560c282dc67ba2390d.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 修复上投入的时间和精力。
原文链接:
https://blog.openreplay.com/an-introduction-to-debugging-in-nodejs/
相关阅读:
Node 之父着急宣布:Deno 将迎来重大变革,更好地兼容
评论