本文正在参加「金石计划」
对于一个大型项目来说,不管是后端还是前端,异常错误的监控都是非常重要的,拥有一套成熟完整的监控体系,能让我们在生产环境上发生错误时,可以第一时间定位错误发生的位置与原因,从而能更快地解决问题并重新发布
对于我们前端来讲,想拥有一套异常监控系统,无非两个方式
- 自研
- 使用第三方框架
首先想简单聊聊自研
自研
在前端领域,js给我们提供的错误捕获的方式总共有三种
- try catch: 对于可能报错的地方,可以采取这种方式来进行捕获错误,非常灵活
- window的error事件:全局兜底错误捕获,当js代码抛错时会触发,需要注意两点
- 如果手动使用try catch捕获错误,那么window的error事件就不会触发,可以理解为错误冒泡被阻止了
- 资源(js,css,img)的加载失败,不会触发window的error事件,而是触发它们自己的error事件
- window的unhandledrejection事件:当Promise被reject且没有reject处理器的时候,会触发unhandledrejection事件
当我们需要搭建自研监控平台时,其核心就是利用这三个方式来进行异常的捕获并上报,由于本文重点并不是如何去搭建一个自研监控平台,因此就不再深入下去了,下面就来聊聊目前一些成熟的第三方框架吧
使用第三方框架
目前市面上成熟的框架有很多,这里罗列一下我目前知道的
是不是好奇为啥这么多监控平台,偏偏Sentry目前是最流行的选择?
其实原因有以下几点
- 开源:有社区和官方一起维护,质量杠杠的
- 支持私有化部署:这块后面会专门讲解
- 免费:个人觉得也是最核心的原因
- 文档完备:可以帮助用户更好地使用,避免踩坑
那么接下来就好好聊聊Sentry吧
Sentry
Sentry是一个基于 Django 构建的现代化实时事件日志监控、记录和聚合平台,主要用于快速地发现故障,支持几乎所有主流开发语言和平台,并提供了现代化UI界面
Sentry可以搜集到客户端非常详细的信息,对于我们排查线上bug非常有帮助
下面是它支持的一些主流语言与平台
由此可见其功能的强大与完备,我猜你是不是已经摩拳擦掌,跃跃欲试了?
那么就聊聊如何使用它吧,使用sentry的方式分为两种
- sass:最方便的使用方式,只是官方提供的saas免费版只支持每天5000个event
- 私有化部署:需要占用自己服务器的资源空间,但数据都存在本地,相对更安全
不管使用哪种方式,都需要在我们前端项目里接入Sentry后才可以运行起来,因此下面就聊聊其接入流程,但首先需要注意下面两个前提
- 以 react 应用为例,对于其他平台的接入,可以查看 这个 地址进行查阅
- Sentry版本为 23.2.0
接入流程
- 安装相关依赖
# Using npm
npm install --save @sentry/react @sentry/tracing
# Using yarn
yarn add @sentry/react @sentry/tracing
- 配置sentry
import React from "react";
import ReactDOM from "react-dom";
import * as Sentry from "@sentry/react";
import { BrowserTracing } from "@sentry/tracing";
import App from "./App";
Sentry.init({
dsn: "https://examplePublicKey@o0.ingest.sentry.io/0",
integrations: [new BrowserTracing()],
// We recommend adjusting this value in production, or using tracesSampler
// for finer control
tracesSampleRate: 1.0,
});
ReactDOM.render(<App />, document.getElementById("root"));
需要注意这里一个dsn,全称 data source name,用于唯一标识创建的每一个项目,在后台管理界面可以拿到这个值
需要注意的是,这一步的引入,是可以捕获 事件监听器 里的错误,但是对于组件 渲染期间的生命周期 里的错误是无法捕获的,因此需要借助下面的 错误边界 来进行辅助
- 引入错误边界,捕获渲染期间的错误
import React from "react";
import * as Sentry from "@sentry/react";
<Sentry.ErrorBoundary fallback={<p>An error has occurred</p>}>
<Example />
</Sentry.ErrorBoundary>;
//也可以使用高阶组件的形式
import React from "react";
import * as Sentry from "@sentry/react";
Sentry.withErrorBoundary(Example, { fallback: <p>an error has occurred</p> });
-
Sentry也提供了上报api,供我们按需进行主动上报,支持的api如下
- captureException: 上报一个错误,
function captureException(exception: any): string;
- captureMessage:上报一个文本消息,
function captureMessage(message: string, level?: Severity): string;
- captureEvent:上报一个手动创建的事件,
function captureEvent(event: Event): string;
- captureException: 上报一个错误,
通过完成上述的接入流程,如果我们使用的是Sentry的sass,那么就可以愉快地监控项目啦
接入完成之后,下面就开始私有化部署的流程吧~
私有化部署
根据我之前的摸索踩坑经验,目前Sentry支持的私有化部署有两种方式
- 利用脚手架一键部署
- 自己手动部署Sentry的docker容器
下面分别说明
一键部署
通过上文可以看到,手动部署是很麻烦的,官方也考虑到了这个问题,于是推出了 脚手架 进行一键部署
其方式非常简单,就是把仓库下载到我们服务器的指定位置后,进入项目根目录,然后执行 ./install.sh
,之后的安装流程就完全是自动的,除了期间需要我们输入用户名和密码外,不需要做任何事情,非常简单便捷
但是通过这种方式进行部署,对机器的性能是有要求的,具体如下
- Docker 19.03.6+
- Docker-Compose 1.28.0+
- 4 CPU Cores
- 8 GB RAM
- 20 GB Free Disk Space
所以部署前,一定要确认自己机器的配置,不然到时候就是白忙活了🤣
还有一点需要注意的是通过这种方式部署后,运行的容器很多,需要占用的端口也很多,如果对这方面有顾虑的话,也需要谨慎使用
手动部署
这种方式需要我们手动去拉取Sentry的官方镜像进行部署,但比较坑的是官方Sentry的镜像最后的更新停留在 2019年,因此部署出来的Sentry版本只有 9.1.2,非常老的版本,很多新功能都不支持,但好在 sourcemap 功能是支持的,因此还是具备可使用性,下面就来具体讲解下这种方式的步骤
- 拉取必需镜像
docker pull sentry
docker pull redis
docker pull postgres
- 启动redis和postgres
docker run -d --name sentry-redis --restart=always redis ###保证了,异常自动拉起
docker run -d --name sentry-postgres -e POSTGRES_PASSWORD=secret -e POSTGRES_USER=sentry --restart=always postgres
- 生成sentry秘钥,生成的秘钥后面步骤中都需要填写,很重要
docker run --rm sentry config generate-secret-key
- 数据库及账户初始化,过程中需要创建用户和密码,用于Sentry系统的登录
docker run -it --rm -e SENTRY_SECRET_KEY='xxxxx' --link sentry-postgres:postgres --link sentry-redis:redis sentry upgrade
- 启动sentry的web服务
docker run -d -p 9000:9000 --name my-sentry -e SENTRY_SECRET_KEY='xxxxx' --link sentry-redis:redis --link sentry-postgres:postgres --restart=always sentry
- 启动sentry-cron/work服务
docker run -d --name sentry-cron -e SENTRY_SECRET_KEY='xxxx' --link sentry-postgres:postgres --link sentry-redis:redis sentry run cron
docker run -d --name sentry-worker-1 -e SENTRY_SECRET_KEY='xxxxx' --link sentry-postgres:postgres --link sentry-redis:redis sentry run worker
通过上述步骤,就可以通过 ip或域名:9000 来访问Sentry了
关于手动部署,由于其镜像过于老旧,因此在接入流程中会跟最新版的有些区别,根据我之前的踩坑经验,区别点如下
- 安装的依赖有所不同
# 新版
# 不能使用这些新版的库,不然无法捕获错误
# Using npm
npm install --save @sentry/react @sentry/tracing
# Using yarn
yarn add @sentry/react @sentry/tracing
# 旧版
# Using npm
npm install --save @sentry/browser@5.4.2
# Using yarn
yarn add @sentry/browser@5.4.2
- 接入sentry库的写法有所不同
//新版,不能使用这些新库
import * as Sentry from "@sentry/react";
import { BrowserTracing } from "@sentry/tracing";
import App from "./App";
Sentry.init({
dsn: "https://examplePublicKey@o0.ingest.sentry.io/0",
integrations: [new BrowserTracing()],
// We recommend adjusting this value in production, or using tracesSampler
// for finer control
tracesSampleRate: 1.0,
});
//旧版
import * as Sentry from '@sentry/browser';
Sentry.init({
dsn: 'abc',
release: 'test',
});
- 错误边界的捕获有所不同
//新版,不能用
import React from "react";
import * as Sentry from "@sentry/react";
<Sentry.ErrorBoundary fallback={<p>An error has occurred</p>}>
<Example />
</Sentry.ErrorBoundary>;
//旧版,需要自己实现错误边界组件,并在componentDidCatch里主动上报错误
import * as Sentry from '@sentry/browser';
......
componentDidCatch(error) {
Sentry.captureException(error);
}
....
上传sourcemap
其实通过上述的接入和部署后,已经可以正常地去监控项目里的错误了,但是还可以再往前走一步,那就是上传sourcemap。现在前端项目上线前,都会经过打包编译的过程,这样导致了线上的代码无法去阅读,最终使我们无法去定位线上bug的具体原因
在我们没有上传sourcemap前,在Sentry里只能看到 编译后 代码的报错行列信息,但这对于我们来说没有意义,我们需要的是定位到源码的位置,因此我们就需要把sourcemap上传到Sentry,从而支持查看源码报错位置信息的功能
上传sourcemap的方式总共两种
- 利用 @sentry/cli 手动上传,不推荐,因为麻烦
- 利用插件自动在打包项目时上传sourcemap,推荐,因为省心且与ci/cd高度契合,下文也会着重讲解这种方式的使用步骤
目前前端项目的打包工具使用最多的就是 webpack 和 vite,幸运的是Sentry官方都提供了对应插件进行支持
- webpack:@sentry/webpack-plugin
- vite:@sentry/vite-plugin
给个简单的使用示例
// vite.config.ts
import { sentryVitePlugin } from "@sentry/vite-plugin";
export default {
plugins: [
sentryVitePlugin({
url: env.VITE_SENTRY_URL,
org: env.VITE_SENTRY_ORG,
project: env.VITE_SENTRY_PRO,
include: './dist',
authToken: env.VITE_SENTRY_AUTH,
telemetry: false,
releaseInjectionTargets: [join(__dirname, 'src', 'main.tsx')],
release: info.version
}),
],
};
//webpack.config.js
const SentryCliPlugin = require('@sentry/webpack-plugin');
const config = {
plugins: [
new SentryCliPlugin({
include: '.',
ignoreFile: '.sentrycliignore',
ignore: ['node_modules', 'webpack.config.js'],
configFile: 'sentry.properties',
release: info.version
}),
],
};
具体配置可以查看对应文档,写的都很详细,只是这里我想单独说明的一点是关于 release 选项的
release可以理解为版本,项目的每一次部署都可以看做是一个版本
在Sentry里,如果想要支持sourcemap功能,那么 插件里的release 必须和 Sentry SDK里的release 相同,否则无法正常使用sourcemap功能,也就是说sourcemap功能是与版本号强绑定的
//webpack.config.js 需要与入口文件的release相同
new SentryCliPlugin({
....
release: 'test'
....
})
//vite.config.ts 需要与入口文件的release相同
sentryVitePlugin({
....
release: 'test'
....
})
//index.js 入口文件
....
import * as Sentry from "@sentry/react";
...
Sentry.init({
....
release: 'test'
....
});
结语
相信通过本文的阅读,你一定对Sentry非常熟悉了,那么赶紧把这么好的东西用到你的项目里吧,早用早享受!
都看到这里啦,如果本篇文章对你有帮助,希望能 点个赞👍 支持下啦,你们的支持才是我最大的动力!😘