小程序性能监听

Android

API

Mriver.setProxy(PrepareNotifyProxy.class, new PrepareNotifyProxy() {
            @Override
            public void notify(String s, PrepareStatus prepareStatus) {

            }

            @Override
            public void apmEvent(final String event, final String param1, final String param2, final String param3, final String param4) {
                runOnUiThread(new Runnable() {
                    @Override
                    public void run() {
                        if (logs == null) {
                            logs = new StringBuilder();
                        }
                        logs.append(s).append(" ").append(s1).append(" ").append(s2).append(" ").append(s3).append(" ").append(s4).append("\n");
                    }
                });

            }
});

事件如下:

MINI_APP_PREPARE 参数errc!=1表示应用打开异常
MINI_PAGE_ABNORMAL          白屏
MINI_APP_REQUEST          参数step=fail表示应用拉包异常
MINI_AL_NETWORK_PERMISSON_ERROR    页面访问受限
MINI_AL_JSAPI_RESULT_ERROR      jsapiName=httpRequest或者request表示request请求异常,其他表示JSAPI异常
MINI_CUSTOM_JS_ERROR               JS异常
MINI_AL_NETWORK_PERFORMANCE_ERROR  资源请求异常
MiniAppStart 启动耗时,startCost 表示时间,单位毫秒

iOS

API

途径为开发一个插件,拦截对应的系统事件,Plist 配置如下:

image.png
// Plist 初始化代码:
- (void)application:(UIApplication *)application beforeDidFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
//    [MPNebulaAdapterInterface initNebula];
    NSString* h5Json = [[NSBundle mainBundle] pathForResource:[NSString stringWithFormat:@"MPCustomPresetApps.bundle/h5_json.json"]ofType:nil];
    NSString* amrBundle = [[NSBundle mainBundle] pathForResource:[NSString stringWithFormat:@"MPCustomPresetApps.bundle"] ofType:nil];
    NSString* jsPath = [NSBundle.mainBundle.bundlePath stringByAppendingPathComponent:@"MPCustomPluginsJsapis.bundle/Poseidon-UserDefine-Extra-Config.plist"];
    [MPNebulaAdapterInterface initNebulaWithCustomPresetApplistPath:h5Json
                                         customPresetAppPackagePath:amrBundle
                                            customPluginsJsapisPath:jsPath];
}

代码如下:

// .h 文件
#import <AriverApp/RVAPluginBase.h>

NS_ASSUME_NONNULL_BEGIN

@interface DemoPlugin4APM : RVAPluginBase

@end

NS_ASSUME_NONNULL_END


// .m 文件
#import "DemoPlugin4APM.h"
#import <NebulaPoseidon/PSDMonitorEvent.h>

@implementation DemoPlugin4APM


- (void)pluginDidLoad
{
    self.scope = kPSDScope_Service;
    [self.target addEventListener:kEvent_Monitor_Log_Before withListener:self useCapture:NO];
    [super pluginDidLoad];
}

