【得物技术】前端项目使用Sentry错误监控实践

10,852 阅读13分钟

前言

鉴于前端正在向多元化方向发展,场景愈加复杂,生产环境也经常会出现一些本地环境和测试环境无法复现的问题。而且,用户的具体行为和操作我们也不可预期,后端错误监控又只能监测到发起请求后的错误日志,那么错误发生时我们就只能靠用户反馈。但是这种反馈不是实时的,往往也只有截图或者文字描述,无法提供我们需要的其他关键信息,加上生产环境的静态资源文件一般都是经过混淆压缩,我们拿到的报错堆栈信息也没有太大含义。

因此,前端项目更加需要一套成熟的错误监控系统,能够帮助我们实时的监控项目运行状态,提供丰富的详细的错误信息,甚至能够提供场景还原能力。让开发人员能够及时对报错进行跟进解决。

本文着重讲解一下前端项目接入Sentry的步骤和规则设置以及其他Sentry后台使用。

为何选择Sentry

  1. 市场已有的成熟的监控平台
  1. Sentry的优势
  • 开源,有免费版
  • 可以部署自己的服务器,安全
  • 错误信息及告警机制完善
  • 简单易上手,开发成本低
  • 错误追踪及状态流转及时,方便
  • 丰富的SDK
  • 社区活跃

综合对比,Sentry的这些优势既能及时抓取生产环境的前端报错发出通知,又能够提供丰富的错误信息及路径方便开发人员定位解决,满足了前端项目错误监控的基本需求;又是开源免费的,可以搭建自己的服务器,不用担心数据及敏感信息泄露等风险,同时支持各种开发语言及框架。所以,最终选择了Sentry做为得物前端平台的错误监控方案。

简介

Sentry is a service that helps you monitor and fix crashes in realtime. The server is in Python, but it contains a full API for sending events from any language, in any application.

Sentry是一项可帮助您实时监控和修复错误的服务。该服务器使用Python,但是它包含用于在任何应用程序中从任何语言发送事件的完整API。

概念

  • Event:事件。

每次产生的日志记录,每个event有很多元信息,包括事件级别,项目信息,环境等。可通过点击具体事件对应的“JSON”数据进行查看

  • Issue:问题。

相同的地方产生的一个异常称为一个Issue(是同一类问题的聚合)。假如在同一个位置发生了两次报错,那么会产生两个Event事件,但是只有一个Issue。

  • DSN:客户端(具体项目)密钥。

DSN是一个url,包含相关密钥信息,客户端与服务端(sentry服务器)就是通过这个DSN进行通信,上报错误信息的。

  • Auth Token:授权令牌。

授权令牌允许基于你的账户使用Sentry API,我们主要用到使用@sentry/cli进行上传sourceMap文件等操作时,sentry/cli会基于Auth Token进行调用相应API方法。

  • Org:组织名称。

对应公司部署的sentry服务器上的组织名称。

  • Release:版本号。

  • Project:客户端名称。(接入sentry的具体项目名)

  • Tag:标签。

部署

虽然Sentry官方提供了免费版本,但是从安全角度和功能限制上我们还是建议自己搭建一个Sentry服务。官方提供了两种安装方式:通过Python安装或通过Docker安装。官方推荐使用Docker进行部署,由于官方文档介绍非常详细,这里不进行阐述,需要注意的是根据实际需求修改相应配置文件。

  • 安装Docker-Compose
  • 拉取Sentry仓库
  • 修改对应配置文件
  • 域名映射
  • 创建管理员密码和用户

接入

1. 创建项目

  • Sentry服务器上创建一个新的项目:ProjectName(项目名),选择Team。
  • 选择对应开发语言模板,这里选择Vue。

截屏2021-04-23 下午6.19.27.png

2. 工程接入Sentry

  • 创建项目后,会弹出配置详细步骤文档,按照文档配置即可。

截屏2021-04-23 下午6.20.02.png

  • 安装依赖
$ yarn add @sentry/browser -D
$ yarn add @sentry/integrations -D
  • 入口文件处生成Sentry实例
import Vue from 'vue'
import * as Sentry from '@sentry/browser'
import * as Integrations from '@sentry/integrations'
Sentry.init({
    dsn: 'https://xxx.sentry域名/项目id',
    integrations: [new Integrations.Vue({Vue, attachProps: true})],
})

