04 Umi4 插件开发 - 网友需求 - 前端数据埋点 Sentry 1/2

3,281 阅读7分钟

携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第5天,点击查看活动详情

0401.png

现在的前端埋点方案,大同小异,好一点的平台会提供 JSSDK 供用户直接调用和接入,差一点的平台会让用户直接调用接口。 但是其实原理上都差不多。这里我们不做深入的讲解,如果有朋友感兴趣的话,可以给我留言,我会专门写个文章给大家讲解。

为了看文章的朋友都能自己动手实践一遍,我们选一个免费的数据埋点平台 Sentry 作为演示。

Sentry 是一个服务,帮助你监测和修复在实时崩溃。服务器是 Python 的,但是它包含一个完整的 API,用于在任何应用程序中从任何语言发送事件。 为了简化和聚焦这篇文章的核心,我们只关注 Sentry 提供的 React 对接方案,有其他需求的朋友,可以自己浏览 Sentry 官网。

根据官网指引,我们先在业务代码中接入 Sentry SDK。

注册账号

根据官网指引在注册页面注册一个账号。

0402.jpg

下一步选择 Install Sentry

0403.jpg

下一步选择创建一个 REACT 项目,点击 Create Project

0404.jpg

安装依赖

官网文章如下:

# Using yarn
yarn add @sentry/react @sentry/tracing

# Using npm
npm install --save @sentry/react @sentry/tracing

为了简化前置步骤说明,我们从上一个文章的源码归档 learn-for-umi-plugin@0.0.3 继续。

因为我们的项目用的是 pnpm ,所以我们应该在项目中执行。

pnpm i @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 注意换成你自己的,直接复制页面上的代码就是正确的
  dsn: "https://96620dadccb040a3a5acc998a6078212@o1378593.ingest.sentry.io/6690607",
  integrations: [new BrowserTracing()],

  // Set tracesSampleRate to 1.0 to capture 100%
  // of transactions for performance monitoring.
  // We recommend adjusting this value in production
  tracesSampleRate: 1.0,
});

ReactDOM.render(<App />, document.getElementById("root"));

// Can also use with React Concurrent Mode
// ReactDOM.createRoot(document.getElementById('root')).render(<App />);

从文档上我们能够看出,他的期望是在 render(<App />) 之前执行 Sentry.init,如果是 create-react-app 项目的话,你可以在 src/index 之类的文件中粘贴一样的代码,但是在 umi 中,这个入口文件是一个临时文件,是当你执行 dev 或者 build 之后才会生成的,就在 src/.umi/umi.ts。这个文件也是构建时候的入口文件,你可以在这个文件中找到 renderClient 方法的调用,最终在 renderClient 中调用了 ReactDOM.createRoot。这在你需要做“入口追踪游戏”的时候,是一个需要知道的常识。

当然了这个文件是临时生成的,意味着你在这里面编写的所有的内容,都会被抛弃,因此我们无法将 Sentry.init 写在这里面。

还好 Umi 提供了一个 global 文件,它也是在较靠前的执行的,因此我们可以把 Sentry.init 写在 global 中。

新建 src/global.ts 文件

import * as Sentry from "@sentry/react";
import { BrowserTracing } from "@sentry/tracing";

Sentry.init({
  // 此处 dsn 注意换成你自己的,直接复制页面上的代码就是正确的
  dsn: "https://acb9e155e5de4ee0a418e798570feae1@o652357.ingest.sentry.io/6683799",
  integrations: [new BrowserTracing()],

  // We recommend adjusting this value in production, or using tracesSampler
  // for finer control
  tracesSampleRate: 1.0,
});

src/pages/index.tsx 中创造一个错误

Sentry 官网文档是让我们写一个 button 调用一个不存在的函数 methodDoesNotExist ,但是 umi 在编译阶段就会检测这种错误,会导致 umi 启动不起来。

0405.jpg

因此我们可以像下面这么写,在点击时执行一个不存在的对象。

import React from 'react';
import styles from './index.less';

export default function Page() {
  return (
    <div>
      <h1 className={styles.title} onClick={()=>{
        // @ts-ignore
        methodDoesNotExist
      }}>Page index</h1>
    </div>
  );
}

当我们运行 npx umi dev,在页面中点击 Page index 的时候,我们就会发送一个错误,此时我们停留的 Sentry 官网会自动跳转到监控页面。

0406.jpg

你可以点击进入查看任意错误的详情信息这里面包含了,用户用的系统版本号浏览器版本号还有执行了什么操作。比如:

0407.jpg

从上面的信息你很容易就能看到,用户在 http://localhost:8000/ 点击了 div#root > div > div > div > h1.title___dUjAT

当然如果只是添加一个接入,那没什么必要将它写成插件,我们可以继续浏览 Sentry React 的文档,加入一些其他的我们想要的功能。

React Error Boundary

给项目加上 Error Boundary 可以在程序发生异常的时候,捕获错误。

依旧我们从官网文档中获取使用方法:

import React from "react";
import * as Sentry from "@sentry/react";

<Sentry.ErrorBoundary fallback={<p>An error has occurred</p>}>
  <Example />
</Sentry.ErrorBoundary>;

只需要用 Sentry.ErrorBoundary 包裹 RootContainer 就可以实现,当错误被捕获时间,就会渲染 fallback 提供的 UI。

巧了,刚好 umi 就有一个运行时配置 rootContainer,它提供了一个口子,让我们能够在外层包裹一个 Provider。

运行时配置 rootContainer

新建 src/app.tsx,编写如下代码:

import React from "react";
import { ErrorBoundary } from "@sentry/react";

export function rootContainer(container: JSX.Element) {
  const props = {
    fallback: () => <p>An error has occurred</p>,
  };
  return React.createElement(ErrorBoundary, props, container);
}

值得注意的是 ErrorBoundary 只捕获 React 渲染错误,不捕获语法错误。

我们可以继续在 src/pages/index.tsx 中创造一个渲染错误。

import React, { useState } from "react";
import styles from "./index.less";

export default function Page() {
  const [count, setCount] = useState(0);
  return (
    <div>
      {count}
      <h1
        className={styles.title}
        onClick={() => {
          // 这里故意设置了一个错误,会触发运行时配置
          // @ts-ignore
          setCount({ a: 123 });
        }}
      >
        Page index
      </h1>
    </div>
  );
}

关键出错代码在 {count} 因为将 count 设置成对象 { a: 123 },保存代码之后,再次点击 Page index 的时候,就可以在页面上看到 An error has occurred。 说明我们的错误已经被正确捕获了。

重置错误 resetError

当然了,这样虽然捕获了错误,但我们也看不到错误内容了,因此我们可以修改一个 fallback,它可以接收一个函数,并且会传入 error, componentStack, resetError

import { ErrorBoundary, ErrorBoundaryProps } from "@sentry/react";

  const props = {
    fallback: ({ error, componentStack, resetError }) =>
      (
        <React.Fragment>
          <div>You have encountered an error</div>
          <div>{error.toString()}</div>
          <div>{componentStack}</div>
          <button
            onClick={() => {
              resetError();
            }}
          >
            Click here to reset!
          </button>
        </React.Fragment>
      ) as React.ReactNode,
  } as ErrorBoundaryProps;

再次点击触发错误,将会看到更详细的错误信息,并且点击 Click here to reset! 按钮可以将页面重置到错误发生之前。

0408.jpg

用户反馈

0409.jpg

当触发错误时,会有如上所示弹窗,供用户主动反馈信息,提交成功之后,可以在后台查看用户提交数据。

0410.jpg

实现也很简单,只需要将 ErrorBoundary 的 showDialog 属性设置为 true

import React from "react";
import { ErrorBoundary, ErrorBoundaryProps } from "@sentry/react";

export function rootContainer(container: JSX.Element) {
  const props = {
    showDialog: true,
    // fallback React.ReactNode,
  } as ErrorBoundaryProps;
  return React.createElement(ErrorBoundary, props, container);
}

设置用户

比如在用户登陆授权之后,全程跟踪用户发生的错误。

Sentry.setUser({ id: '123', username: 'xiaohuoni', email: '' });

设置之后用户再次触发错误,就可以从后台看到用户信息。

0411.jpg

Sentry.setUser 你可以在任意时候调用,也可以在不需要的时候清除当前设置的用户:

Sentry.configureScope((scope) => scope.setUser(null));

源码归档 learn-for-umi-plugin@0.0.4

总结

以上是我个人觉得比较有用和有趣的功能,你也可以再找一些你想要的功能,根据官网的指引添加到项目中。 这篇文章的内容写的非常细,是希望在开始编写插件之前,能够更准确的理解需求。我们会在下一篇文章中,将上面的功能编写成插件。

感谢阅读,如果你对这个内容感兴趣,可以关注这个专栏:Umi 插件开发。如果你觉得这个文章对你有帮助,请点赞评论收藏支持我,并将这个文章分享给更多的朋友,文章的数据是我持续更新的动力。感谢。