RN 线上错误搜集
最近项目组收到用户反馈无法进入收到一个 RN 页面,怀疑是客户端加载页面时发生异常,但也不排除是前端代码异常导致的。
为此打算接入早先别组同学搭建的 Sentry,以便于观测与搜集前端异常。不过现有的 sdk 是基于 h5 与 node 的,rn 这方面没有对应的 sdk。sentry 是有开源的 rn sdk,但由于项目特殊性,与客户端耦合较多,因此简单自行封装一个 sdk 作为过渡,等后续负责 sentry 的同学提供统一的 RN sdk 再替换掉,这里记录一下封装的思路。
原理
公司的 sentry 项目不是我这里负责的,不过大概的原理在封装 sdk 的过程中也猜的差不多了。
- client 捕获到异常
- client 通过请求 / 图片将异常信息发送到日记统计平台
- 定时从日志平台拉取 sentry 异常相关的日志
- 将日志整理成 sentry 识别的字段,存入数据库
- 用户在 sentry 的平台查看 issue 即可
过程
提供一个 HOC 用于捕获未处理的异常,就叫 withErrorBoundary 吧,这个与 React 捕获异常其实都差不多
import React from 'react'; import { report } from '@hooks/useSentry'; /** 捕获页面异常并上报到 sentry */ class ErrorBoundary extends React.Component { state: { error?: any } = {}; static getDerivedStateFromError(error: any) { return { error }; } componentDidCatch(error: any) { report(error); } render() { return this.state.error ? ( <div>oops!</div> ) : ( this.props.children ); } } export const withErrorBoundary =(WrappedComponent) => (props) => (<ErrorBoundary {...props}> <WrappedComponent {...props} /> </ErrorBoundary>) export default ErrorBoundary;给需要的页面包裹上
withErrorBoundary(Page)即可实现 report,实际上 report 无非就是将信息整理成需要的格式,然后发送到日志平台。
那么需要的信息包括什么呢?
首先肯定是错误栈,这里通过 ErrorBoundary 里的 error 能获取 error.message 以及错误栈
{ column: 2410, line: 1100, componentStack: in p // ... isComponentError: True, sourceURL: ... }其次是错误发生的时间 / 版本信息 / 机型 等辅助信息
最后是业务相关的信息,通常包括 用户 / 页面路径
sentry 管这个叫 event ,具体的参数可以参考sentry 的开发文档。
那么当我们整理好所需的信息就需要上报上去,顺带说一下前端常用的手段是用 1x1 像素的透明 gif 图片,也叫 image beacon。通过 new Image 构造一个图片请求,主要原因是
1. 对比 ajax,不会有跨域问题 1. 对比 js 标签,不阻塞页面加载 1. 对比 png / jpg,这种图片体积最小source map,线上的代码都是压缩过的,报错的信息就很模糊,这个时候我们需要借助 source map 来还原源码的报错位置。rn 也是有提供 source map 的,在执行 react-native bundle 的时候添加参数
--bundle-output [sourcemap-name].map即可输出对应平台的 source map,通过下述代码即可定位源码报错位置const sourceMap = require('source-map') const fs = require('fs-extra') // 读取 source map 为 json const rawSourceMapJsonData = await fs.readJson('[sourcemap-name].map') const consumer = await new sourceMap.SourceMapConsumer(rawSourceMapJsonData); // line 与 column 指压缩后的报错位置 console.log(consumer.originalPositionFor({ line, column }));当然 sentry 也提供了 source map 的配置,这块还没尝试,可自行查看文档 https://docs.sentry.io/platforms/react-native/sourcemaps/
不过遗憾的是 component stack 似乎是没有办法还原出报错所在组件的位置,参考 https://github.com/facebook/create-react-app/issues/3753