远程call带try异常保护_异常哪里跑?

a99ea5fbcad04beb898796120e86f98a.png

运行在用户浏览器环境下的前端项目中,可能会产生各种异常。要监控前端的运行稳定性,异常的捕获上报并收集数据进行分析是必要的一个环节。某次线上的异常捕获问题引起了笔者的关注,就顺便对项目中可用的异常捕获流程进行了研究。

问题背景

某段时间,在我们的前端监控系统中一直会捕获到一个错误,错误内容是 "Cannot read property '$refs' of undefined"。但是在本地开发环境中构建运行线上分支时,没有看到任何错误信息被抛出,因此一度以为本地无法复现问题。

实际上,换个角度想就不难理解了。既然监控系统能收集到错误的具体信息,说明这些信息应该被前端代码中使用的监控平台 SDK 捕获并通过 HTTP 请求将错误信息从浏览器发到了后台。因此被捕获的错误没有在控制台抛出是正常的。查看请求列表果然找到了相关的请求:

30db58af9b886b7f2519bae3eb0f4282.png

Vue 的异常捕获机制

由于上文提到的项目是基于 Vue 开发的,这里就来谈谈 Vue 如何进行异常的捕获。

对于 2.4.0+ 版本的 Vue,会捕获组件的渲染和观察期间、生命周期钩子中以及自定义事件处理函数内部的异常。这些捕获点在通过 try-catch 语句捕获到异常后,会通过 handleError 工具函数统一处理异常,这个工具函数定义在源码中的 /src/core/util/error.js 中:

export function handleError (err: Error, vm: any, info: string) {
  if (vm) {
    let cur = vm
    while ((cur = cur.$parent)) {
      const hooks = cur.$options.errorCaptured
      if (hooks) {
        for (let i = 0; i < hooks.length; i++) {
          try {
            const capture = hooks[i].call(cur, err, vm, info) === false
            if (capture) return
          } catch (e) {
            globalHandleError(e, cur, 'errorCaptured hook')
          }
        }
      }
    }
  }
  globalHandleError(err, vm, info)
}

可以看到,对于捕获到的异常,首先会查找抛出异常 Vue 组件实例的 errorCaptured 钩子(2.5.0+ 版本新增),并沿父组件的路径依次调用 errorCaptured 钩子(如果有的话)。如果 errorCaptured 钩子没有返回 false 来阻止异常继续传播,或是干脆没有提供 errorCaptured 钩子的话,则会将异常交由通用处理函数 globalHandleError 进行处理。如果提供了全局错误处理函数 errorHandler 的话,就会调用该函数,如果没有提供,或是在执行 errorHandler 函数时又抛出了错误,则会直接将异常打印在控制台上,如果运行在非浏览器环境中,则直接抛出错误。

实现错误上报服务

我们可以利用 Vue 的异常捕获机制实现自己的错误上报服务。实现思路也很简单,只需要劫持原有的 errorHandler 函数,保存原来的函数引用,取得传入的参数,执行上报逻辑后再继续执行原函数引用即可。

以错误追踪服务 Sentry 为例,它在为 Vue 项目提供的前端 SDK 中实现劫持逻辑的代码如下:

function vuePlugin(Raven, Vue) {
  Vue = Vue || window.Vue;
​
  // quit if Vue isn't on the page
  if (!Vue || !Vue.config) return;
​
  var _oldOnError = Vue.config.errorHandler;
  Vue.config.errorHandler = function VueErrorHandler(error, vm, info) {
    var metaData = {};
​
    // vm and lifecycleHook are not always available
    if (Object.prototype.toString.call(vm) === '[object Object]') {
      metaData.componentName = formatComponentName(vm);
      metaData.propsData = vm.$options.propsData;
    }
​
    if (typeof info !== 'undefined') {
      metaData.lifecycleHook = info;
    }
​
    Raven.captureException(error, {
      extra: metaData
    });
​
    if (typeof _oldOnError === 'function') {
      _oldOnError.call(this, error, vm, info);
    }
  };
}

它将全局配置下的 errorHandler 的引用保存于 _oldOnError 变量,并在最后通过 _oldOnError.call(this, error, vm, info) 语句执行原函数,在无感知的情况下完成了异常信息的上报。

然而,以上代码只能完成 Vue 组件内部的错误上报,并不能覆盖全局,要实现浏览器中全局的异常捕获,还需要填补一些“漏洞”。

window.onerror

首先处理这个全局 onerror 事件,处理方式和 Vue 的 errorHandler 一样劫持即可:

const _window = window
const _oldOnerrorHandler = _window.onerror
_window.onerror = customOnerrorHandler // 自定义的 onerror 函数

promise catch

当代码中的 promise reject 的时候,onerror 是捕获不到异常的。对于 promise reject 的异常,除了对每个用到 promise 的地方都加上 catch 之外,我们还应该在全局环境下进行一个兜底。

我们可以监听全局 unhandledrejection 事件:

window.addEventListener('unhandledrejection', (e) => {
  console.log(e)
})

如果要阻止异常输出到控制台上,可以加上 e.preventDefault()

网页崩溃

对于网页崩溃的监控, @寸志 老师总结的一篇文章叙述得比较详细,可以参考这篇:https://zhuanlan.zhihu.com/p/40273861。

总结

浏览器环境中的错误监控有很多方面的工作要做,本文只是抛砖引玉,在具体实现过程中会有很多细节要处理。另外也推荐使用成熟的开源监控系统,例如 Sentry。可以节省很多时间将精力放在业务开发上。

x64进程远程hook,x64_远程调用函数,源码更新V1.8.2:2021/4/12 源码为下方连接帖子后续更新内容: 浅谈64位进程远程hook技术: https://bbs.125.la/forum.php?mod=viewthreadtid=14666356extra= 不管您是转载还是使用请保留版权,源码在精益论坛免费发布本人未获利,请不要用于非法途径。 --------------------------------------------------------------- 2021/4/12 模块源码 v1.8.2更新 1:修复 x64_远调用函数()在 易语言 主线程调用时造成消息无法回调,导致易语言主线程窗口卡死的问题。      感谢楼下易友发现的BUG,已经第一时间更新 2021/4/12 模块源码 v1.8.1更新 1:修复 hook全部卸载时的流程写法的一个错误,由于句柄的提前关闭导致多个hook点卸载不干净的问题 2:改写了消息回调时线程传参的代码优化,优化了其他一些小问题 3:  鉴于很多朋友需要,改写了模块自实列,对TCP,UDP的两组封包函数做了hook实列写法 4:列子中同样增加对x64_远调用函数()的应用写了几个列子,如使用套接字取得本地或远端IP端口API调用的的应用实列 5:本hook模块不支持非模块内存区hook,如申请的动态分配页等,不是不能支持,只是觉得没有任何意义,对这方面有需求的,自行改写模块源码使用 提醒:hook回调函数中尽量减少耗时代码,时间越长返回越慢,回调中谨慎操作控件,如必须要用到可参考源码中实列写法采用线程操作 历史更新 --------------------------------------------------------------- 2021/3/1   模块源码v1.6更新: 1:修复  x64_远程调用函数()命令,在没有提供 寄存器 参数时,没有返回值的BUG。 --------------------------------------------------------------- 2021/2/28 模块源码v1.5更新: 一:修复win7 64位系统下枚举模块 出现部分模块长度出现负数的问题,从而导致部分win7用户不能使用 二:强化 远程hook64指令_安装 的稳定性:        1,穿插代码中增加对标志位的保护,避免hook位置长度下一条指令为跳转时产生跳转错乱的问题,强化了hook任意位置的定位        2,因为穿插代码中会调用API函数,而64位汇编必须遵守栈指针16字节对齐,故对穿插代码进行栈指针16字节对齐,增强稳定性        3,hook指令安装支持长度由6-127字节 变动 为 6-119字节,原因么没必要说了,代码优化造成的,稍微少了一点无所谓了        4,对模块回调进行了适当优化处理,增强稳定性 三:应支持的朋友需要故增加 x64_远程调用函数()命令,易语言可以直接远call64进程,且无需写汇编代码或机器码指令,支持15个参数,支持返回值,支持16个通用寄存器全部取得返回值       该功能调用即16字节栈对齐,不要用户管堆栈,代码内部构成,远线程执行,你只需要知道call有几个参数,需要什么寄存器,对应提供即可。 四:有朋友说原模块x64英文看了烦,那好吧就给改成了中文标识,弄得我自己也不习惯 五:源码内列子改了改,可以自己看,需要注意的是模块注释的很详细,使用前最好看一看,尤其是hook回调接口的写法和安装的写法最好按照模块列子中的写法来,除非你能把64hook模块组看懂一遍,对于一些对本模块一知半解的朋友请不要乱改乱发,这个模块我会继续增强的,只是工作原因时间有限,只能一点一点来
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值