ReactNative错误异常收集和处理

ReactNative

在生产环境下,因RN页面异常导致整个APP白屏,闪退,或者是卡顿,用户体验并不好,所以应该对异常进行捕获并处理,提高用户体验。

需求:

对于闪退,白屏,卡死等APP异常表象,在日志中必须能够详细输出:报错时间,错误位置(所在代码文件和出错行),错误描述,相关堆栈,如果是rn框架和组件报错,应能通过堆栈看到具体调用顺序,相关出错的一系列文件及代码行

解决方法:

1. React Error Boundaries (异常边界组件)

React官方文档:https://react.docschina.org/docs/error-boundaries.html
React 16 引入了一个新的概念 —— 错误边界。

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

注意:

错误边界无法捕获以下场景中产生的错误:
事件处理(了解更多)
异步代码(例如 setTimeout 或 requestAnimationFrame 回调函数)
服务端渲染
它自身抛出来的错误(并非它的子组件)

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
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>;
}

return this.props.children;
}
}

可行性:我们的React版本>16。所以可以使用。
总结:主要是捕获render过程中的一些错误,就是写componentDidCatch方法,然后捕获生命周期中的异常,

2. React Native自带的 ErrorUtils 模块

使用 React Native ErrorUtils 模块对错误边界无法捕获的场景中对这些异常进行捕获并处理。
React Native ErrorUtils 是负责对RN页面中异常进行管理的模块,功能很类似Web页面中的 window.onerror

首先我们看看怎么利用 React Native ErrorUtils 进行异步捕获和处理,存在javascriptCore,所以官方提供了方法:global.ErrorUtils.setGlobalHandler来监听全局的错误,直接上代码:

1
2
3
4
global.ErrorUtils.setGlobalHandler(error => {
console.log('ErrorUtils发现了语法错误,避免了崩溃,具体报错信息:');
console.log(error.name, error.message, [{ text: 'OK' }]);
}, true);

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

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

项目中已经集成了。

3. 逻辑崩溃处理react-native-exception-handler也可以统一处理报错,另外它也可以处理Native端的报错。

https://github.com/master-atul/react-native-exception-handler

使用:

4. redux-logger 使用

安装
npm install redux-logger --save-dev

redux-logger 仅且在开发模式下使用,所以只需增加参数--save-dev.

--save-dev 开发模式下使用的插件
--save 生成环境下使用的插件

-g 将插件安装成全局插件,该电脑的所有项目都可以使用。无-g参数时,仅且该项目可以使用改插件。
使用
API定义:createLogger(options?:Object)=>LoggerMiddleware;
JS新语法说明:
options?:Object 是类型定义,表明options参数的类型是Object类型。
“?”号表示非必须参数。
options包含的属性:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
{
//日志等级,可以是以下字符串,也可以是返回以下字符串的function.
level = 'log': 'log' | 'console' | 'warn' | 'error' | 'info', // console's level

//执行持续时间
duration = false: Boolean, // Print the duration of each action?

//执行时间
timestamp = true: Boolean, // Print the timestamp with each action?

//日志颜色
colors: ColorsObject, // Object with color getters. See the ColorsObject interface.

//自定义一个日志输出函数,相当于再次封装console类
logger = console: LoggerObject, // Implementation of the `console` API.

logErrors = true: Boolean, // Should the logger catch, log, and re-throw errors?

collapsed, // Takes a boolean or optionally a function that receives `getState` function for accessing current store state and `action` object as parameters. Returns `true` if the log group should be collapsed, `false` otherwise.

predicate, // If specified this function will be called before each action is processed with this middleware.

stateTransformer, // Transform state before print. Eg. convert Immutable object to plain JSON.

actionTransformer, // Transform state before print. Eg. convert Immutable object to plain JSON.

errorTransformer // Transform state before print. Eg. convert Immutable object to plain JSON.
}

注意事项
logger 中间件必须放在所有中间件的最后,不然会出现部分action无法打印日志的情况。 使用如下:

var logger = createLogger({ predicate: (getState, action) => isDebuggingInChrome, collapsed: true, duration: true, });

var createF8Store = applyMiddleware(thunk, promise, array, analytics, logger)(createStore);

相关资料:

React Native 异常处理 :https://github.com/HuJiaoHJ/blog/issues/13

RN-逻辑崩溃处理:react-native-exception-handler:https://www.jianshu.com/p/c46e06b4b73e

总结:
以上可以一起使用,通过异常边界组件监控每个组件的生命周期,通过ErrorUtils 模块来监控事件处理等其他全局异常,并把报错发送给原生端处理。并有错误的显示页面,类似404 的那种,防止APP直接crash,或者白屏。

5.打印出错的行号和文件

我们知道在开发者模式下的时候,会出现红屏,那个红屏会闪一下,然后出现报错信息和报错的行号和方法。
可以在RN源码看到:
文件夹位置
/node_modules/react-native/Libraries/Core/InitializeCore.js中可以看到:

打印错误日记的方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// Set up console
const ExceptionsManager = require('ExceptionsManager');
ExceptionsManager.installConsoleErrorReporter();

// Set up error handler
if (!global.__fbDisableExceptionsManager) {
const handleError = (e, isFatal) => {
try {
ExceptionsManager.handleException(e, isFatal);
} catch (ee) {
/* eslint-disable no-console */
console.log('Failed to print error: ', ee.message);
/* eslint-enable no-console */
throw e;
}
};

const ErrorUtils = require('ErrorUtils');
ErrorUtils.setGlobalHandler(handleError);
}

