搭建sentry监控平台,实现前后端异常监控。——从零开始搭建一个高颜值后台管理系统全栈框架(十六)

背景

前面项目基本功能已经实现完了,这一篇集成一下监控平台,监控一下前后端异常。

监控平台选型

阿里云的应用实时监控服务ARMS和字节的应用性能监控全链路版这两个我都在公司里使用过,使用起来差不多,支持的功能也差不多,不过是收费的。

这里我推荐一个开源的监控平台sentry,功能比上面两个强大很多,支持多种语言。有saas版,也可以自己部署一套。

私有化部署sentry

环境准备

  • Docker 19.03.6+
  • Docker-Compose 1.28.0+
  • 4 CPU Cores
  • 8 GB RAM
  • 20 GB Free Disk Space

部署

执行下面命令,拉取项目

sh 复制代码
git clone https://github.com/getsentry/onpremise

代码拉下来之后,项目根目录下,会有一个install.sh文件,执行这个文件。

sh 复制代码
cd onpremise
sh 复制代码
./install.sh

这里选n,继续安装,中间需要设置管理员帐号和密码,这里设置的帐号和密码要记住,后面登录需要用到。安装过程比较慢,要多等一会。

执行结束后,执行下面命令启动服务,启动也比较慢。

sh 复制代码
docker-compose up -d

启动项目

启动成功后,访问9000端口,输入刚才设置的帐号和密码登录。

设置语言

登录之后,点开用户设置,可以设置语言为中文。

小结

如果不想自己部署,可以到sentry saas平台注册一个帐号,不过只能免费试用30天。

新建前端项目

前端项目集成sentry

安装依赖

sh 复制代码
pnpm i @sentry/react --save

测试验证

安装上面的教程把代码复制到项目里,改造main.tsx文件。

在登录方法里故意整个错,测试一下

进入项目后,可以发现报错

这个最牛的是,可以回放用户的操作,有利于定位问题,这个功能其它几个监控平台都不支持。

对接react-router

为了更精准的获取页面加载性能信息和页面报错信息,需要把sentry和react-router结合。

设置用户上下文

线上报错了,为了更好的调试,我们需要知道当前报错是哪个用户触发的,sentry支持在上传报错信息时注入当前用户信息。

登录成功后,调用setUser方法全局设置用户信息。

查看报错信息时,可以看到当前用户id了。

对接React Error Boundary

现在组件渲染的时候,如果有报错,会出现不友好的报错界面。

模拟组件渲染出现异常

这个报错页面,其实是react-router默认报错页面,react-router支持自定义报错页面,使用errorElement属性就行了。

如果这样写,因为异常被拦截了,没办法上报。还好sentry支持了异常组件,只需要用Sentry.ErrorBoundary组件包裹最外层组件就行了。

改造完测试了一下,发现不生效,因为上面我们拦截了异常,所以Sentry.ErrorBoundary监听不到。把errorElement去掉也不行,因为react-router内部会捕获异常,不设置errorElement会用内置的。这里我被卡了一段时间,后来在react-router官网中找到了useRouteError这个api,可以获取报错信息。

后来的解决方案是,写一个组件,组件里使用useRouteError获取到报错信息后,再抛出异常,这时候外面的Sentry.ErrorBoundary就能捕获到异常了。

tsx 复制代码
import { useRouteError } from 'react-router-dom';

const RouterErrorElement = () => {
  const error = useRouteError();
  throw error;
}

export default RouterErrorElement;

异常也能正常上报了

美化一下报错页面

tsx 复制代码
import React from 'react';
import { Button, Result } from 'antd';
import { router } from './router';

const ErrorPage: React.FC = () => (
  <Result
    status="error"
    title="出错了"
    subTitle="我们正在努力修复中,请稍后再试。"
    extra={[
      <Button onClick={() => { router.navigate('/') }} type="primary" key="console" >
        回到首页
      </Button>
    ]}
  />
);

export default ErrorPage;

使用刚才封装的报错页面

上传sourcemap

打包发布到线上后,因为代码压缩混淆,没办法定位报错的代码。

sentry提供了命令,可以快速生成配置。

sh 复制代码
npx @sentry/wizard@latest -i sourcemaps

执行命令后,可以选择使用它的saas平台,还是自己搭建的,如果是自己搭建的选第二个,然后输入自己的平台地址。

我们已经创建了用户,这里选yes

选择后,会打开网页,让你授权,授权成功后,选择项目

这里选择Vite

选择后,会使用pnpm帮你安装@sentry/vite-plugin依赖

这里选择yes,我们使用CICD发布

上面选择完yes后,会给你生成一个authToken,这个token要配置github环境变量里。下面继续选yes。

然后就完成了,有几个地方需要改造一下。

  • 删除env.sentry-build-plugin文件,因为这个文件里存了token。生成的token不能放在项目里,项目是公开的,token会泄漏。

  • 这里因为我们使用的是github workflow,可以把token配在github中,然后代码里从环境变量里去token。

  • 因为需要上传sourcemap,所以脚本把sourcemap打开了,打包出来的代码有sourcemap文件,这玩意不能上传到线上,不然源代码就泄漏了,所以在sourcemap上传到sentry平台后,需要给移除掉。

