Node.js 的后期诊断和调试

在你希望判断出你的 Node.js 应用在生产环境中发生了什么错误时,后期诊断和调试就显得尤为重要了。

这里我们探讨 node-report 这个核心项目,用来帮助我们进行后期诊断和调试。

Node.js At Scale 中的全部文章:

使用 npm:

深入 Node.js

使用 Node.js 构建项目

测试 + Node

生产环境中的 Node.js

Node.js 与微服务

node-report 诊断模块

该模块的目的是生成一个用户可读的诊断摘要文件。 它旨在用于开发和生产环境

产生的报告包括:

目前,节点报告支持 AIX,Linux,MacOS,SmartOS 和 Windows 上的 Node.js v4,v6 和 v7。

将其添加到你的项目只需要安装 npm 其命令:

npm install node-report --save

//index.js
require('node-report')

将 node-report 添加到应用程序后,它将自动侦听未处理的异常和致命错误事件,并将触发报告生成。也可以通过向 Node.js 进程发送 USR2 信号来触发报告生成。

node-report 用例

异常诊断

为了简单起见,假设你的应用程序中有以下端点:

function myListener(request, response) {  
  switch (request.url) {
  case '/exception':
    throw new Error('*** exception.js: uncaught exception thrown from function myListener()');
  }
}

一旦调用了 /exception 路由处理程序,这个代码就会抛出一个异常。 为了确保我们获得诊断信息,我们必须将 node-report 模块添加到我们的应用程序中,如前文所示。

require('node-report')  
function my_listener(request, response) {  
  switch (request.url) {
  case '/exception':
    throw new Error('*** exception.js: uncaught exception thrown from function my_listener()');
  }
}

让我们看看端点被调用后会发生什么! 我们的报告刚写进一个文件:

Writing Node.js report to file: node-report.20170506.100759.20988.001.txt  
Node.js report completed

标头

打开文件后,你会看到如下结果:

=================== Node Report ===================

Event: exception, location: "OnUncaughtException"  
Filename: node-report.20170506.100759.20988.001.txt  
Dump event time:  2017/05/06 10:07:59  
Module load time: 2017/05/06 10:07:53  
Process ID: 20988  
Command line: node demo/exception.js

Node.js version: v6.10.0  
(ares: 1.10.1-DEV, http_parser: 2.7.0, icu: 58.2, modules: 48, openssl: 1.0.2k, 
 uv: 1.9.1, v8: 5.1.281.93, zlib: 1.2.8)

node-report version: 2.1.2 (built against Node.js v6.10.0, 64 bit)

OS version: Darwin 16.4.0 Darwin Kernel Version 16.4.0: Thu Dec 22 22:53:21 PST 2016; root:xnu-3789.41.3~3/RELEASE_X86_64

Machine: Gergelys-MacBook-Pro.local x86_64

你可以将此部分视为诊断摘要的标头 - 它包括..

堆栈跟踪

报告接下来的一部分包括捕获的堆栈跟踪,包括 JavaScript 和本机部分:

=================== JavaScript Stack Trace ===================
Server.myListener (/Users/gergelyke/Development/risingstack/node-report/demo/exception.js:19:5)  
emitTwo (events.js:106:13)  
Server.emit (events.js:191:7)  
HTTPParser.parserOnIncoming [as onIncoming] (_http_server.js:546:12)  
HTTPParser.parserOnHeadersComplete (_http_common.js:99:23)

在 JavaScript 部分, 你可以看到..

在本机部分,您可以在 Node.js 的本地代码中看到同样的事情 - 只是其在一个较底层的级别

=================== Native Stack Trace ===================
 0: [pc=0x103c0bd50] nodereport::OnUncaughtException(v8::Isolate*) [/Users/gergelyke/Development/risingstack/node-report/api.node]
 1: [pc=0x10057d1c2] v8::internal::Isolate::Throw(v8::internal::Object*, v8::internal::MessageLocation*) [/Users/gergelyke/.nvm/versions/node/v6.10.0/bin/node]
 2: [pc=0x100708691] v8::internal::Runtime_Throw(int, v8::internal::Object**, v8::internal::Isolate*) [/Users/gergelyke/.nvm/versions/node/v6.10.0/bin/node]
 3: [pc=0x3b67f8092a7] 
 4: [pc=0x3b67f99ab41] 
 5: [pc=0x3b67f921533]

堆和垃圾收集器参数