在/node_modules/react-native/Libraries/Core/ExceptionsManager.js中:这这个方法中可以看到,在开发者模式下,它会解析那个错误栈,然后追溯那个错误栈,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
function reportException(e: ExtendedError, isFatal: bool) {
const {ExceptionsManager} = require('NativeModules');
if (ExceptionsManager) {
const parseErrorStack = require('parseErrorStack');
const stack = parseErrorStack(e);
const currentExceptionID = ++exceptionID;
if (isFatal) {
ExceptionsManager.reportFatalException(e.message, stack, currentExceptionID);
} else {
ExceptionsManager.reportSoftException(e.message, stack, currentExceptionID);
}
if (__DEV__) {
const symbolicateStackTrace = require('symbolicateStackTrace');
symbolicateStackTrace(stack).then(
(prettyStack) => {
if (prettyStack) {
ExceptionsManager.updateExceptionMessage(e.message, prettyStack, currentExceptionID);
} else {
throw new Error('The stack is null');
}
}
).catch(
(error) => console.warn('Unable to symbolicate stack trace: ' + error.message)
);
}
}
}

可以借鉴这个源码的方案来写一下这个打印出行号和文件名还有方法的一个方法,写在index.js或者是index.android.js或者是index.ios.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
ErrorUtils.setGlobalHandler((e) => { 
const parseErrorStack = require('parseErrorStack');
const stack = parseErrorStack(e);
const symbolicateStackTrace = require('symbolicateStackTrace');
symbolicateStackTrace(stack).then(
(prettyStack) => {
if (prettyStack) {
const errorString = '\nfile:'+prettyStack[0].file + '\nmethodName:'+prettyStack[0].methodName+'\nlineNumber:'+prettyStack[0].lineNumber+'\nerrorInfo:'+e.toString();
WebSDK.recordJsLog({
jsLog: errorString,
logMode:'ERROR'})
} else {
throw new Error('The stack is null');
}
}
).catch(
(error) => console.warn('Unable to symbolicate stack trace: ' + error.message)
);
});

在捕获了异常以后,就可以把这个错误信息报给原生,或者服务端。

在iOS下的RN源码也可以看到类似的信息:

RCTRedBox

RCTErrorInfo

RCTJSStackFrame

Release状态下统计日志

Bugly是腾讯提供的专业Crash、Android ANR、ios卡顿解决方案,24小时实时监控和告警,可以及时发现异常问题。

  1. ReactNative 不同的编译模式,使用的 JS 来源不同。Debug 模式来自 Packager Server,而 Release 模式,来自 Apk 的 assets 目录。
  2. Debug 模式下的崩溃,会触发红屏,而 Release 模式下的崩溃,会直接导致 App 崩溃。
  3. Debug 模式,之所以可以显示崩溃栈的基本信息,是因为编译的 JS 文件中,包含了对应的源文件和代码行号。而这些在 Release 模式下的 JS 是没有的。
  4. Release 模式的崩溃栈是被混淆后的,可以通过崩溃栈显示的行号和列号,来定位代码,但是无法定位具体源文件。
  5. 通过 react-native 命名,增加 –sourcemap-output参数,指定输出需要的混淆 Mapping 文件,它其内包含了混淆的信息。
  6. 解读 ReactNative Mapping 文件,可以使用 source-map 这个 NodeJs 库来进行解析,可以精准定位到行号和源文件名。

打包的同时打出map的包:

1
react-native bundle --platform android --dev false --entry-file index.js --bundle-output android/app/src/main/assets/index.android.bundle --assets-dest android/app/src/main/res/ --sourcemap-output android-release.bundle.map

在release模式下,通过集成bugly,在bugly平台上收集错误信息,定位到某行某列。然后运行下面的map.JS文件。替换console.log里面的行和列。

1
2
3
4
5
6
7
8
9
10
11
var sourceMap = require('source-map');
var fs = require('fs');

fs.readFile('../android-release.bundle.map', 'utf8', function (err, data) {
var smc = new sourceMap.SourceMapConsumer(data);

console.log(smc.originalPositionFor({
line: 344, //报错的行
column: 868 //报错的列
}));
});

在终端执行:node map.js
即可看到打印:文件名,行号等信息

https://bugly.qq.com/docs/introduction/bugly-introduction/?v=20180709165613

缺点:使用的是node的sourceMap来解析文件,只能做到手动来统计错误日志,不能做到自动化来统计日志并且把错误上报到自己的服务器上。

其实不集成bugly也可以
参考:

关于App异常监控那点事:https://www.jianshu.com/p/9aba5572ddcb

https://blog.csdn.net/luolin2611/article/details/81110718

文章目录
  1. 1. 1. React Error Boundaries (异常边界组件)
  2. 2. 2. React Native自带的 ErrorUtils 模块
  3. 3. 3. 逻辑崩溃处理react-native-exception-handler也可以统一处理报错,另外它也可以处理Native端的报错。
  4. 4. 4. redux-logger 使用
  5. 5. 5.打印出错的行号和文件
    1. 5.0.1. RCTRedBox
    2. 5.0.2. RCTErrorInfo
    3. 5.0.3. RCTJSStackFrame
  • 6. Release状态下统计日志
  • 本站总访问量 本站访客数人次 ,