P.S. 这里的dsn为刚刚创建项目对应的客户端密钥,在项目->设置->客户端密钥(DSN)出查看。

截屏2021-04-23 下午6.21.33.png

3. 上传SourceMap

上述步骤完成以后,手动添加一个错误,启动项目就可以在Sentry服务器对应项目下看到对应的错误了。但是这里我们只能看到错误信息的描述,无法看到具体是哪个文件的哪一行报错,以及具体的错误信息。因为生产环境我们的资源文件都是经过混淆压缩的,接下来我们开始上传sourceMap到服务器,方便还原错误信息。

截屏2021-04-23 下午6.22.08.png

Sentry官方提供了两种上传sourceMap的方式:使用Sentry-cli命令行和webpack插件。由于命令行方式太过繁琐,且与前端工程化概念脱离。因此采用webpack插件方式进行上传sourceMap文件。

  • 安装webpack插件
yarn add @sentry/webpack-plugin -D
  • 项目根目录下添加Sentry配置文件:.sentryclirc @sentry/cli上传sourcemap文件时会自动检查该配置文件下的信息,并使用
[defaults]
url=https://xxxxxx       //  - sentry服务器地址
org=xxx                  //  - 组织名称
project=projectname      //  - 项目名称
[auth]
token=xxx                //  - auth token

截屏2021-04-23 下午6.23.32.png

P.S. 注意,这里申请的Auth Token要选择project:read和project:releases权限。

截屏2021-04-23 下午6.24.17.png

  • webpack配置文件添加插件
const SentryCliPlugin = require('@sentry/webpack-plugin')
const version = '' // 版本号
config.devtool('source-map')
config.plugin('SentryCliPlugin').use(SentryCliPlugin, [{
  release: version,
  // 打包后的代码目录 根据项目实际调整
  include: './dist',
  // url路径访问到的js资源前缀 根据项目实际调整 默认不用动
  // urlPrefix: "~/static/js/",
  ignore: ['node_modules'],
  setCommits: {
    // ==================== 需要改成对应项目的git地址=============
    repo: 'https://xxx.xx.xx',
    // ==================== 需要改成对应项目的git地址=============
    auto: true,
  },
}]
)

这里有几个点需要注意:

  • 上传的SourceMap格式需要为"source-map"
  • 版本号version可以根据实际情况及公司编码规范进行设置,且需要在入口处也填写这个版本号(保持一致)
  • urlPrefix设置需要跟实际资源路径一致(后面会具体讲解)

4. 上传到sentry后删除SourceMap

因为@sentry/cli插件上传完sourcemap文件到sentry服务器后不会自动删除对应map文件,为保证站点代码安全性,需要上传后将map文件删除。

config.plugins.push(
  new CleanWebpackPlugin({
    cleanAfterEveryBuildPatterns: ["./dist/js/*.js.map"]
  })
)
  • 使用rimraf,在postbuild脚本中执行删除
"postbuild:prod": "rimraf ./dist/js/*.js.map"
  • 在打包完成后上传文件到服务器时,排除map文件

5. 区分环境

为避免项目无用告警信息过多,可以通过代码设置区分一下环境,针对不同环境做不同操作。这里是根据BUILD_ENV环境变量进行区分(脚本命令中注入)。下面几个地方需要做对应修改:(加粗部分为新增修改代码)

  • package.json脚本命令
"build:test": "cross-env BUILD_ENV=test vue-cli-service build",
"build:prod": "cross-env BUILD_ENV=production vue-cli-service build",
  • 入口文件生成实例处
import Vue from 'vue'
import * as Sentry from '@sentry/browser'
import * as Integrations from '@sentry/integrations'
const isProd = process.env.BUILD_ENV === 'production'
isProd && Sentry.init({
    dsn: 'https://xxx.sentry域名/项目id',
    integrations: [new Integrations.Vue({Vue, attachProps: true})],
})
  • webpack配置文件处
