本博客讲讲App异常监控,每个app都要保证使用质量,这样才能保住用户量,所以对于应用程序的监控显得尤为重要。想象一下,当用户向你抛出一个bug(或者说异常),而你却找不到异常出现的原因和时机,也很难去重现这种奇葩的事件,此时你有一种众里寻他千百度,那bug却不知在何处的感觉。所以,利用某种工具去实现App的异常监控,方便我们及时查看结果,并且作出合理的处理,这对于那些有产品质量追求的人,是挺重要的。

首先,我们来理解几个概念:

1、异常与捕获

  • 异常指的是在程序运行过程中发生的异常事件,通常是由外部问题(如硬件错误、输入错误)所导致的。在Java等面向对象的编程语言中异常属于对象。
  • 异常(Exception)都是运行时的。编译时产生的不是异常,而是错误(Error)。需要注意的是,程序设计导致的错误不属于异常(Exception)。
  • 总结:
    Error:系统内部错误,这类错误由系统进行处理,程序本身无需捕获处理;
    Exception:可以处理的异常
    Read More:https://www.zhihu.com/question/36278363
  • 捕获:获取异常
    通过try…catch语句进行捕获异常。
    通过throw抛出异常,throws向上一级调用方法抛出异常。

2、DSN(数据源名称)

  • DSN:即Data Source Name
  • Sentry官网是如此描述DSN的:
    完成Sentry项目的设置后,将给您指定一个值,我们称之为DSN或数据源名称。它看起来像标准的URL,但实际上只是Sentry SDKS所需要的配置的表示。它由几个部分组成,包括协议、公共密钥和秘密密钥、服务器地址和项目标识符。
    如下图所示:

DSN.png

可以在你所在的项目中查看你的Client Keys,它将集成到你的客户端代码中:

异常自动恢复执行情况监控 异常行为监控_官网

Client Keys.png

相关链接:https://docs.sentry.io/quickstart/

一、为什么要集成异常监控?

即使我们对上线的项目做了大量的测试,但有时候还是会有潜在的bug,这种比较顽固的问题只能通过监控机制才能有效的减少其带来的损失,所以异常捕获和上报很重要。
如果你是一个开发者,你的开发流程一般是这样子的:
Design → Code & Review → Testing/QA → CI/CD → Issue discovery → Investigation → Remediation
即:设计 → 编码&复查→ 测试/质量保证 → 持续集成/持续部署 → 问题发现 → 侦查→ 修复
现在的开发是趋向于越来越简单,因为有很多很好的开发工具和SDK,但是在问题发现、侦查和修复这几个过程仍旧是挺痛苦、烦人和漫长的。比如说,在生产环境中发现了一个bug,你如何及时得到通知?你如何估计其影响以及紧迫性?你如何快速找到问题的根源?当你修正这个漏洞时,你怎么知道是否解决了问题?
带着这些疑问,让我们来更进一步了解异常监控,它能给我们带来什么样的收益?而我们又需要付出多大的成本?

二、如何做到异常捕获?

关于异常捕获的原理,笔者第一次接触,不甚了解,这里有一个关于前端异常捕获的链接推荐你们参考,感觉写得还行(前端异常捕获与上报:)。
因为我这里只注重react native App相关的异常监控,所以就只分析Sentry是如何捕获到js的异常,然后将异常事件上报到服务器的。我是通过react-native-sentry的一些源码慢慢摸索出来的,至于理解的对不对,望大家一起探讨。
作为一个程序猿,大家都一定写过如下面一段代码:

try {
        throw new Error('自定义异常msg')
    }
    catch (e) {
        console.log('e==='+e)
    }

这是捕获异常的简单程序,它可以避免你的页面因为一个致命的异常而闪崩,而sentry主要是捕获到你未catch的程序异常,所以如果你用程序去catch了异常,sentry就没有将这个异常上报到服务器了。
接下来看react-native-sentry的源码,主要看以下几个:

 

异常自动恢复执行情况监控 异常行为监控_异常自动恢复执行情况监控_02

react-native-sentry.png

  • Sentry.d.ts:用typescript写的一个sentry相关的配置、注册方法等

Sentry.d.ts.png

  • Sentry.js:这一块是Sentry的主要代码,在这里它引入了两个原生模块RNSentry和RNSentryEventEmitter,可以查看原生的代码(注意这里是安卓端的,ios的请自行查看),如下图所示:

异常自动恢复执行情况监控 异常行为监控_官网_03


 