- (void)handleEvent:(RVKEvent *)event
{
    [super handleEvent:event];
    
    if ([kEvent_Monitor_Log_Before isEqualToString:event.eventType]){
        
        PSDMonitorEvent *mEvent = (PSDMonitorEvent *)event;
        NSArray *params = [mEvent.params isKindOfClass:[NSArray class]]? mEvent.params : @[];
        NSString *bizType = [NSString stringWithFormat:@"%@", mEvent.bizType];
        NSString *seedId = [NSString stringWithFormat:@"%@", mEvent.seedId];
        if ([params count] != 4 || ![bizType length]) {
            return;
        }
        
        NSString *aplogstr = [NSString stringWithFormat:@"%@\nbizType=%@,param1=%@,param2=%@,param4=%@",params[2],bizType,params[0],params[1],params[3]];
        NSLog(@"seed seedId:[%@]\nlog:[%@]", seedId, aplogstr);
        // 打开异常:复现路径,打开demo后点 “打开异常 H5_APP_PREPARE”
        if ([seedId isEqualToString:@"H5_APP_PREPARE"]) {
            NSString *currentStep = [self mp_valueFromLogStr:params[2] key:@"step"];
            // step 参数为 noexistForce 表示打开异常
            if ([currentStep isEqualToString:@"noexistForce"]) {
                NSLog(@"seedId:[%@]\nlog:[%@]", seedId, aplogstr);
            }
        }
        // js异常:复现路径,打开demo后点 “APM 小程序 -> js error”
        else if ([seedId isEqualToString:@"H5_CUSTOM_ERROR"]) {
            NSLog(@"seedId:[%@]\nlog:[%@]", seedId, aplogstr);
        }
        // 白屏:复现路径,断网之后(飞行模式,WiFi断开),打开demo后点 “打开异常 H5_APP_PREPARE”
        else if ([seedId isEqualToString:@"H5_PAGE_ABNORMAL"]) {
            NSLog(@"捕获白屏:[%@]\nlog:[%@]", seedId, aplogstr);
        }
        // 拉包异常,暂无复现路径,需要遇到小程序拉包接口异常才可复现:
        else if ([seedId isEqualToString:@"H5_APP_REQUEST"]) {
            NSString *currentStep = [self mp_valueFromLogStr:params[2] key:@"step"];
            NSLog(@"拉包:[%@]\nlog[%@]:[%@]", seedId, currentStep, aplogstr);
            if ([currentStep containsString:@"fail"]) {
                NSLog(@"拉包异常:[%@]\nlog[%@]:[%@]", seedId, currentStep, aplogstr);
            }
        }
        // 页面访问受限                        H5_AL_NETWORK_PERMISSON_ERROR
        // 复现路径,打开demo后点 “APM 小程序 -> 页面访问受限”
        else if ([seedId isEqualToString:@"H5_AL_PAGE_UNAUTHORIZED"] || [seedId isEqualToString:@"H5_AL_NETWORK_PERMISSON_ERROR"]) {
            NSLog(@"seedId:[%@]\nlog:[%@]", seedId, aplogstr);
        }
        // request请求异常 / JSAPI异常         H5_AL_JSAPI_RESULT_ERROR
        // 复现路径,打开demo后点 “APM 小程序 -> js api error”
        else if ([seedId isEqualToString:@"H5_AL_JSAPI_RESULT_ERROR"]) {
            NSLog(@"seedId:[%@]\nlog:[%@]", seedId, aplogstr);
        }
        // 资源请求异常                        H5_AL_NETWORK_PERFORMANCE_ERROR
        // 复现路径,断网 -> 打开demo后点 “跳转小程序 -> 点刷新”
        else if ([seedId isEqualToString:@"H5_AL_NETWORK_PERFORMANCE_ERROR"]) {
            NSLog(@"seedId:[%@]\nlog:[%@]", seedId, aplogstr);
        }
    }
    
    // 启动耗时
    // 计算规则:从 AppStart 起,到 PageLoad 止,中间消耗的时间,单位是秒。
    // 业务取值应该只计算第一次回调,即首页加载完毕(从下边 h5WebVC.url 的 log 可以看出区别)
    if ([event.eventType isEqualToString:kEvent_Session_Create]) {
        // 记住时间戳:
        CFTimeInterval tm = CACurrentMediaTime(); // CACurrentMediaTime() 是基于内建时钟的,能够更精确更原子化地测量,并且不会因为外部时间变化而变化(例如时区变化、夏时制、秒突变等),但它和系统的uptime有关,系统重启后CACurrentMediaTime()会被重置。 CACurrentMediaTime() 常用于测试代码的效率。
        self.appStartTimestamp = tm;
        NSLog(@"kEvent_Session_Create: AppStart [%f]", tm);
    }
    
    if ([event.eventType isEqualToString:kEvent_Page_Load_Complete]) {
        PSDEvent *oldEvent = (PSDEvent *)event;
        H5WebViewController *h5WebVC = (H5WebViewController *)[oldEvent.context currentViewController];
        NSString* currentUrl = [h5WebVC.url absoluteString];
        NSLog(@"kEvent_Session_Create: page[%@]", currentUrl);
        if ([currentUrl containsString:@"#"]) { //
            // 时间戳:
            CFTimeInterval tm = CACurrentMediaTime();
            NSLog(@"kEvent_Session_Create: PageLoad [%f]", tm);
            CFTimeInterval appStartTime = tm - self.appStartTimestamp;
            NSLog(@"kEvent_Session_Create: AppStart - PageLoad = 启动时间[%f]", appStartTime);
        }
    }
}

@end

Android 、iOS 事件对照表

事件

Android

iOS

打开异常

MINI_APP_PREPARE

H5_APP_PREPARE

白屏

MINI_PAGE_ABNORMAL

H5_PAGE_ABNORMAL

拉包异常

MINI_APP_REQUEST

H5_APP_REQUEST

页面访问受限

MINI_AL_NETWORK_PERMISSON_ERROR

H5_AL_NETWORK_PERMISSON_ERROR / H5_AL_PAGE_UNAUTHORIZED

request 请求异常 /JSAPI 异常

MINI_AL_JSAPI_RESULT_ERROR

H5_AL_JSAPI_RESULT_ERROR

js 异常

MINI_CUSTOM_JS_ERROR

H5_CUSTOM_ERROR

资源请求异常

MINI_AL_NETWORK_PERFORMANCE_ERROR

H5_AL_NETWORK_PERFORMANCE_ERROR

启动耗时

MiniAppStart

小程序

my.onError(Function listener)

简介

my.onError 监听小程序错误事件。

入参

Function listener

参数

属性

类型

兼容性

描述

error

String

-

异常描述,一般为 Error 对象的 message 字段。

stack

String

基础库:2.7.4

异常堆栈,一般为 Error 对象的 stack 字段。

代码示例

my.onError(Function listener)

Page({
 onLoad() {
 my.onError(this.errorHandler);
 },
 errorHandler(error, stack) {
 console.log('onError error', error);
 console.log('onError stack', stack);
 }
})
说明
  • 使用 my.onError 监听到的报错,app.js 中的 onError 方法也会监听到。

  • 使用 my.onError 监听页面报错,如果在多个页面开启监听没有关闭,则页面报错时会触发多个监听事件,建议在页面关闭时调用 my.offError 关闭监听。

my.onUnhandledRejection(Function listener)

简介

my.onUnhandledRejection 监听未处理的 Promise 拒绝(unhandled rejection)事件。

入参

Function listener

未处理的 Promise 拒绝事件的回调函数。

参数

Object res

属性

类型

描述

reason

any

拒绝原因。reject() 的接收值,一般是 Error 对象。

promise

Promise

被拒绝的 Promise 对象。

注:仅 iOS 上支持

代码示例

my.onUnhandledRejection(Function listener)

Page({
 onLoad() {
 my.onUnhandledRejection(this.unhandledRejectionHandler);
 },
 unhandledRejectionHandler(res) {
 console.log('onUnhandledRejection reason', res.reason);
 console.log('onUnhandledRejection promise', res.promise);
 }
})
说明
  • my.onUnhandledRejection 的回调函数内继续触发 Promise unhandledrejection 事件,则可能会导致循环触发 unhandledrejection 事件,请注意规避。

  • 所有的 unhandledRejection 都可以被这一监听捕获,但只有 Error 类型的才会在小程序后台触发报警。