const SentryCliPlugin = require('@sentry/webpack-plugin')
const version = '' // 版本号
const isProd = process.env.BUILD_ENV === 'production'
config.when(isProd, config => {
  config.devtool('source-map')
  config.plugin('SentryCliPlugin').use(SentryCliPlugin, [
    {
      release: version,
      // 打包后的代码目录 根据项目实际调整
      include: './dist',
      // url路径访问到的js资源前缀 根据项目实际调整 默认不用动
      // urlPrefix: "~/static/js/",
      ignore: ['node_modules'],
      setCommits: {
        // ==================== 需要改成对应项目的git地址=============
        repo: 'https://xxx.xx.xx',
        // ==================== 需要改成对应项目的git地址=============
        auto: true,
      },
    },
  ])
})
  • 最后,也可以选择性修改Sentry异常上报方法
import * as Sentry from '@sentry/browser'
const isProd = process.env.BUILD_ENV === 'production'
function ResetCaptureout(Sentry: any) {
  if (isProd) return Sentry
  Sentry.captureException = function() {
    const args = Array.prototype.slice.call(arguments).join(' ')
    return console.log('%csentry:', 'color:red;', args)
  }
  Sentry.captureMessage = function() {
    const args = Array.prototype.slice.call(arguments).join(' ')
    return console.log('%csentry:', 'color:green;', args)
  }
  return Sentry
}
Sentry = ResetCaptureout(Sentry)

6. 完成上述步骤,重新部署以后可以到Sentry服务器上进行验证

  • 查看是否生成对应版本:项目 -> 版本

截屏2021-04-23 下午6.29.19.png

  • 查看SourceMap文件是否上传成功:点击进入某一个版本 -> 工件(artifacts)

截屏2021-04-23 下午6.30.09.png

7. 看到版本以及对应的工件上传成功就表示已经接入完成,这时再发生报错我们就能看到具体源码信息了。

扩展

  • WebHook关联

Sentry提供很多插件集成,除了邮件通知功能还可以接入飞书或者钉钉等其他企业办公软件进行有效快速的通知。

  • 路径:项目 -> 设置 -> Legacy Integrations -> 选择Webhook -> 打开开关 -> Configure plugin

截屏2021-04-23 下午6.31.07.png

截屏2021-04-23 下午6.32.00.png

  • GitLab仓库集成

通过集成Gitlab仓库,我们可以将版本部署、提交记录,sentry issue等进行关联。

  • 路径:Sentry -> 设置 -> 集成 -> GitLab -> Add

截屏2021-04-23 下午6.32.40.png

  • 点击Add Another以后会弹出详细操作说明
  • 安装完以后可以选择Group下面对应的仓库进行添加
  • Issue关联

当Sentry上有新的报错产生时,可以选择将该报错信息作为一个issue关联到代码仓库上,比较简单,就不赘述了。效果如下图

截屏2021-04-23 下午6.33.20.png

告警设置

Sentry本身提供了组合项,可以根据具体业务及错误信息等实际情况进行选择设置。共有三种常见方式:告警规则设置、错误入站过滤器和Issue Owners指定。下面分别对这三种方式进行简单阐述说明。

告警规则

  • 设置路径 Sentry -> 项目 -> 设置 -> 警报 -> 编辑/新增

  • 可选项说明

  • An event is seen:当一个事件发生时

  • An issue is first seen:错误第一次出现

  • An issue changes state from resolved to unresolved:针对已修复问题复现场景

  • An issue changes state from ignored to unresolved:当xx条件下“忽略”的问题重新触发时进行提醒

  • An issue is seen more than xx times in xx time:对于一些已知的低级别,不影响使用的问题可以设置超过多少次以后再进行告警

  • An event's tags match xx equal to/does not equal xxx:根据事件标签值进行相应决策

  • An event's level is equal/does not equal xxx:选择事件级别(致命的/错误/警告/日志)

截屏2021-04-23 下午6.34.37.png

  • 触发动作 当产生的event符合告警规则时,触发的提醒动作。可以选Email或者其他集成。由于公司内部使用飞书作为日常办公IM,故这里设置为触发飞书WebHook。效果如下图:

截屏2021-04-23 下午6.34.58.png

入站过滤器

通过入站过滤器可以针对某些版本、某些IP地址用户或者针对指定版本浏览器原因造成的问题进行过滤处理。最重要的是我们可以通过入站过滤器进行自定义过滤规则对一些已知可忽视问题进行设置,比如针对一些请求超时、没有权限,登录态失效等非代码健壮性问题。

  • 设置路径 Sentry -> 项目 -> 设置 -> 入站过滤器

  • 查看过滤错误情况

