如何用Sentry及时、准确地捕获前端异常

621 阅读11分钟

作为一名前端开发工程师,我发现大家总会遇到这样的场景:用户反馈功能异常,我们为了找出问题所在,在本地费尽心力地复现异常,结果却一无所获。另一方面,前端代码的运行环境复杂,而且不可控,如 PC 浏览器、手机移动浏览器等,这就意味着代码可能出现各种难以预料的报错。

在这种情况下,如果我们希望能有一套完善的前端异常监控系统,那我们首先要关心的问题就是怎么及时捕获异常,又怎么能精确定位异常报错位置?在收集到异常后怎么及时通知相关人员呢?

这三个问题,就是我们今天这节课要解决的问题。

选择解决方案

我们先来看一下,常见的前端监控解决方案有哪些。

方案一:自研。具体的工作主要是:自行重写 Window 对象内的 onerror、onunhandledrejection 方法,收集报错信息,再通过服务端接口上传,写入服务器文件,用 Sourcemap 文件还原源代码来排查问题。除此之外可能还要搭建异常信息查看平台。所以,采用自研的方式,缺点就非常明显,需要耗费大量的精力。当然,自研也有扩展方便的优点,比如在前端监控里面加入性能监控的功能,方便后续对页面进行性能优化。

方案二:借助成熟的第三方工具,比如 Fundebug、ARMS、Sentry 等。这种方式不需要重度开发,只要接入配置就行了。

对于这两种方案,如果你没有那么多精力自研,就选择借助第三方工具吧。

在成熟的第三方工具中,我会推荐你使用 Sentry。因为 Sentry 是一个开源的错误追踪工具,能帮助我们实时监控和修复系统的错误。而且,Sentry 支持通过 Sourcemap 文件还原 JS 错误调用栈,也可以在收集到异常后实时钉钉或者邮件通知。

还有很重要的一点,Sentry 允许我们在自己的服务器上搭建私有服务,这就意味着我们可以免费使用 Sentry 强大的功能,还能避免源代码外泄的风险。这张图可以直观地展示 Sentry 收集异常信息的效果。

确定选择 Sentry 之后,怎么部署它,让它能及时捕获到前端异常呢?接下来我会一步步地跟你介绍。

如何及时捕获异常?

部署 Sentry

第一步,毫无疑问你要部署 Sentry。因为官方推荐 Docker 部署,再加上 Docker 操作起来也比较简单,所以我就用 Docker 举例,给你展示怎么从零开始搭建一个 Sentry 服务。

首先我们安装 Docker、Docker-Compose,安装完成后启动 Docker,拉取 sentry-onpremise 仓库代码,这是 Sentry 官方提供的安装程序,然后启动里面的安装脚本,在这期间,它会引导你创建一个管理员账户,最后启动 Sentry 服务。相关的命令可以参考这段代码:

git clone https://github.com/getsentry/onpremise
cd onpremise
./install.sh
# 启动 Sentry 服务
$ docker-compose up -d

如果启动的过程一切正常,在浏览器中输入 http://ip:9000 就能访问到 Sentry 的登录页面了,然后用刚才创建的管理员用户名和密码登录系统。

前端项目接入

经过刚才的一番操作,我们的 Sentry 服务端就搭建完成了,接下来就可以在应用程序中集成 Sentry 客户端 SDK,用来在前端代码中实时上报错误。

Sentry 的功能十分强大,支持各种前端框架,如 Vue、Angular、React 等。我们都知道 Vue 是一个非常受欢迎的前端轻量级框架,有轻巧、高性能、可组件化等优点,那么下面我就以 Vue 项目为例来进行详细的介绍。

创建项目

首先,我们点击 Sentry 页面左侧导航栏第一项 Projects,接着点击页面右上角 Create Project 按钮,然后选择 Browser 下的 Vue,再次点击 CreateProject 创建项目。接下来,你就会看到 Sentry 给出的关于 Vue 项目的配置指南,它会引导你一步一步完成项目配置。

那么我们按照指南继续。

首先通过 npm 在工程内安装 Sentry 依赖的两个 npm 包,也就是@sentry/browser 和@sentry/integrations,接着在 main.js 内 import 引入它们,然后通过 Sentry.init 进行 Sentry 的初始化配置。配置的时候,指南内已经提供了初始化相关代码,我们直接复制粘贴到 main.js 里面就行。最后,我们点击指南底部的确认按钮,然后就自动跳转到了这个项目的 Issue 报错页面。

