微前端接入Sentry

Sentry 在微前端的支持

官方文档 # Micro Frontend Support

依赖

注意: 我们说一下功能点对版本有一定的要求, 如果版本低的话会不生效

  • @sentry/webpack-plugin@2.5.0
  • @sentry/react@7.59.0

安装依赖

js 复制代码
yarn add -D @sentry/webpack-plugin
yarn add @sentry/react

识别来源

要确定错误的来源,必须注入元数据,以帮助确定哪些捆绑包对错误负责。您可以通过启用 @sentry/webpack-plugin 的 _experiments.moduleMetadata 选项,使用任何 Sentry bundler 插件来实现这一点。

这样的话我们就可以在微前端的主项目和的子应用配置 moduleMetadata, 然后传入子应用的 dsn 动态区分确认的来源

一旦元数据被注入到模块中, moduleMetadata 集成就可以用来查找元数据,并将其附加到具有匹配文件名的堆栈帧中。然后,该元数据在其他集成中 比如 beforeSend 回调中可用,作为每个 StackFrame 上的 module_metadata 属性。这可用于识别哪些捆绑包可能对错误负责,并用于标记或路由事件。

例子:

js 复制代码
// webpack.config.js
const { sentryWebpackPlugin } = require("@sentry/webpack-plugin");

module.exports = {
  devtool: "source-map",
  plugins: [
    sentryWebpackPlugin({
      /* Other plugin config */
      // 我们在配置 @sentry/webpack-plugin 的时候 可以自定义一些 moduleMetadata 数据
      // 这些数据可以在 sentry.init下的 beforeSend 拿到
      _experiments: {
        moduleMetadata: ({ org, project, release }) => {
          return { team: 'frontend', release },
        }
      },
    }),
  ],
};
js 复制代码
import * as Sentry from "@sentry/browser";
import { ModuleMetadata } from "@sentry/core";

Sentry.init({
  dsn: "https://examplePublicKey@o0.ingest.sentry.io/0",
  integrations: [ new ModuleMetadata() ]
  // 这里就可以拿到上面 @sentry/webpack-plugin 配置的 module_metadata 数据
  // 通过动态过滤就可以添加额外参数
  // 当然这样直接添加额外参数一般情况是不需要用到的 所以下面要提到自定义多虑传输 Transports
  beforeSend: (event) => {
    const frames = event?.exception?.values?.[0].stacktrace.frames || [];
    // Get all team names in the stack frames
    const teams = frames.filter(frame => frame.module_metadata && frame.module_metadata.team)
                        .map(frame => frame.module_metadata.team);
    // If there are teams, add them as extra data to the event
    if(teams.length > 0){
      event.extra = {
        ...event.extra,
        teams
      };
    }

    return event;
  },
});

Sentry.captureException(new Error("oh no!"));

但是我们在识别了不同子应用的报错后应该怎么准备的将错误上报到对应的子应用的 sentry 呢

这个时候我们就需要用到 sentry 为我们提供的自定义多路传输 Transports

例子:

ts 复制代码
import { captureException, init, makeFetchTransport } from "@sentry/browser";
import { makeMultiplexedTransport } from "@sentry/core";
import { Envelope, EnvelopeItemType } from "@sentry/types";

interface MatchParam {
  /** The envelope to be sent */
  envelope: Envelope;
  /**
   * A function that returns an event from the envelope if one exists. You can optionally pass an array of envelope item
   * types to filter by - only envelopes matching the given types will be returned.
   *
   * @param types Defaults to ['event'] (only error events will be returned)
   */
  getEvent(types?: EnvelopeItemType[]): Event | undefined;
}

function dsnFromFeature({ getEvent }: MatchParam) {
  const event = getEvent();
  switch (event?.tags?.feature) {
    case "cart":
      return [{ dsn: "__CART_DSN__", release: "cart@1.0.0" }];
    case "gallery":
      return [{ dsn: "__GALLERY_DSN__", release: "gallery@1.2.0" }];
    default:
  }
  return [];
}

init({
  dsn: "__FALLBACK_DSN__",
  // 我们可以通过 transport 自定义多虑上传, 将我们前面自定义的数据动态传递给其他sentry项目
  transport: makeMultiplexedTransport(makeFetchTransport, dsnFromFeature),
});

captureException(new Error("oh no!"), (scope) => {
  scope.setTag("feature", "cart");
  return scope;
});

微前端配置

那我们接下来看下成果吧

主应用:

js 复制代码
// webpack.config.js
const { sentryWebpackPlugin } = require("@sentry/webpack-plugin");