RNSentry和RNSentryEventEmitter都是原生封装模块供js端调用,RNSentry有一个捕获异常事件的方法,通过这个方法它将js端的事件的相关信息捕获到原生处理;而RNSentryEventEmitter是一个事件发射模块,用于将event进行发送,通知js端进行相应的操作。

@ReactMethod
public void captureEvent(ReadableMap event) {
    ReadableNativeMap castEvent = (ReadableNativeMap)event;

    EventBuilder eventBuilder = new EventBuilder()
            .withLevel(eventLevel(castEvent));

    if (event.hasKey("message")) {
        eventBuilder.withMessage(event.getString("message"));
    }

    if (event.hasKey("logger")) {
        eventBuilder.withLogger(event.getString("logger"));
    }

    if (event.hasKey("user")) {
        UserBuilder userBuilder = getUserBuilder(event.getMap("user"));
        User builtUser = userBuilder.build();
        if (builtUser.getId() != null) {
            UserInterface userInterface = new UserInterface(
                    builtUser.getId(),
                    builtUser.getUsername(),
                    null,
                    builtUser.getEmail(),
                    builtUser.getData()
            );
            eventBuilder.withSentryInterface(userInterface);
        }
    }

    if (castEvent.hasKey("extra")) {
        for (Map.Entry<String, Object> entry : castEvent.getMap("extra").toHashMap().entrySet()) {
            eventBuilder.withExtra(entry.getKey(), entry.getValue());
        }
    }

    if (castEvent.hasKey("tags")) {
        for (Map.Entry<String, Object> entry : castEvent.getMap("tags").toHashMap().entrySet()) {
            String tagValue = entry.getValue() != null ? entry.getValue().toString() : "INVALID_TAG";
            eventBuilder.withTag(entry.getKey(), tagValue);
        }
    }

    if (event.hasKey("exception")) {
        ReadableNativeArray exceptionValues = (ReadableNativeArray)event.getMap("exception").getArray("values");
        ReadableNativeMap exception = exceptionValues.getMap(0);
        ReadableNativeMap stacktrace = exception.getMap("stacktrace");
        ReadableNativeArray frames = (ReadableNativeArray)stacktrace.getArray("frames");
        if (exception.hasKey("value")) {
            addExceptionInterface(eventBuilder, exception.getString("type"), exception.getString("value"), frames);
        } else {
            // We use type/type here since this indicates an Unhandled Promise Rejection
            // https://github.com/getsentry/react-native-sentry/issues/353
            addExceptionInterface(eventBuilder, exception.getString("type"), exception.getString("type"), frames);
        }
    }

    Sentry.capture(buildEvent(eventBuilder));
}
  • RavenClient.js和raven-plugin.js:这两个主要是写有关捕获js端的异常
  • raven-plugin.js这个插件有什么用处呢?
    它会自动检查并捕获js端所触发的未catch的异常。

如果你感兴趣,还可以再看一些更底层的东西

 

异常自动恢复执行情况监控 异常行为监控_异常自动恢复执行情况监控_04

image.png

讲了这么多,简单画个流程图:

异常自动恢复执行情况监控 异常行为监控_App_05

流程图.png

三、选择哪个异常监控工具?

选择哪个异常监控工具其实也是蛮纠结的事,一个一个尝试太耗费时间了,个人觉得那些别人用的最多的工具可以尝试使用,只有好的东西才有多人使用。这里只列举一些异常监控的工具:

  • 国内的
  • (1)笔者之前集成过腾讯的bugly,每天都会收到一份崩溃报告邮件

bugly.png

但是感觉比较适合纯原生开发的应用,不太适合reactnative的应用,因为它报告的异常基本都是java端的代码,你很难定位到JavaScript端的代码异常。

  • (2)之前也集成过百度移动统计SDK,这种也是适合原生开发的应用,reactnative应用也是不推荐使用,而且百度产品的有关服务相当不给力,还不如去使用极光的相关产品。

baidu.png

最主要的一点是,从报告的异常来看,你也很难快速定位到错误的原因和位置。
这里我只举例之前用过的,其他的国内异常监控工具一定还有更好的。

  • 国外的
    国外的有bugsnag、sentry等,这两个貌似还比较流行。
  • (1)Bugsnag
    bugsnag:聚焦于web端、移动端、服务端程序的错误监控,能自动报告未处理的异常和崩溃,通过崩溃报告日志更好地了解到有关用户的操作情况,也附上了相应的用户信息来确定受崩溃的程度。
    官网上目前可以集成的平台如下图所示:

bugsnag.png