这里你需要特别注意的是,DSN 地址与项目一一对应,是不可以随意改动的。

import * as Sentry from '@sentry/browser'
import { Vue as VueIntegration } from '@sentry/integrations'
// Sentry初始化设置,attachProps用于配置是否上报组件的props
Sentry.init({
  dsn: 'https://xxxxxxxxxxxxxxxxx@sentry.in-hope.cn/2',
  integrations: [new VueIntegration({
    Vue,
    attachProps: true
  })]
})

验证

现在我们看到的 Issue 报错页面内,只有一个空白的表格,还没有任务异常信息。我们就来造一个 JS 报错,看一下 Sentry 的效果。

首先我们在 App.vue 的 created 方法内增加一行代码:this.test(),来调用一个当前组件内不存在的方法,强行产生一个 JS 报错。通过 Chrome DevTools 内的 Network,我们可以看到每次页面刷新后,都会有一个 Sentry 相关的 Post 请求发出,这是 Sentry 在收集异常信息。这时候再去看 Issue 报错页面,我们发现 Sentry 已经将捕获到的异常展示出来了:

我们知道,Sentry 将每一次异常上报看做一个 Event,每一个 Event 都有 Fingerprint(指纹),Fingerprint 默认是由 Sentry 的分组算法生成,相同 Fingerprint 的 Event 会被自动合并成一个 Issue,具体的生成逻辑你可以查看官方文档: docs.sentry.io/data-manage…

通过 Issue 列表,我们可以得到的信息有:异常类型、异常名称、触发位置、最近触发时间、首次触发时间、触发总次数、触发总人数等。

点击进入到 Issue 详情页面,在页面中间区域,你可以看到最近一次 Event 的具体信息,如用户的 IP 地址、浏览器信息、系统信息、异常调用栈信息等。

如何精确定位异常报错位置?

现在,我们看到 Sentry 已经捕获到了异常调用栈信息了。但是,因为线上代码都是被压缩混淆过的,所以要知道具体是哪行代码报错只能全局搜索关键字,然后根据压缩后代码的上下文来进行定位。

那么如何能精准定位错误信息呢?这是我们今天要解决的第二个主要问题。

首先,我们在 Sentry 后台配置 AuthToken,这个是配置上传 Sourcemap 的一个必要参数。那怎么创建这个 Token 呢?

我们点击页面左上角的用户名,在下拉菜单内找到 User Settings,点击进入到用户设置界面,然后点击 Auth Tokens 菜单选项,创建一个新 Token。接下来,我们将代码构建编译时的 Sourcemap 配置开启,然后在根目录下新建 .sentryclirc 文件,配置内容可以参考这段代码:

[auth]
token=e0c8840a0f97489fb8cdd2e37b30394028776b754ee
[defaults]
### 项目名称
project=xiaoan-web
### 组织团队名,默认是 sentry
org=sentry
### 你的域名
url=https://sentry.xxxxx.cn/

最后,在工程内下载安装 Webpack 插件@sentry/webpack-plugin,在打包配置文件内增加上传 Sourcemap 到 Sentry 的配置,具体配置可以参考这段代码:

这里面的 Release 属性对应代码版本,Sentry 在收集异常信息的时候,会同步收集用户的代码版本信息,通过这个信息,我们可以知道一个新产生的问题是由哪一次发布导致的。

需要注意的是,UrlPrefix 属性值不是固定的,而是与项目静态资源访问路径相关,比如我的项目生产环境中静态资源 JS 的完整地址是“ xiaoan.in-hope.cn/static/js/a…

const SentryWebpackPlugin = require('@sentry/webpack-plugin')
// 获取当前Git记录的hashId
const commitHash = require('child_process').execSync('git rev-parse HEAD').toString();
// 上传Sourcemap文件到Sentry
new SentryWebpackPlugin({
  include: path.resolve(__dirname, '../dist/static/js/'),
  ignoreFile: '.sentrycliignore',
  ignore: ['node_modules', 'webpack.config.js'],
  release: commitHash,
  urlPrefix: '~/static/js'  // 线上访问到JS文件的路径,https://xiaoan.in-hope.cn/static/js/app.js
})