您可以在堆参数中查看每个堆空间在创建报告过程中是如何执行的:

这些参数包括:

为了更好地了解 Node.js 工作中是如何处理内存的,请参考以下文章:

=================== JavaScript Heap and GC ===================
Heap space name: new_space  
    Memory size: 2,097,152 bytes, committed memory: 2,097,152 bytes
    Capacity: 1,031,680 bytes, used: 530,736 bytes, available: 500,944 bytes
Heap space name: old_space  
    Memory size: 3,100,672 bytes, committed memory: 3,100,672 bytes
    Capacity: 2,494,136 bytes, used: 2,492,728 bytes, available: 1,408 bytes

Total heap memory size: 8,425,472 bytes  
Total heap committed memory: 8,425,472 bytes  
Total used heap memory: 4,283,264 bytes  
Total available heap memory: 1,489,426,608 bytes

Heap memory limit: 1,501,560,832

资源利用率

资源利用率部分包括这些指标..

=================== Resource usage ===================
Process total resource usage:  
  User mode CPU: 0.119704 secs
  Kernel mode CPU: 0.020466 secs
  Average CPU Consumption : 2.33617%
  Maximum resident set size: 21,965,570,048 bytes
  Page faults: 13 (I/O required) 5461 (no I/O required)
  Filesystem activity: 0 reads 3 writes

系统信息

系统信息部分包括..

致命错误的诊断

 一旦你的应用程序有致命的错误,node-report 模块也可以为你提供帮助,比如你应用程序运行时内存溢出。

默认情况下,你会收到如下错误信息:

<--- Last few GCs --->

   23249 ms: Mark-sweep 1380.3 (1420.7) -> 1380.3 (1435.7) MB, 695.6 / 0.0 ms [allocation failure] [scavenge might not succeed].
   24227 ms: Mark-sweep 1394.8 (1435.7) -> 1394.8 (1435.7) MB, 953.4 / 0.0 ms (+ 8.3 ms in 231 steps since start of marking, biggest step 1.2 ms) [allocation failure] [scavenge might not succeed].

就其信息本身而言,这些信息没有什么用处。你不知道运行环境,以及应用程序的状态。但随着 node-report 不断上报,这些信息就变的更有价值了。

首先, 在生成的后期诊断总结中,你将获得一个更具描述性的事件:

Event: Allocation failed - JavaScript heap out of memory, location: "MarkCompactCollector: semi-space copy, fallback in old gen"

其次,你将获得本机堆栈跟踪 - 这可以帮助您更好地了解配置失败的原因。

阻塞操作的诊断

假如你有以下循环,这个循环会阻止到你的事件循环。 这是一场噩梦般表演。

var list = []  
for (let i = 0; i < 10000000000; i++) {  
  for (let j = 0; i < 1000; i++) {
    list.push(new MyRecord())
  }
  for (let j=0; i < 1000; i++) {
    list[j].id += 1
    list[j].account += 2
  }
  for (let j = 0; i < 1000; i++) {
    list.pop()
  }
}

使用 node-report 你可以通过发送 USR2 信号在程序运行期间请求上报, 一旦你这么做,你将获得堆栈跟踪,以及你在 1 分钟内你的应用程序耗费的时间。

( 用于 node-report 资源库的示例)

node-report 的 API

以编程方式触发报告生成

报告的创建也可以通过使用 JavaScript API 来触发。这样的话,你的报告将保存在文件中,就像它被自动触发一样。

const nodeReport = require('node-report')  
nodeReport.triggerReport()

以字符串形式获取报告

使用 JavaScript API,报告也可以作为字符串检索。

const nodeReport = require('nodereport')  
const report = nodeReport.getReport()

使用非自动触发

如果你不想使用自动触发 (如致命错误或未捕获的异常) 则可以通过调用 API 本身,选择退出自动触发,同样也可以指定其文件名:

const nodeReport = require('node-report/api')
nodeReport.triggerReport('name-of-the-report')

贡献

如果你希望 Node.js 变得更好,请考虑加入 Postmortem Diagnostics 工作组,在这里你可以为该项目做出贡献。

The Postmortem 工作组致力于支持和改进 Node.js 的后期调试,它旨在提升 Node 的后期调试的作用,以协助开发技术和工具,并且为 Node.js 用户提供已知和可用的技术和工具。

在 Node.js Scale 系列的下一章中,我们将讨论 Node.js 应用程序的性能分析。如果你有任何问题,请在下面的评论中告知我们。