sentryWebpackPlugin({
  url: sourceMappingURL, // 上传 sourcemap 的cdn地址
  org: "组织名称",
  project: projectName, // 项目名称
  authToken: process.env.SENTRY_AUTH_TOKEN, // 用户的授权token
  // TODO: 主应用必须要有 不然子应用的 _experiments 不能触发拿到
  _experiments: {
    moduleMetadata: ({ org, project, release }) => {
      // 这里参数可以随便写
      return { team: projectName, release };
    },
  },
});
js 复制代码
const EXTRA_KEY = "ROUTE_TO"; // 这个可以写死 也就是在当前js使用 用于 beforeSend 和 transport 传递值

// 自定义多路上传
const transport = makeMultiplexedTransport(
  Sentry.makeFetchTransport,
  (args) => {
    const event = args.getEvent();
    if (
      event &&
      event.extra &&
      EXTRA_KEY in event.extra &&
      Array.isArray(event.extra[EXTRA_KEY])
    ) {
      // 这里返回的就是子应用配置的 dsn和release 用于将错误上传到子应用的sentry
      return event.extra[EXTRA_KEY];
    }
    return [];
  }
);

Sentry.init({
  dsn: "", // 填写 Client Keys DSN
  sampleRate: isDev ? 1 : 0.5,
  tracesSampleRate: 0.1,
  release: packageInfo.version || "last",
  integrations: [new ModuleMetadata()],
  transport, // 自定义上传策略
  beforeSend: (event: any) => {
    if (event?.exception?.values?.[0].stacktrace.frames) {
      const frames: any[] = event.exception.values[0].stacktrace.frames;
      // TODO 官方demo有误请使用如下方案
      // Find the last frame with module metadata containing a DSN
      let routeToList = frames
        .filter((frame) => frame.module_metadata && frame.module_metadata.dsn)
        .map((v) => v.module_metadata);

      if (routeToList.length) {
        event.extra = {
          ...event.extra,
          [EXTRA_KEY]: routeToList,
        };
      }
    }

    return event;
  },
});

注意:

  • 主应用初始化 @sentry/webpack-plugin 的时候一定要带上 _experiments.moduleMetadata, 不然就识别不到子应用的数据, 也就拿不到 routeToList 做多路上传了
  • 官方的 demo 针对 frame.module_metadata 的处理有误, 我已经提了 pr, 我这边已经改过了.

子应用

ts 复制代码
// webpack.config.js
const { sentryWebpackPlugin } = require("@sentry/webpack-plugin");

sentryWebpackPlugin({
  url: process.env.REACT_APP_SOURCE_MAPPING_URL, // 上传 sourcemap 的地址
  org: "xmly", // 组织名
  project: projectName, // package.json 的name
  authToken: process.env.SENTRY_AUTH_TOKEN, // 用户的授权token
  _experiments: {
    moduleMetadata: ({ org, project, release }) => {
      return {
        team: projectName,
        // dsn 和release是必填的 其他参数都无所谓
        dsn: 子应用的sentry dsn,
        release,
      };
    },
  },
});

子应用初始化 dsn 和 release 是必填的 其他参数都无所谓

总结

我们看下主应用的报错和子应用的报错 sentry 如何展示的

主应用是 SETTLEMENT-PLATFORM-ADMIN, 子应用是 BALANCE-SERVICE-MANAGE

主应用的sentry报错收集

子应用的sentry报错收集

我们看上面的截图可以看到, 两个不同的 sentry 项目, 报错的地址是一个项目, 至此, 我们实现了微前端项目中配置 sentry 的功能, 助力我们更加精确的排查前端项目报错

相关推荐
我是ed2 分钟前
# thingjs 基础案例整理
前端
Ashore_9 分钟前
从简单封装到数据响应:Vue如何引领开发新模式❓❗️
前端·vue.js
落魄实习生11 分钟前
小米路由器开启SSH,配置阿里云ddns,开启外网访问SSH和WEB管理界面
前端·阿里云·ssh
bug丸19 分钟前
v8引擎垃圾回收
前端·javascript·垃圾回收
安全小王子20 分钟前
攻防世界web第三题file_include
前端
&活在当下&21 分钟前
ref 和 reactive 的用法和区别
前端·javascript·vue.js
百事老饼干25 分钟前
VUE前端实现防抖节流 Lodash
前端
web Rookie29 分钟前
React 高阶组件(HOC)
前端·javascript·react.js
云白冰42 分钟前
hiprint结合vue2项目实现静默打印详细使用步骤
前端·javascript·vue.js
葡萄架子1 小时前
Python中的logger作用(from loguru import logger)
java·前端·python