配置完成后,Sentry 就可以根据上传的 Sourcemap 还原代码位置了:

从这张图中你可以看到,Sentry 明确标识了这次报错,是在 commonSearch.vue 文件的第 643 行,同时,它把前后代码也展示出来了。

前面介绍的是怎么配置 Sentry 自动捕获异常上报,但是有些时候我们需要主动上报一些信息,例如:增加线上日志排查问题、服务端接口异常捕获,这时候我们应该怎么办呢?

Sentry 提供了两个 API 来主动上报信息,一个是 captureMessage,另一个是 captureException,它们的使用方式可以参考这段代码:

Sentry.captureMessage('Hello, world!'); // 上报信息
Sentry.captureException(new Error('Good bye')); // 上报异常

很多情况下,我们排查线上报错的时候,期望能获得用户的业务相关信息,例如:UserId、UserInfo 等,方便我们根据业务逻辑分析问题在哪。那么我们就可以在 Sentry 初始化的时候,通过 Sentry.configureScope 来设置一些基础信息,这些信息会在发生异常的时候,被 Sentry 同步收集,展示在 Issue 详情页。(代码清单 配置额外信息)

设置基础信息的命令可以参考这段代码:

// Sentry初始化配置
Sentry.init({
   dsn: 'https://xxxxxxxxxxxxxxxxx@sentry.in-hope.cn/2',
  integrations: [new VueIntegration({
    Vue,
    attachProps: true
  })]
}
Sentry.configureScope((scope) => {
  // 设置Sentry区分不同用户的信息,此处以UserId区分不同用户
  scope.setUser({'id': Storage.get('userId') || ''})
  // 设置额外的上传信息
  scope.setExtra('userInfo', Storage.getUserInfo())
})

讲到这里,我们已经解决了两个问题,也就是怎么及时捕获异常,以及怎么能精确定位异常报错位置?下面接着来看第三个问题,在收集到异常后怎么及时通知相关人员?

收集到异常后如何及时通知相关人员?

当 Sentry 捕获到异常后,我们肯定希望它能实时通知到开发人员,对于这个情况,Sentry 提供了邮件通知的功能,只需要在 Sentry 的配置文件内增加相关配置就可以了。由于邮件的查看可能不够及时,如果你所在团队使用钉钉协作办公,那么可以尝试使用第三方钉钉插件 sentry-dingding 来让 Sentry 实时推送消息到钉钉。那么如何进行相关配置呢?

首先,我们在部署 Sentry 的 Docker 容器里,安装 sentry-dingding,然后编辑插件列表,增加钉钉插件配置:

# 下载 sentry-dingding
pip install sentry-dingding
# 编辑插件列表
vim requirements.txt
# Add plugins here
sentry-dingding~=0.0.1
redis-py-cluster==1.3.4

然后,我们创建一个钉钉机器人,在项目设置的 LegacyIntegrations 页面找到 DingDing 插件并启用,接着在 AccessToken 输入框内填入钉钉机器人的对应 Token。如果一切正常,在 Sentry 再次捕获到异常后,钉钉会推送相关信息。就像这张图里显示的这样,我们点击详细链接就会跳转到对应的 Issue 详情页。

这里需要注意的是,sentry-dingding 钉钉这个插件已经两年左右没更新了,如果你的 Sentry 版本比较高,会出现“Event object has no attribute id”这样的报错信息。这个时候,你可以在 GitHub 上 sentry-dingding 项目的 Issues 内,找到第三方 fork 修复后的版本,当然你也可以参考 PR 自行本地安装修改源码,只需要改动一个字段就行。

总结

以上就是使用 Sentry 这个工具收集异常的全过程了,我做了一张思维导图来帮助你更好地消化这一期的内容。

在做到异常的及时收集后,我们肯定还需要建立任务进行修复,还要跟踪修复状态。Sentry 可以集成 JIRA、GitHub 等任务管理系统,帮助我们更方便的进行进度跟踪。比如,集成 JIRA 后,在 Issue 详情页面,就可以直接创建 JIRA 任务,同时将 JIRA 任务与这个 Issue 状态关联起来,JIRA 任务完成后,这个 Issue 状态自动就同步成已解决了。