前端一站式异常监控捕获方案

开发 前端
在服务器开发中,我们常常使用日志来记录请求的错误和服务器异常问题,但是在客户端,前端应用直接部署运行在用户的浏览器中,如果发生错误,应该怎样去捕获并传送给服务器呢?前端错误日志传送给服务器很简单,在异常发生时直接发请求就可以了,下面我们主要讨论下错误的捕获方案。

【引自ouven的博客】一、前端异常监控的重要性

软件异常监控常常直接关联到软件本身的质量,完备的异常监控体系常常能够快速定位到软件运行中发生的问题,并能帮助我们快速定位异常的源头,提升软件质量。

在服务器开发中,我们常常使用日志来记录请求的错误和服务器异常问题,但是在客户端,前端应用直接部署运行在用户的浏览器中,如果发生错误,应该怎样去捕获并传送给服务器呢?前端错误日志传送给服务器很简单,在异常发生时直接发请求就可以了,下面我们主要讨论下错误的捕获方案。

二、现有的异常监控方案

  • window.onerror全局异常捕获   目前前端捕获页面异常的方式主要有两种,window.onerror捕获整个页面中运行的错误,它的局限是对于跨域的JavaScript脚本需要添加跨域支持,也就是需要涉及服务器的修改成本,否则无法获取到运行时具体的堆栈错误信息,而是"script error"的信息,不利于我们定位问题。
  1. window.onerror = function(msg, file, row, column, errorObj) { 
  2.     console.log(msg); // script error. 
  3.     console.log(file); //  
  4.     console.log(row); // 0 
  5.     console.log(column); // 0 
  6.     console.log(errorObj); // {} 
  7.     setTimeout(function() { 
  8.         // 发送请求上报日志信息 
  9.         errorReport(e.name, e.message + e.stack); 
  10.     }, 5000); 
  11.  
  1. <script src="//domain.com/path/main.js" crossorigin></script> 
  • try-catch运行时解决方案

  现有的另一中方案则是try-catch,对于某个方法函数,我们可以这样定义来捕获函数里面运行时的异常,但是try-catch只能捕获当前单个作用域下的异常。另外,使用try-catch会带来一定的性能损耗,根据循环测试,平均大概会损失6%~10%的性能,但是为了提升应用的质量和稳定性,这些是可以接受的。 

  1. function wrapFunction(fn) { 
  2.     return function() { 
  3.         try { 
  4.             return fn.apply(this, arguments); 
  5.         } catch (e) { 
  6.             console.log(e); 
  7.             _errorProcess(e); 
  8.             return
  9.         } 
  10.     }; 
  11.  
  12. // 之后fn函数里面的代码运行出错时则是可以被捕获到的了 
  13. fn = wrapFunction(fn); 
  14.  
  15. // 或者异步函数里面的回调函数中的错误也可以被捕获到 
  16. var _setTimeout = setTimeout; 
  17. setTimeout = function(fn, time){ 
  18.     return _setTimeout(wrapFunction(fn), time); 
  19.  
  20. // 模块定义函数也可以做重写定义 
  21. var _require = require; 
  22. require = function(id, deps, factory) { 
  23.     if (typeof(factory) !== 'function' || !factory) { 
  24.         return _require(id, deps); 
  25.     } else { 
  26.         return _require(id, deps, wrapFunction(factory)); 
  27.     } 
  28. };  

那么我们可以对常用的模块入口函数进行重定义,包括define,require等,这样模块中的主要作用域中的异常都可以通过try-catch来捕获了。在之前的处理方法中,这种方法是非常有效的,直接可以拿到大多数错误栈中的异常和堆栈信息。

三、改进的一站式解决方案

React开发时代,这种方式就不能直接使用了,我们知道React的组件都是class,其实也就是构造函数,这里普及下class和构造函数其实是非常类似的,class A除了constructor为class A,其它信息和function A类似,typeof获取的类型也相同。但是我们是没办法把构造函数A直接装入try-catch中运行的,因为需要通过关键字new进行实例化,并创建新的作用域。 

    

 

此时我们要处理的问题其实是捕获React中属性方法中的错误,应该还记得,JavaScript中函数有个特殊的属性prototype,当函数作为构造函数是,prototype中的属性就成了实例化后的属性方法,而且这一属性对class同样生效。那么我们可以对React中class的prototype这个特殊属性的内容进行处理,对Component中的方法函数进行封装。

  1. function defineReact(Component) { 
  2.  
  3.     var proto = Component.prototype; 
  4.  
  5.     for (var key in proto) { 
  6.         if (typeof(proto[key]) === 'function') { 
  7.             proto[key] = _wrapFunction(proto[key]); 
  8.         } 
  9.     } 
  10.  
  11.     return Component; 
  12.  

这样通过实例化产生的React组件中的内部方法中的错误就可以被捕获到了。 

  1. class component extends React.Component { 
  2.     componentDidMount(){ 
  3.         var a = {}; 
  4.         console.log(a.b.c); 
  5.     } 
  6.     render() { 
  7.         return <div>hello world</div>; 
  8.     } 
  9. export default defineReact(component);  

这里添加defineReact的操作就可以放到构建打包工具中去处理了,这样就避免了我们对代码层直接进行修改。 

 

React直接报错不利于定位问题   

 

封装后直接获取堆栈错误 

四、小结

  小结一下,其实和原有的方式差别不大,仍然通过try-catch的方式,覆盖到React组件prototype属性中进行异常捕获,极大增加了错误捕获范围,不仅能帮助我们快速定位开发中的问题,也能捕获React线上应用的运行时错误。 

责任编辑:庞桂玉 来源: ouven的博客
相关推荐

2021-03-16 17:51:03

戴尔

2010-05-06 16:02:26

2013-06-14 09:30:52

2013-10-20 13:30:07

华为一站式BYOD敏捷办公

2015-04-19 16:36:10

腾讯云

2015-12-15 17:33:57

戴尔云计算

2013-12-12 15:34:00

Moneta移动支付一站式解决方案

2009-10-23 09:42:24

2009-07-30 21:16:29

布线服务电缆架设

2022-09-16 11:27:46

建设微服务

2023-10-26 06:59:58

FinOps云原生

2017-11-28 13:53:18

2015-02-02 11:06:21

cocos cocos一站式解决

2023-05-26 08:37:04

All in ECPES数据

2012-04-09 17:36:38

华为智真

2013-10-24 17:35:01

云网络H3C电子政务

2009-07-27 11:37:04

网络拓扑摩卡

2021-12-07 10:04:34

Azure Kuber场景应用

2014-01-13 09:00:54

PythonDjango
点赞
收藏

51CTO技术栈公众号