百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 技术文章 > 正文

搭建sentry监控平台从零开始搭建一个高颜值后台管理系统全栈框架

wxin55 2024-10-25 18:06 9 浏览 0 评论

背景

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

监控平台选型

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

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

私有化部署sentry

环境准备

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

部署

执行下面命令,拉取项目

git clone https://github.com/getsentry/onpremise

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

cd onpremise
./install.sh

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

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

docker-compose up -d

启动项目

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

设置语言

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

小结

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

新建前端项目

前端项目集成sentry

安装依赖

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就能捕获到异常了。

import { useRouteError } from 'react-router-dom';

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

export default RouterErrorElement;

异常也能正常上报了

美化一下报错页面

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提供了命令,可以快速生成配置。

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

安装依赖

pnpm i --save @sentry/node @sentry/profiling-node @sentry/utils

初始化Sentry

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

使用sentry捕获异常

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

改造src/filter/default.filter.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项目的示例代码改造而来的,理解起来比较费劲。

// 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个位置上的值。

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


原文链接:https://juejin.cn/post/7275980024262443068

相关推荐

ES6中 Promise的使用场景?(es6promise用法例子)

一、介绍Promise,译为承诺,是异步编程的一种解决方案,比传统的解决方案(回调函数)更加合理和更加强大在以往我们如果处理多层异步操作,我们往往会像下面那样编写我们的代码doSomething(f...

JavaScript 对 Promise 并发的处理方法

Promise对象代表一个未来的值,它有三种状态:pending待定,这是Promise的初始状态,它可能成功,也可能失败,前途未卜fulfilled已完成,这是一种成功的状态,此时可以获取...

Promise的九大方法(promise的实例方法)

1、promise.resolv静态方法Promise.resolve(value)可以认为是newPromise方法的语法糖,比如Promise.resolve(42)可以认为是以下代码的语...

360前端一面~面试题解析(360前端开发面试题)

1.组件库按需加载怎么做的,具体打包配了什么-按需加载实现:借助打包工具(如Webpack的require.context或ES模块动态导入),在使用组件时才引入对应的代码。例如在V...

前端面试-Promise 的 finally 怎么实现的?如何在工作中使用?

Promise的finally方法是一个非常有用的工具,它无论Promise是成功(fulfilled)还是失败(rejected)都会执行,且不改变Promise的最终结果。它的实现原...

最简单手写Promise,30行代码理解Promise核心原理和发布订阅模式

看了全网手写Promise的,大部分对于新手还是比较难理解的,其中几个比较难的点:状态还未改变时通过发布订阅模式去收集事件实例化的时候通过调用构造函数里传出来的方法去修改类里面的状态,这个叫Re...

前端分享-Promise可以中途取消啦(promise可以取消吗)

传统Promise就像一台需要手动组装的设备,每次使用都要重新接线。而Promise.withResolvers的出现,相当于给开发者发了一个智能遥控器,可以随时随地控制异步操作。它解决了三大...

手写 Promise(手写输入法 中文)

前言都2020年了,Promise大家肯定都在用了,但是估计很多人对其原理还是一知半解,今天就让我们一起实现一个符合PromiseA+规范的Promise。附PromiseA+规范地址...

什么是 Promise.allSettled()!新手老手都要会?

Promise.allSettled()方法返回一个在所有给定的promise都已经fulfilled或rejected后的promise,并带有一个对象数组,每个对象表示对应的pr...

前端面试-关于Promise解析与高频面试题示范

Promise是啥,直接上图:Promise就是处理异步函数的API,它可以包裹一个异步函数,在异步函数完成时抛出完成状态,让代码结束远古时无限回掉的窘境。配合async/await语法糖,可...

宇宙厂:为什么前端离不开 Promise.withResolvers() ?

大家好,很高兴又见面了,我是"高级前端进阶",由我带着大家一起关注前端前沿、深入前端底层技术,大家一起进步,也欢迎大家关注、点赞、收藏、转发。1.为什么需要Promise.with...

Promise 新增了一个超实用的 API!

在JavaScript的世界里,Promise一直是处理异步操作的神器。而现在,随着ES2025的发布,Promise又迎来了一个超实用的新成员——Promise.try()!这个新方法简...

一次搞懂 Promise 异步处理(promise 异步顺序执行)

PromisePromise就像这个词的表面意识一样,表示一种承诺、许诺,会在后面给出一个结果,成功或者失败。现在已经成为了主流的异步编程的操作方式,写进了标准里面。状态Promise有且仅有...

Promise 核心机制详解(promise机制的实现原理)

一、Promise的核心状态机Promise本质上是一个状态机,其行为由内部状态严格管控。每个Promise实例在创建时处于Pending(等待)状态,此时异步操作尚未完成。当异步操作成功...

javascript——Promise(js实现promise)

1.PromiseES6开始支持,Promise对象用于一个异步操作的最终完成(包括成功和失败)及结果值的表示。简单说就是处理异步请求的。之所以叫Promise,就是我承诺,如果成功则怎么处理,失败怎...

取消回复欢迎 发表评论: