软件开发很复杂,有时您的 Node.js 应用程序会失败。如果你幸运的话,你的代码将会崩溃并显示一个明显的错误信息。如果您不走运,您的应用程序将继续执行,但不会产生您期望的结果。如果你真的不走运,一切都会正常工作,直到第一个用户发现灾难性的磁盘擦除错误。
什么是调试?
调试是修复软件缺陷的黑魔法。修复错误通常很容易——更正的字符或额外的代码行就可以解决问题。找到该错误是另一回事,开发人员可能会花费许多不愉快的时间来试图找到问题的根源。幸运的是,Node.js 有一些很棒的工具可以帮助跟踪错误。
术语
调试有自己选择的晦涩术语,包括以下内容:
学期 |
解释 |
breakpoint |
调试器停止程序以便检查其状态的点 |
debugger |
一种提供调试工具的工具,例如逐行运行代码以检查内部变量状态 |
feature |
如声明中所述:“这不是错误,而是功能”。所有开发人员在他们职业生涯的某个时候都会这么说 |
frequency |
错误发生的频率或条件 |
it doesn’t work |
最常出现但最没用的错误报告 |
log point |
给调试器的指令以在执行期间的某个点显示变量的值 |
logging |
将运行时信息输出到控制台或文件 |
logic error |
该程序有效,但未按预期运行 |
priority |
在计划更新列表中分配错误的位置 |
race condition |
难以追踪的错误取决于不可控事件的顺序或时间 |
refactoring |
重写代码以提高可读性和维护性 |
regression |
可能由于其他更新而重新出现以前修复的错误 |
related |
与另一个相似或相关的错误 |
reproduce |
导致错误所需的步骤 |
RTFM error |
伪装成错误报告的用户无能,通常随后是对“阅读翻转手册”的响应 |
step into |
在调试器中逐行运行代码时,进入被调用的函数 |
step out |
逐行运行时,完成当前函数的执行并返回调用代码 |
step over |
逐行运行时,在不进入它调用的函数的情况下完成命令的执行 |
severity |
错误对系统的影响。例如,数据丢失通常被认为比 UI 问题更成问题,除非发生频率非常低 |
stack trace |
错误发生前调用的所有函数的历史列表 |
syntax error |
印刷错误,例如console.log() |
user error |
由用户而非应用程序引起的错误,但仍可能会根据该人的资历进行更新 |
watch |
在调试器执行期间要检查的变量 |
watchpoint |
类似于断点,但程序在变量设置为特定值时停止 |
如何避免错误
在测试应用程序之前,通常可以防止错误……
使用好的代码编辑器
一个好的代码编辑器将提供许多功能,包括行号、自动完成、颜色编码、括号匹配、格式化、自动缩进、变量重命名、片段重用、对象检查、函数导航、参数提示、重构、无法访问的代码检测,建议、类型检查等。
Node.js 开发者被VS Code、Atom和Brackets等免费编辑器以及大量商业替代品所宠坏了。
使用代码 Linter
在保存和测试代码之前,linter 可以报告代码错误,例如语法错误、缩进不良、未声明的变量和括号不匹配。JavaScript 和 Node.js 的流行选项包括ESLint、JSLint和JSHint。
这些通常作为全局 Node.js 模块安装,因此您可以从命令行运行检查:
eslint myfile.js
但是,大多数 linter 都有代码编辑器插件,例如用于 VS Code的 ESLint和用于 Atom 的 linter-eslint,它们会在您键入时检查您的代码:

使用源代码管理
Git等源代码控制系统可以帮助保护您的代码并管理修订。更容易发现在何时何地引入了错误以及谁应该受到指责!GitHub和Bitbucket等在线存储库提供免费空间和管理工具。
采用问题跟踪系统
如果没有人知道,是否存在错误?问题跟踪系统用于报告错误、查找重复项、记录复制步骤、确定严重性、计算优先级、分配开发人员、记录讨论以及跟踪任何修复的进度。
在线源存储库通常提供基本的问题跟踪,但专用解决方案可能适用于较大的团队和项目。
使用测试驱动开发
测试驱动开发(TDD) 是一个开发过程,它鼓励开发人员编写代码,在编写函数之前测试函数的操作——例如,当函数 Y 传递输入 Z 时是否返回X。
可以在开发代码时运行测试以证明功能有效,并在进行进一步更改时发现任何问题。也就是说,您的测试也可能有错误……
走开
很容易熬夜以徒劳地试图找到一个讨厌的错误的来源。不。走开,做点别的。你的大脑会下意识地处理这个问题,并在凌晨 4 点用解决方案叫醒你。即使这没有发生,新鲜的眼睛也会发现那个明显丢失的分号。
Node.js 调试:环境变量
在主机操作系统中设置的环境变量可用于控制 Node.js 应用程序设置。最常见的是NODE_ENV,通常development在调试时设置为 。
Linux/macOS 上可以设置环境变量:
NODE_ENV=development
窗户cmd:
set NODE_ENV=development
或 Windows Powershell:
$env:NODE_ENV="development"
在内部,应用程序将启用进一步的调试功能和消息。例如:
// is NODE_ENV set to "development"?
const DEVMODE = (process.env.NODE_ENV === 'development');
if (DEVMODE) {
console.log('application started in development mode on port ${PORT}');
}
NODE_DEBUG启用使用 Node.js 调试消息util.debuglog(见下文),但也可以查阅主要模块和框架的文档以发现更多选项。
请注意,环境变量也可以保存到.env文件中。例如:
NODE_ENV=development
NODE_LOG=./log/debug.log
SERVER_PORT=3000
DB_HOST=localhost
DB_NAME=mydatabase
dotenv然后使用模块加载:
require('dotenv').config();
Node.js 调试:命令行选项
启动应用程序时,可以将各种命令行选项传递给运行时。node其中最有用的是–trace-warnings,它输出进程警告(包括弃用)的堆栈跟踪。
可以设置任意数量的选项,包括:
- –enable-source-maps:启用源地图(实验性)
- –throw-deprecation:使用不推荐使用的功能时抛出错误
- –inspect:激活 V8 检查器(见下文)
举个例子,让我们尝试记录加密模块的DEFAULT_ENCODING属性,它在 Node v10 中已被弃用:
const crypto = require('crypto');
Function bar() {
console.log(crypto.DEFAULT_ENCODING);
}
function foo(){
bar();
}
foo();
现在使用以下命令运行它:
node index.js
然后我们会看到:
buffer
(node:7405) [DEP0091] DeprecationWarning: crypto.DEFAULT_ENCODING is deprecated.
但是,我们也可以这样做:
node --trace-warnings index.js
这会产生以下结果:
buffer
(node:7502) [DEP0091] DeprecationWarning: crypto.DEFAULT_ENCODING is deprecated.
at bar (/home/Desktop/index.js:4:22)
at foo (/home/Desktop/index.js:8:3)
at Object.<anonymous> (/home/Desktop/index.js:11:1)
at Module._compile (internal/modules/cjs/loader.js:1151:30)
at Object.Module._extensions..js (internal/modules/cjs/loader.js:1171:10)
at Module.load (internal/modules/cjs/loader.js:1000:32)
at Function.Module._load (internal/modules/cjs/loader.js:899:14)
at Function.executeUserEntryPoint [as runMain] (internal/modules/run_main.js:71:12)
at internal/main/run_main_module.js:17:47
这告诉我们弃用警告来自第 4 行(console.log语句)中的代码,该代码在bar函数运行时执行。该函数在第 8 行bar被函数调用,该函数在我们脚本的第 11 行被调用。foofoo
请注意,同样的选项也可以传递给nodemon。
控制台调试
调试应用程序的最简单方法之一是在执行期间将值输出到控制台:
console.log( myVariable );
很少有开发人员会钻研这个不起眼的调试命令,但他们错过了更多的可能性,包括:
console方法 |
描述 |
.log(msg) |
向控制台输出消息 |
.dir(obj,opt) |
用于util.inspect漂亮地打印对象和属性 |
.table(obj) |
以表格格式输出对象数组 |
.error(msg) |
输出错误信息 |
.count(label) |
一个命名计数器,报告该行已执行的次数 |
.countReset[label] |
重置命名计数器 |
.group(label) |
缩进一组日志消息 |
.groupEnd(label) |
结束缩进组 |
.time(label) |
启动计时器以计算操作的持续时间 |
.timeLog([label] |
报告自计时器启动以来经过的时间 |
.timeEnd(label) |
停止计时器并报告总持续时间 |
.trace() |
输出堆栈跟踪(所有调用函数的列表) |
.clear() |
清除控制台 |
console.log()接受逗号分隔值的列表。例如:
let x = 123;
console.log('x:', x);
// x: 123
但是,ES6 解构可以提供类似的输出,但输入工作更少:
console.log({x});
// { x: 123 }
使用以下命令可以将较大的对象作为压缩字符串输出:
console.log( JSON.stringify(obj) );
util.inspect将格式化对象以便于阅读,但console.dir()会为您完成艰苦的工作。
节点.jsutil.Debuglog
Node.jsutil模块提供了一个内置debuglog方法,可以有条件地将消息写入STDERR:
const util = require('util');
const debuglog = util.debuglog('myapp');
debuglog('myapp debug message [%d]', 123);
当NODE_DEBUG环境变量设置为myapp(或通配符,如*or my*)时,控制台中会显示消息:
NODE_DEBUG=myapp node index.js
MYAPP 9876: myapp debug message [123]
这里9876是 Node.js 进程 ID。
默认情况下,util.debuglog是静音的。如果您在不设置变量的情况下运行上述脚本NODE_DEBUG,则不会向控制台输出任何内容。这使您可以在代码中留下有用的调试日志,而不会弄乱控制台以供常规使用。
使用日志模块进行调试
如果您需要更复杂的消息级别、详细程度、排序、文件输出、分析等选项,则可以使用第三方日志记录模块。热门选项包括:
- 舱
- 日志级别
- 摩根(Express.js 中间件)
- 皮诺
- 信号
- 故事板
- 示踪剂
- 温斯顿
Node.js V8 检查器
在以下部分中,将使用其他教程中开发的pagehit 项目来说明调试概念。您可以通过以下方式下载它:
git clone https://github.com/sitepoint-editors/pagehit-ram
或者您可以使用任何您自己的代码。
Node.js 是 V8 JavaScript 引擎的包装器,其中包括自己的检查器和调试客户端。首先,使用inspect参数(不要与 混淆–inspect)来启动应用程序:
node inspect ./index.js
调试器将在第一行暂停并显示debug>提示:
< Debugger listening on ws://127.0.0.1:9229/6f38abc1-8568-4035-a5d2-dee6cbbf7e44
< For help, see: https://nodejs.org/en/docs/inspector
< Debugger attached.
Break on start in index.js:7
5 const
6 // default HTTP port
> 7 port = 3000,
8
9 // Node.js modules
debug>
您可以通过输入以下内容逐步完成应用程序:
- cont或c:继续执行
- next或n:运行下一个命令
- stepor s: 进入一个被调用的函数
- out或o:跳出函数并返回调用命令
- pause: 暂停运行代码
其他选项包括:
- 观察变量值watch('myvar')
- setBreakpoint()使用/sb()命令设置断点(debugger;在代码中插入语句通常更容易)
- restart一个脚本
- .exit调试器(.需要初始值)
如果这听起来非常笨拙,那就是. 仅在绝对没有其他选项时使用内置调试客户端,您感觉特别受虐,并且您没有使用 Windows(这通常是有问题的)。
使用 Chrome 进行 Node.js 调试
Node.js 检查器(没有调试器客户端)以以下–inspect标志启动:
node --inspect ./index.js
注意:必要时nodemon可以代替。node
这将启动调试器侦听127.0.0.1:9229,任何本地调试客户端都可以附加到:
Debugger listening on ws://127.0.0.1:9229/20ac75ae-90c5-4db6-af6b-d9d74592572f
如果您在其他设备或 Docker 容器上运行 Node.js 应用程序,请确保端口9229可访问并使用以下命令授予远程访问权限:
node --inspect=0.0.0.0:9229 ./index.js
或者,您可以使用–inspect-brk在第一条语句上设置断点,以便立即暂停应用程序。
打开 Chrome 并chrome://inspect在地址栏中输入。

注意:如果 Node.js 应用程序未显示为Remote Target,请确保选中Discover network targets ,然后单击配置以添加运行应用程序的设备的 IP 地址和端口。
单击目标的检查链接以启动 DevTools。任何有浏览器调试经验的人都会立即熟悉它。

+ 添加文件夹到工作区链接允许您选择 Node.js 文件在系统上的位置,因此加载其他模块和进行更改变得更加容易。
单击任何行号会设置一个断点,用绿色标记表示,当到达该代码时停止执行:

通过单击+图标并输入变量名称,可以将变量添加到右侧的Watch窗格。只要暂停执行,就会显示它们的值。
调用堆栈窗格显示调用了哪些函数来达到这一点。
Scope窗格显示所有可用的局部和全局变量的状态。
Breakpoints窗格显示所有断点的列表,并允许启用或禁用它们。
Debugger paused消息上方的图标可用于恢复执行、单步执行、单步执行、单步执行、单步执行、停用所有断点以及暂停异常。
使用 VS 代码进行 Node.js 调试
当您在本地系统上运行 Node.js 应用程序时,无需任何配置即可启动 VS Code Node.js 调试。打开起始文件(通常是index.js),激活运行和调试窗格,然后单击运行和调试 Node.js (F5)按钮。

调试屏幕类似于带有变量、监视、调用堆栈、加载脚本和断点列表的 Chrome DevTools。

可以通过单击行号旁边的装订线来设置断点。您也可以右键单击。

通过此右键单击,您可以设置以下内容:
- 标准断点。
- 满足条件时停止的条件断点,例如count > 3.
- 一个日志点,实际上console.log()没有代码!任何字符串都可以用花括号表示的表达式输入——例如,{count}显示count变量的值。
注意:不要忘记点击ReturnVS Code 来创建条件断点或日志点。
顶部的调试图标栏可用于恢复执行、单步执行、单步执行、单步执行、重新启动或停止应用程序和调试。菜单中的Debug项也提供相同的选项。
有关详细信息,请参阅Visual Studio Code 中的调试。
高级调试配置
当您调试远程服务或需要使用不同的启动选项时,需要进一步配置。VS Code 将启动配置存储在项目文件夹内launch.json生成的文件中。要生成或编辑文件,请单击“运行和调试”窗格.vscode右上角的齿轮图标。

可以将任意数量的配置设置添加到configurations阵列中。单击添加配置按钮以选择一个选项。VS Code 可以:
- 使用 Node.js 本身启动一个进程,或者
- 附加到 Node.js 检查器进程,可能在远程机器或 Docker 容器上运行
在上面的示例中,定义了单个 Nodemon 启动配置。Save launch.json,从Run and Debugnodemon窗格顶部的下拉列表中选择,然后单击绿色的开始图标。

有关详细信息,请参阅VS Code 启动配置。
其他 Node.js 调试工具
Node.js 调试指南为其他 IDE 和编辑器提供建议,包括 Visual Studio、JetBrains、WebStorm、Gitpod 和 Eclipse。Atom 还有一个节点调试扩展。
ndb通过附加到子进程和脚本黑盒等强大功能提供改进的调试体验,因此仅显示特定文件夹中的代码。
用于 Node.js的IBM 报告工具包通过在使用该选项node运行时分析数据输出来工作。–experimental-report
最后,LogRocket和Sentry.io等商业服务与您在客户端和服务器中的实时 Web 应用程序集成,以记录用户遇到的错误。
获取调试!
Node.js 有一系列出色的调试工具和代码分析器,可以提高应用程序的速度和可靠性。他们能不能诱惑你离开console.log()是另一回事!