我访问bugsnag超级慢且卡,用我的qq邮箱无法登陆,直接导致我不继续研究bugsnag的原因是看到了这个:

异常自动恢复执行情况监控 异常行为监控_ci_06

image.png

  • 也就是说它能免费试用14天,oh my god,要收费的请离我远一点。
  • (2)Sentry
    Sentry是一个实时事件日志和聚合平台。它的核心是监视错误和提取所有必要的信息,以便进行正确的事后分析。它支持以下语言或平台:

sentry.png

  • 如果您最喜欢的语言没有在以上列出,可以在社区论坛上开始讨论如何支持它(社区链接:https://forum.sentry.io/
    后面主要讲sentry方面的集成和使用,因为笔者认为对于reactnative的应用,集成sentry异常监控相当合适,而且没有出现要收费的提示。
    相关链接:https://blog.sentry.io/2018/03/06/the-sentry-workflow

四、react-native-sentry SDK的集成和使用

集成react-native-sentry也很简单,如下图所示:

 

异常自动恢复执行情况监控 异常行为监控_异常自动恢复执行情况监控_07

还可以自己搭建sentry,感觉还挺复杂的,这里附录一些教程,有时间可以尝试弄一下:

五、Demo分析

  • sentry分析

(1)贴上一段简单的代码,自己模拟抛出异常:
/**
* Sample React Native App
* https://github.com/facebook/react-native * @flow
*/

import React, {Component} from 'react';
import {
    TouchableOpacity,
    StyleSheet,
    Text,
    View,
    WebView,
} from 'react-native';

import {Sentry} from 'react-native-sentry';

Sentry.config('https://9126f02fbec841f7808636a21b3d89b7:47dccf6f5ca64a09a9bfbab6f5e717ef@sentry.io/1200153').install();


type Props = {};
export default class App extends Component<Props> {

    _refresh = () => {
        try {
            throw new Error('刷新')
        }
        catch (e) {
            console.log('e==='+e)
        }

    }

    _throwException = () => {
        throw new Error('我在客户端用js抛出了一个异常2')
    }

    render() {
        return (
            <View style={styles.container}>

                <View style={{flex: 1, backgroundColor: '#fff'}}>
              
                    <TouchableOpacity onPress={this._throwException} style={{
                        height: 50,
                        backgroundColor: 'green',
                        justifyContent: 'center',
                        alignItems: 'center'
                    }}>
                        <Text style={{color: '#fff'}}>抛出异常</Text>
                    </TouchableOpacity>

                </View>

                <TouchableOpacity onPress={this._refresh} style={{
                    height: 50,
                    backgroundColor: 'green',
                    justifyContent: 'center',
                    alignItems: 'center'
                }}>
                    <Text style={{color: '#fff'}}>刷新</Text>
                </TouchableOpacity>


            </View>
        );
    }
}

const styles = StyleSheet.create({
    container: {
        flex: 1,
        backgroundColor: '#fff'
    },

});

(2)查看异常详情

  • 自己模拟抛出异常(这里是在开发模式下)
  • throw ‘刷新’

TAGS.png

点 JSON可以查看所有的字段:

异常自动恢复执行情况监控 异常行为监控_异常自动恢复执行情况监控_08

device.png

下面解释几个字段所代表的意思:

Architecture :cpu架构
 Battery Level:电池电量
 Orientation:屏幕方向(portrait为纵向)
 Memory:运行内存
 Capacity:存储容量
 Simulator:是否为模拟器
 brand:商标
 charging:是否正在充电中
 manufacturer:制造商
 online:联网
 screen_density:屏幕像素比
 screen_dpi:屏幕密度
 screen_resolution:屏幕分辨率

异常自动恢复执行情况监控 异常行为监控_App_09

image.png

Bundle ID:应用的包名
Bundle Name:应用名称

  • throw new Error('点刷新抛出一个Error')

image.png

如果你打包出正式的apk,那么其报告日志就有所不同了

  • 如下图所示:它会定位到代码文件和代码行数,让你很快知道异常抛出的位置。

image.png

而且Sentry还可以发送崩溃报告邮件到你的邮箱,还可以进行相应的通知设置,如下图所示:

 

异常自动恢复执行情况监控 异常行为监控_异常自动恢复执行情况监控_10

崩溃报告.png

综上所述,Sentry还是比较适合用于做reactnative App的异常监控工具,简单易用,免费使用,而且还可以阅读英文版报告邮件。但是,实践是检验真理的唯一标准,把它应用到实际项目中去,看看是否真的好用。