关于 Node.js 调试,你需要了解的一切

JavaScript/前端
219
0
0
2024-01-23
标签   NodeJs

作者 | Craig Buckler

译者 | 核子可乐

策划 | 丁晓昀

Node.js 是一种颇具人气的 JavaScript 运行时,与谷歌 Chrome 浏览器一样采用同款 V8 引擎。

Node.js 具备跨平台属性,目前已经成为服务器端 Web 应用程序开发、工具构建和命令行应用程序等领域的主流选项。

但体验过 Node.js 的朋友往往发现,一旦编写代码并尝试运行,往往难以轻松处理深藏其中的问题。幸运的时候,代码崩溃还能显示明确的错误信息;但如果运气不好,应用程序仍能勉强运行,只是结果与开发者预期相去甚远。

什么是调试?

所谓调试,就是修复软件缺陷的艺术。修复 bug 并不高深,大多数问题其实就是由字符错录或代码行里的小问题引发,但查找 bug 却是无缘艰难。开发人员往往得花上大量时间才能抽丝剥茧、厘清问题的根源。

以下几种方法能帮助大家有效规避错误:

  1. 使用高质量的代码编辑器,应具备行编号、彩色编码、代码校验、自动补全、括号匹配、参数提示等功能。
  2. 使用 Git 等源代码控制系统以管理代理修订工作。这些工具能帮助开发者检查更新,定位 bug 出现的方式、时间和位置。
  3. 采用 bug 跟踪系统,例如 Jira、FogBugz 以及 Bugzilla 等。它们能向开发者报告 bug、高亮显示重复项、记录重现步骤、确定 bug 严重性、计算优先级、分配开发人员、记录讨论线索并跟踪修复进度。
  4. 使用测试驱动开发(TDD)方法。TDD 是一种开发过程,鼓励开发者在编写函数之前先用编码测试该函数的运行效果。
  5. 尝试使用代码解释或结对编程等方法同其他开发者携手合作,对方提供的全新视角能帮助我们发现自己遗漏的问题。

但没有哪种解决方案能够直接消除所有错误,而且任何一种编程语言都免不了出现以下几种错误类型。

语法错误

如果代码内容未遵循某些语言规则,就会触发错误。常见的语法错误包括拼写错误或缺少括号等。

VS Code 等优秀代码编辑器能帮助大家在实际运行代码之前,预先检查各种常见的 Node.js 问题:

  • 将有效和无效语句标记为彩色形式;
  • 自动补全函数和变量名称;
  • 高亮显示匹配的括号;
  • 自动缩进代码块;
  • 为函数、属性和方法提供参数提示;
  • 检测无法访问的代码;
  • 重构混乱函数。

可以使用 ESLint 等代码检查器寻找各种语法问题,或者不符合正常编码风格的情况。使用以下命令,即可将 ESLint 安装为全局 Node.js 模块:

npm i eslint -g

而后通过命令行检查 JavaScript 文件:

eslint code.js

ESLint for VS Code 扩展程序的效果更好,能在我们输入的同时对代码内容做验证:

逻辑错误

逻辑错误意味着我们的代码可以运行,但却无法达成预期的效果。例如,用户无法使用有效凭证正常登录;报告中的统计信息不正确;用户数据未被保存至数据库等。引发逻辑错误的原因多种多样,包括:

  • 使用了不正确的变量名称;
  • 使用了不正确的条件,例如应该是 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() 之外,大家也可以考虑其他几个选项:

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 是围绕 V8 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:

几秒后,您的 Node.js 应用就会显示为 Remote Target。如果仍未找到,请选中 Discover network targets,而后单击 Configure 按钮为运行应用的设备添加 IP 地址和端口。

单击目标的 inspect 链接以启动 DevTools。对于熟悉在浏览器上调试客户端应用的朋友,整个操作流程应该非常顺畅。

要直接从 DevTools 加载、编辑和保存文件,请打开 Sources 窗格,单击 + Add folder to workspace 向工作区添加文件夹。之后选择 Node.js 文件的位置,而后单击 Agree。现在,我们可以从左侧窗格或按 Ctrl | Cmd + P 并输入文件名。

单击任何行号以设置断点(显示为蓝色标记):

这里的 breakpoint 断点,负责指定调试器应在何处暂停处理。我们可以借此检查程序状态,包括局部和全局变量。您可以定义任意数量的断点,或向代码中添加调试器语句,这些语句会在调试器开始运行时停止处理。

右侧面板显示以下内容:

  • Watch 窗格中,您可以通过单击 + 图标以输入变量名称并监视变量
  • Breakpoint 窗格中,您可以查看、启用和禁用断点
  • Scope 窗格中,您可以检查所有变量
  • Call Stack 窗格中,您可以查看达到此点前所调用的所有函数

Paused on breakpoint“在断点处暂停”上方,会出现一行图标。

从左至右,各图标分别对应以下操作:

  1. resume execution: 继续处理至下一断点
  2. step over: 执行下一条命令,但停留在当前函数内;不跳转至命令所调用的任何其他函数
  3. step into: 执行下一条命令,并跳转至命令所调用的任何其他函数
  4. step out: 继续处理至函数末尾,而后返回至调用命令
  5. step: 与 step into 类似,但不会跳转至 async 函数中
  6. deactivate all breakpoints:禁用所有断点
  7. pause on exceptions: 当发生错误时,停止处理

在 Chrome 中设置条件断点

假设我们有一个运行 1000 次迭代的循环,但真正需要关注的是最后一次迭代的状态:

for (let i = 0; i < 1000; i++) {
  // set breakpoint here?
}

这里我们当然无需对着 resume 单击 999 次,而是右键单击该行并选择 Add conditional breakpoint 添加条件断点,而后输入条件,例如 i=999:

条件断点会显示为黄色,而非蓝色。

在 Chrome 中设置日志点

日志点为 console.log(),不涉及任何代码!执行此代码时会输出一条表达式,但与断点不同的是,处理过程不会暂停。要添加日志点,先右键单击任意行,选择 Add log point 添加日志点,而后输入表达式,例如’loop counter I’,i。

使用 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 语句时停止执行:

VS Code 调试方法与 Chrome DevTools 中的 Variables、Watch、Call stack 和 Breakpoints 窗格类似。其中 Loaded Scripts 窗格会显示应用程序所加载的各脚本,也包括 Node.js 的内部脚本。

操作图标工具栏提供以下功能:

  1. resume execution: 继续处理至下一断点
  2. step over: 执行下一条命令,但停留在当前函数之内;不跳转至命令调用的任何函数
  3. step into: 执行下一条命令,并跳转至它调用的任何其他函数
  4. step out: 继续处理至函数末尾,而后返回至调用命令
  5. restart:重新启动应用程序和调试器
  6. 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 环境。

可以使用 Add Configuration 按钮,将任意数量的配置设置对象添加至”configurations”: [] 数组当中。VS Code 能够:

  1. Launch 启动 Node.js 进程本身,或者
  2. Attach 附加至调试 Web Socket 服务器,该服务器可能运行在远程计算机或 Docker 容器中。

以上截屏所示,为 nodemon 的启动配置。其中 Add Configuration 按钮提供 nodemon 选项,我们可以编辑其中的 program 属性以指向入口脚本 (${workspaceFolder}/index.js)。

保存 launch.json,而后在 Run and Debug 窗格上方的下拉菜单中选择 nodemon,接着单击绿色的运行图标:

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:https://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/