打包发布后,这里可以查看到sourcemap。

报错也能定位源码了

到此前端异常监控搞定了,前端性能监控和埋点我还在研究中,等研究好了,再出一篇文章。

新建后端项目

sentry没有midway插件,但是支持koa项目,midway底层用的就是koa,所以这里选koa项目。

后端项目集成sentry

安装依赖

sh 复制代码
pnpm i --save @sentry/node @sentry/profiling-node @sentry/utils

初始化Sentry

src/configuration.ts文件中加入下面代码,初始化Sentry

使用sentry捕获异常

普通的业务报错,我们不用上报,只上报500的异常。

改造src/filter/default.filter.ts文件

ts 复制代码
import { Catch } from '@midwayjs/core';
import { Context } from '@midwayjs/koa';
import * as Sentry from '@sentry/node';
@Catch()
export class DefaultErrorFilter {
  async catch(err: Error, ctx: Context) {
    // 捕获异常,并把异常和接口绑定一起上报
    Sentry.withScope(scope => {
      scope.addEventProcessor(event => {
        return Sentry.addRequestDataToEvent(event, ctx.request);
      });
      Sentry.captureException(err, { user: { id: ctx?.userInfo?.userId } });
    });

    ctx.status = 500;

    return {
      code: 500,
      message: '系统错误',
    };
  }
}

改造获取当前用户信息接口,故意写一段报错代码

前端访问一下接口,然后线上就能看到报错信息。

统计接口执行时间

编写中间件,这里的代码参考了官网koa项目的示例代码改造而来的,理解起来比较费劲。

ts 复制代码
// src/middleware/sentry.ts
import { Middleware, IMiddleware } from '@midwayjs/core';
import { NextFunction, Context } from '@midwayjs/koa';
import * as Sentry from '@sentry/node';
import { stripUrlQueryAndFragment } from '@sentry/utils';

@Middleware()
export class SentryMiddleware implements IMiddleware<Context, NextFunction> {
  resolve() {
    return async (ctx: Context, next: NextFunction) => {
      return new Promise<void>(resolve => {
        Sentry.runWithAsyncContext(() => {
          const hub = Sentry.getCurrentHub();
          hub.configureScope(async scope => {
            scope.addEventProcessor(event => {
              return Sentry.addRequestDataToEvent(event, ctx.request);
            });

            const reqMethod = (ctx.method || '').toUpperCase();
            const reqUrl = ctx.url && stripUrlQueryAndFragment(ctx.url);

            const transaction = Sentry.startTransaction({
              name: `${reqMethod} ${reqUrl}`,
              op: 'api',
            });

            Sentry.getCurrentHub().configureScope(scope => {
              scope.setSpan(transaction);
            });

            ctx.__sentry_transaction = transaction;

            await next();

            if (ctx._matchedRoute) {
              const mountPath = ctx.mountPath || '';
              transaction.setName(
                `${reqMethod} ${mountPath}${ctx._matchedRoute}`
              );
            }
            transaction.setHttpStatus(ctx.status);
            transaction.finish();

            resolve();
          });
        });
      });
    };
  }

  static getName(): string {
    return 'sentry';
  }
}

查看效果

上面有几个参数要说一下

p50: 所有用户请求这个接口请求时间的中位数,从小到大排序。

p95: 所有用户请求这个接口请求时间的95%的值,从小到大排序。假设有有100个人请求,从小到大排序后,第95个位置上的值。

这里为啥不使用请求时间平均值,因为如果某次接口出现异常调用时间特别长,会导致平均值和实际值差距会很大。

最后

到此最基本的前后端异常监控搞定了,性能分析和埋点我还在研究中,后面研究完再给大家分享吧。

项目体验地址:fluxyadmin.cn/user/login

前端仓库地址:github.com/dbfu/fluxy-...

后端仓库地址:github.com/dbfu/fluxy-...

相关推荐
xing251627 分钟前
pytest-html
前端·html·pytest
茂茂在长安37 分钟前
Linux 命令大全完整版(11)
java·linux·运维·服务器·前端·centos
Violet51538 分钟前
ECMAScript规范解读——this的判定
javascript
知识分享小能手1 小时前
Html5学习教程,从入门到精通,HTML5 简介语法知识点及案例代码(1)
开发语言·前端·javascript·学习·前端框架·html·html5
IT、木易1 小时前
大白话React第二章深入理解阶段
前端·javascript·react.js
晚安7201 小时前
Ajax相关
前端·javascript·ajax
图书馆钉子户1 小时前
怎么使用ajax实现局部刷新
前端·ajax·okhttp
bin91532 小时前
DeepSeek 助力 Vue 开发:打造丝滑的单选按钮(Radio Button)
前端·javascript·vue.js·ecmascript·deepseek
qianmoQ2 小时前
第五章:工程化实践 - 第五节 - Tailwind CSS 常见问题解决方案
前端·css
那就可爱多一点点2 小时前
超高清大图渲染性能优化实战:从页面卡死到流畅加载
前端·javascript·性能优化