前言
看着一封又一封错误监控邮件发到自己的邮箱,揉了揉自己布满血丝的双眼,想着许久没有吃过深海鳕鱼片,心里默默的叹了一口气,这一切要从接入错误监控的那天说起...
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 组件才可以成为错误边界组件。大多数情况下, 你只需要声明一次错误边界组件, 并在整个应用中使用它。
注意错误边界仅可以捕获其子组件的错误,它无法捕获其自身的错误。如果一个错误边界无法渲染错误信息,则错误会冒泡至最近的上层错误边界,这也类似于 JavaScript 中 catch {} 的工作机制。
错误上报
知道了在react中如何捕获错误,我们还有个重要的任务就是把错误上传给服务端,这样我们才能第一时间知道错误,发生错误时被邮件和短信狂轰乱炸,迅速修复问题。
错误监控系统千千万,我们需要一个成熟的监控系统,Sentry【哨兵】就是这样的一个工具。Sentry可以实时监控生产环境上的系统运行状态,一旦发生异常会第一时间把报错的路由路径、错误所在文件等详细信息以邮件形式通知我们,并且利用错误信息的堆栈跟踪快速定位到需要处理的问题。
Sentry的优势有以下几点:
- 开源
- 对各种前端框架支持比较友好(
React、Vue、Angular) - 支持
SourceMap
我们可以利用 Sentry 的开源库在自己的服务器上搭建服务,官方已经提供了完善的操作文档。
参考文档:zhuanlan.zhihu.com/p/51446011
前端接入sentry共如下三步:
- 配置
wepack - 将
sentry的js文件引入到html中 - 在
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
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的所有使用场景。目前暂时还没有对应不常用的getSnapshotBeforeUpdate,getDerivedStateFromError 和 componentDidCatch 生命周期的hook等价写法。
目前 Hook 还处于早期阶段,一些第三方的库可能还暂时无法兼容 Hook。
总结
错误监控的方式千千万,如果有更好的做法,欢迎互相交流~~~
不说了,改bug去了...