Node 中异常收集与监控

Stella981
• 阅读 541

在一个后端服务设计中,异常捕获是必不可少需要考虑的因素

而当异常发生时,能够第一时间捕捉到并且能够获得足够的信息定位到问题至关重要 这也是本篇文章的内容

刚开始,先抛出两个问题

1.在生产环境中后端连接的数据库挂了,是否能够第一时间收到通知并定位到问题,而不是等到用户反馈之后又用了半天时间才找到问题 (虽然运维肯定会在第一时间知道数据库挂了)2.在生产环境中有一条 API 出了问题,能否衡量该错误的紧急重要程度,并根据报告解决问题

异常收集

异常一般发生在以下几个位置

1.API/GraphQL 层,在 API 层的最外层使用一个中间件对错误集中进行处理,并进行上报。在具体逻辑层往往不需要主动捕捉异常,除非针对异常有特殊处理,如数据库事务失败后的回退

 // 在中间件中集中处理异常

22.script 等非 API 层,如拉取配置,数据库迁移脚本以及计划任务等

另外除了主动捕捉到的异常,还有一些可能漏掉的异常,可能导致程序退出

process.on('uncaughtException', (err) => {

API 异常结构化

当 API 层发生异常时,传输数据至客户端时需要对异常进行结构化,方便下一步的异常上报与前端对结构化信息的解析以及对应的展示

以下使用 FormatError 表示当发生异常时 API 应该返回的结构化的信息

而当异常发生时,异常可以在最顶层中间件作为错误处理中间件统一捕获,捕获到时可以使用一个函数 formatError 在中间件中统一结构化异常信息

type FormatError {

以下是 FormatError 各字段的示意

code

表示错误标识码,用以对错误进行归类,如用户输入数据不合法 (ValidationError),数据库异常 (DatabaseError),外部服务请求失败 (RequestError)

根据经验我把 code 分为以下几类

  • ValidationError,用户输入不合法

  • DatabaseError,数据库问题

  • DatabaseUniqueError

  • DatabaseConnectionError

  • RequestError,外部服务

  • RequestTimeoutError

  • ForbiddenError

  • AuthError,未授权请求授权资源

  • AppError,业务问题

  • AppBadRequest

对于数据校验,数据库异常与请求失败,我们通常会使用第三方库。 此时可以根据第三方库的 Error 来定制 code

message

表示 human-readable 的错误信息,但不一定代表它可以展示在前端。这里的 human 代表的是开发人员,而非用户,如以下两个 message 就不适宜展示在前端

1.connect ECONNREFUSED postgres.xiange.tech. 不需要把数据库断连的消息展示在前端2.email is required. 输入数据校验,虽然可以展示给用户,但是需要展示中文 (国际化)

你可以根据 code,来决定前端是否可以展示后端期待它展示的信息,而在前端也可以根据 code 来进行全局集中处理

info

表示一些针对 code 的更为详细的信息

•当发送请求失败的时候,你至少得知道失败的这个请求长什么样子: method,params/body 以及 headers•当用户输入数据校验失败时,至少得知道是那几个字段

stack

表示当前错误的堆栈,当异常发生时可以快速定位问题发生的位置 (虽然 node 有时候抛出的堆栈信息都是自己从未见过的文件)

当在开发和测试环境时,把 stack 附到 API 中可以快速定位问题, 当在生产环境时,不要把你的堆栈也放到 API 里,你可以在监控系统中找到你的堆栈位置

你可以使用以下两个 API 来优化你的 stacktrace

Error.captureStackTrace(error, constructorOpt)

具体使用方法可以参考 v8 stack trace api[1]

data

代表该接口返回的数据。当 API 报错时 data 是不是应该返回为 null

不应该,当 API 报错时,可能只有部分字段有问题,剩余字段可以正常返回。 由于 graphql 是由字段(field)聚合而成,这在 graphql 中体现地非常明显。

http status

当API处理过程中发生错误时,应该返回 400+ 的 status code

•HTTP/1.1 400 Bad Request•HTTP/1.1 401 Unauthorized•HTTP/1.1 403 Forbidden•HTTP/1.1 429 Too Many Requests

监控系统

监控首先需要有一个监控系统,我这里比较推荐 Sentry,具体如何部署可以参考我的上一篇文章:如何部署 Sentry[2]。

你也可以直接在官方注册使用 SaaS 版本: Sentry 付费[3]。个人免费版每个月有 5K 的报错限额,也足够个人使用。

相比自建版本,使用 SaaS 免了一些运维的日常工作。最主要的是, 自建系统有功能限制

这里有关于 Sentry 的文档

指标

异常监控除了异常本身以外,还要采集更多一些的指标

.  异常级别: Fatel, Error 以及 Warn。这决定你周日收到报警邮件或报警短信是继续浪还是打开笔记本改 Bug。可以通过 code 来标记

 const codeLevelMap = {

22.环境: 生产环境还是测试环境,早于用户及测试发现问题,可以直接读取应用服务的环境变量

3.上下文: 如哪一条 API 请求,哪一个用户,以及更详细的 http 报文信息。可以直接利用 Sentry 的API上报上下文信息

 Sentry.configureScope(scope => {

4.用户: API 错误是由那个用户触发的 5.code: 便于对错误进行分类 6.request_id: 便于 tracing,也方便获取更多的调试信息:在 elk 中查找当前 API 执行的 SQL 语句

 const requestId = ctx.header['x-request-id'] || Math.random().toString(36).substr(2, 9)

由上可见,对于采集指标的数据一般来源于两个方面,http 报文以及环境变量

Filter

在本地开发时,往往不需要把异常上报到 SentrySentry 也提供了 hook 再上报之前对异常进行过滤

beforeSend?(event: Event, hint?: EventHint): Promise<Event | null> | Event | null;

References

[1] v8 stack trace api: https://v8.dev/docs/stack-trace-api
[2] 如何部署 Sentry: 
[3] Sentry 付费: https://sentry.io/pricing/

本文分享自微信公众号 - 全栈成长之路(shanyue-road)。
如有侵权,请联系 support@oschina.cn 删除。
本文参与“OSC源创计划”,欢迎正在阅读的你也加入,一起分享。

点赞
收藏
评论区
推荐文章
blmius blmius
2年前
MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1
文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s
Wesley13 Wesley13
2年前
java将前端的json数组字符串转换为列表
记录下在前端通过ajax提交了一个json数组的字符串,在后端如何转换为列表。前端数据转化与请求varcontracts{id:'1',name:'yanggb合同1'},{id:'2',name:'yanggb合同2'},{id:'3',name:'yang
Jacquelyn38 Jacquelyn38
3年前
2020年前端实用代码段,为你的工作保驾护航
有空的时候,自己总结了几个代码段,在开发中也经常使用,谢谢。1、使用解构获取json数据let jsonData  id: 1,status: "OK",data: 'a', 'b';let  id, status, data: number   jsonData;console.log(id, status, number )
皕杰报表之UUID
​在我们用皕杰报表工具设计填报报表时,如何在新增行里自动增加id呢?能新增整数排序id吗?目前可以在新增行里自动增加id,但只能用uuid函数增加UUID编码,不能新增整数排序id。uuid函数说明:获取一个UUID,可以在填报表中用来创建数据ID语法:uuid()或uuid(sep)参数说明:sep布尔值,生成的uuid中是否包含分隔符'',缺省为
Easter79 Easter79
2年前
tcp_tw_recycle参数引发的数据库连接异常
【问题描述】开发反馈有个应用在后端数据库某次计划性重启后经常会出现数据库连接异常问题,通过监控系统的埋点数据,发现应用连接数据库异常有两类表现:  其一:连接超时  <spanstyle"backgroundcolor:FFFF00"131148.00msTomcatConnectionPool</span  其二:连接耗时过
Easter79 Easter79
2年前
Twitter的分布式自增ID算法snowflake (Java版)
概述分布式系统中,有一些需要使用全局唯一ID的场景,这种时候为了防止ID冲突可以使用36位的UUID,但是UUID有一些缺点,首先他相对比较长,另外UUID一般是无序的。有些时候我们希望能使用一种简单一些的ID,并且希望ID能够按照时间有序生成。而twitter的snowflake解决了这种需求,最初Twitter把存储系统从MySQL迁移
Stella981 Stella981
2年前
Django中Admin中的一些参数配置
设置在列表中显示的字段,id为django模型默认的主键list_display('id','name','sex','profession','email','qq','phone','status','create_time')设置在列表可编辑字段list_editable
Wesley13 Wesley13
2年前
MySQL部分从库上面因为大量的临时表tmp_table造成慢查询
背景描述Time:20190124T00:08:14.70572408:00User@Host:@Id:Schema:sentrymetaLast_errno:0Killed:0Query_time:0.315758Lock_
京东云开发者 京东云开发者
8个月前
Java服务总在半夜挂,背后的真相竟然是... | 京东云技术团队
最近有用户反馈测试环境Java服务总在凌晨00:00左右挂掉,用户反馈Java服务没有定时任务,也没有流量突增的情况,Jvm配置也合理,莫名其妙就挂了
Python进阶者 Python进阶者
5个月前
Excel中这日期老是出来00:00:00,怎么用Pandas把这个去除
大家好,我是皮皮。一、前言前几天在Python白银交流群【上海新年人】问了一个Pandas数据筛选的问题。问题如下:这日期老是出来00:00:00,怎么把这个去除。二、实现过程后来【论草莓如何成为冻干莓】给了一个思路和代码如下:pd.toexcel之前把这