截屏2021-04-23 下午6.35.35.png

截屏2021-04-23 下午6.35.49.png

Issue Owners

通过Issue Owners选项我们可以在Issue发生时自动建立Issue与责任人关联关系,与此同时直接发送邮件或者飞书通知给对应负责的小伙伴。一共有两种设置方式,都是根据正则表达式进行匹配

  • 路径(path):通过指定源文件所在目录及owners

  • 页面路由(url):通过指定具体页面路由及owners

截屏2021-04-23 下午6.46.06.png

错误解决方案

  • Resolve
    • 当前版本解决:发完版本以后,修改issue状态
    • 下个版本解决:issue发现后马上解决,代码跟随下个版本上线
    • 其他版本:一般不使用该选项
  • Ignore
    • 时间维度:xx时间之前忽略(可自定义时间)
    • 数量维度:再出现xx次之前忽略
    • 用户维度:再有xx个用户复现这个错误之前忽略

原理

前端错误类型

  • 即时运行错误:代码错误
  • 资源加载错误
  • 图片加载错误

上报错误的方法

  • 利用网络请求进行上报
  • 利用图片方式上报

Sentry实现原理

  • Init初始化,配置release和项目dsn等信息,然后将sentry对象挂载到全局对象上。
  • 重写window.onerror方法。

当代码在运行时发生错误时,js会抛出一个Error对象,这个error对象可以通过window.onerror方法获取。Sentry利用TraceKit对window.onerror方法进行了重写,对不同的浏览器差异性进行了统一封装处理。

  • 重写window.onunhandledrejection方法。

因为window.onerror事件无法获取promise未处理的异常,这时需要通过利用window.onunhandledrejection方法进行捕获异常进行上报。在这个方法里根据接收到的错误对象类型进行不同方式的处理。

  1. 如果接收到的是一个ErrorEvent对象,那么直接取出它的error属性即可,这就是对应的error对象。
  2. 如果接收到的是一个DOMError或者DOMException,那么直接解析出name和message即可,因为这类错误通常是使用了已经废弃的DOMAPI导致的,并不会附带上错误堆栈信息。
  3. 如果接收到的是一个标准的错误对象,不做处理
  4. 如果接收到的是一个普通的JavaScript对象
  5. 使用Ajax上传

当前端发生异常时,最终会调用Fetch请求上报到对应的Sentry服务器上,这里需要用到在初始化Sentry时传入的DSN。

踩坑记录

  • urlPrefix

在上传sourceMap文件时,不同项目打包后以及最终上传的静态资源文件路径可能有所区别。因此需要配置这个urlPrefix选项。该选项的意思是指项目上线后生产环境下对应的资源文件的完整路径。其中~表示网站根目录,下面举例说明:

站点域名:www.demo.com

静态资源路径:

www.demo.com/assets/js/1…

www.demo.com/assets/js/1…

那么这里:~ = www.demo.com

urlPrefix选项应该配置为:~/assets/js/

P.S. 默认为~/static/js/

截屏2021-04-23 下午6.48.59.png

以上面这张图为例:

Sentry拉取资源的时候路径为:域名/js/chunk-xxxx.js

  • sourceMap需要为“source-map”类型,否则无法还原错误。
  • DSN配置为HTTPS协议头,因此需要处理跨域问题。
  • sentry报错可以通过控制台Network选项进行查看。
  • 遇到问题多跑跑sentry社区,或者扒一下之前的issue记录,能解决80%的问题。

总结

前端项目业务场景日益复杂,经常会出现一些诡异的Bug,测试也很难覆盖到100%场景。某些小的代码问题很有可能造成一些意想不到的后果。与其等待用户发现问题反馈时背锅,不如通过监控系统及早发现,趁带来实际损失之前将其夭折。

参考文献

www.feishu.cn/hc/zh-CN/ar… zhuanlan.zhihu.com/p/75385179 juejin.cn/post/684490… zhuanlan.zhihu.com/p/75577689 zhuanlan.zhihu.com/p/89539449

文|Evan

关注得物技术,携手走向技术的云端