react 错误监控 概览

3,024 阅读4分钟

前言

看着一封又一封错误监控邮件发到自己的邮箱,揉了揉自己布满血丝的双眼,想着许久没有吃过深海鳕鱼片,心里默默的叹了一口气,这一切要从接入错误监控的那天说起...

React 错误边界(Error Boundaries)

参考文档:错误边界

写代码总有发生错误的时候,错误边界(Error Boundaries)是一种React组件,这种组件可以捕获并打印发生在其子组件数任何位置的JavaScript错误,并且,他会渲染出备用UI,而不是渲染那些崩溃了的子组件树。错误边界在渲染期间、生命周期方法和整个组件树的构造函数中捕获错误。

如果一个 class 组件中定义了 static getDerivedStateFromError() componentDidCatch() 这两个生命周期方法中的任意一个(或两个)时,那么它就变成一个错误边界。当抛出错误后,请使用static getDerivedStateFromError()渲染备用UI,使用 componentDidCatch() 打印错误信息。

class ErrorBoundary extends React.Component {
    constructor(props) {
        super(props)
        this.state = {hasError: false}
    }
    static getDerivedStateFromError(error) {
        // 更新 state 使下一次渲染能能够显示降级后的 UI
        return { hasError: true }
    }
    componentDidCatch(error, errorInfo) {
        // 我们可以在这里将错误日志上报给服务器
        logErrorToMyService(error, errorInfo)
    }
    render() {
        if (this.state.hasError) {
            // 你可以自定义降级后的UI并渲染
            return <h1>Something went wrong.</h1>;
        }
        // 如果没有发生错误则直接将子组件返回
        retrun this.props.children
    }
}

然后我们可以将它作为一个常规组件去使用:

<ErrorBoundary>
  <MyWidget />
</ErrorBoundary>

错误边界的工作方式类似于JavaScript catch {},不同的地方在于错误边界只针对React组件。只有 class 组件才可以成为错误边界组件。大多数情况下, 你只需要声明一次错误边界组件, 并在整个应用中使用它。

注意错误边界仅可以捕获其子组件的错误,它无法捕获其自身的错误。如果一个错误边界无法渲染错误信息,则错误会冒泡至最近的上层错误边界,这也类似于 JavaScriptcatch {} 的工作机制。

错误上报

知道了在react中如何捕获错误,我们还有个重要的任务就是把错误上传给服务端,这样我们才能第一时间知道错误,发生错误时被邮件和短信狂轰乱炸,迅速修复问题。

错误监控系统千千万,我们需要一个成熟的监控系统,Sentry【哨兵】就是这样的一个工具。Sentry可以实时监控生产环境上的系统运行状态,一旦发生异常会第一时间把报错的路由路径、错误所在文件等详细信息以邮件形式通知我们,并且利用错误信息的堆栈跟踪快速定位到需要处理的问题。

Sentry官网

Sentry的优势有以下几点:

  • 开源
  • 对各种前端框架支持比较友好(React、Vue、Angular
  • 支持SourceMap

我们可以利用 Sentry 的开源库在自己的服务器上搭建服务,官方已经提供了完善的操作文档。

参考文档:zhuanlan.zhihu.com/p/51446011

前端接入sentry共如下三步:

  1. 配置wepack
  2. sentryjs文件引入到html
  3. ErrorBoundary中调用Sentry相关的方法

1. 配置webpack

使用HtmlWebpackPlugin将环境变量注入到生成的html中(变量包括:sentryEnvironment 当前环境packageVersion 项目版本号sentryDsnUrl 接入的url)等

// wepback.base.conf.js

const {
  CI_BUILD_TAG, // 打包tag
  npm_package_version, // 版本号
  analyzer,
} = process.env
// 通过打tag时的环境,获取到当前的打包环境(生产环境pro、测试环境test)
const [currentEnv] = (CI_BUILD_TAG || '').split('-')

// 使用HtmlWebpackPlugin将环境变量注入到生成的html中(变量包括:sentryEnvironment、packageVersion、sentryDsnUrl)
new HtmlWebpackPlugin({
      // 自定义 option 在 html 中 使用 <%= htmlWebpackPlugin.options.external %> 调用
      external: {
        // 环境变量,用以区分Sentry
        sentryEnvironment: NODE_ENV === 'development' ? 'local' : currentEnv,
        // 版本号
        packageVersion:
          NODE_ENV === 'development' ? '0.0.0' : npm_package_version,
        // 上线地址区分,线上环境使用一个独立项目接收
        sentryDsnUrl:
          currentEnv === 'pro'
            ? '//xxx.com'
            : '//xxx.com',
      },
    })

2. 将sentry的js文件引入到html中

通过htmlWebpackPlugin.options.external.的方式将变量插入到生成的html

<%if ( htmlWebpackPlugin.options.external.sentryEnvironment !== 'local' ) {%>
    <script
      src="https://browser.sentry-cdn.com/5.xx.x/bundle.min.js"
      integrity="xxx"
      crossorigin="anonymous"
    ></script>
    <script>
      if (window.Sentry) {
        Sentry.init({
          environment:
            '<%= htmlWebpackPlugin.options.external.sentryEnvironment %>',
          release: '<%= htmlWebpackPlugin.options.external.packageVersion %>',
          dsn:
            window.location.protocol +
            '<%= htmlWebpackPlugin.options.external.sentryDsnUrl %>',
        })
      }
    </script>
    <% } %>

相关文档: Subresource Integrity

sentry integrating-the-sdk

3. 在ErrorBoundary中调用Sentry相关的方法

componentDidCatch中调用Sentry的方法,将错误发送给服务端,这样就可以收到邮件和短信啦~

componentDidCatch(error: any, info: any) {
    this.setState({
      hasError: true,
    })

    // 调用sentry将错误上传
    if (window.Sentry) {
      window.Sentry.captureException(error, { extra: info })
    }

  }

hook

参考文档:Hook 能否覆盖 class 的所有使用场景?

Hook 设定的目标是尽早覆盖class的所有使用场景。目前暂时还没有对应不常用的getSnapshotBeforeUpdategetDerivedStateFromErrorcomponentDidCatch 生命周期的hook等价写法。

目前 Hook 还处于早期阶段,一些第三方的库可能还暂时无法兼容 Hook

总结

错误监控的方式千千万,如果有更好的做法,欢迎互相交流~~~

不说了,改bug去了...