Front-end one-stop exception capture solution (full)

1. The importance of front-end exception monitoring

  Software exception monitoring is often directly related to the quality of the software itself. A complete exception monitoring system can often quickly locate problems that occur during software operation, and can help us quickly locate the source of problems and improve software quality.

  In server development, we often use logs to record request errors and server exceptions, but in front-end development, front-end engineers complete page development according to requirements, and through product experience confirmation and testing, the page can be launched. But unfortunately, the product quickly received complaints from users. The user reported that the page did not respond when the button was clicked and could be reproduced. We tried it and everything was normal, so we asked the user about the environment used by the user. The final conclusion was that the user used a very small browser to open the page because the browser does not support certain Because of this feature, the page reports an error and the entire page stops responding. In this case, the complaints reported by users took us a lot of time to locate the problem, but this is not the most terrifying, and what worries us is that more users will directly abandon the problem after encountering such a scenario. The "junk product" in question. The only solution to this problem is to immediately fix the problem when as few users as possible encounter such a scenario, so as to ensure that as many users as possible can use it normally.

  First of all, we need to know when a small number of users make mistakes when using the product, and try to locate what the error is. Since the user's operating environment is on the browser side, the error information can be uploaded to the server when the front-end page script execution error occurs, and then the error information collected by the server can be opened for analysis to improve the quality of the product. Below we mainly discuss the capture of errors. plan.

2. Existing anomaly monitoring solutions

  • window.onerror global exception capture

  At present, there are two main ways for the front end to catch page exceptions: try...catch and window.onerror.

  The method of window.onerror can be executed in any execution context. If you add an error handling function to the window object, you can handle capturing errors and keep the code elegant. window.onerror is generally used to capture script syntax errors and runtime errors, and can obtain error file information, such as error information, error file, line number, etc. All JavaScript script errors executed on the current page will be captured.

window.onerror = function (msg, url, line){
         // 可以捕获异步函数中的错误信息并进行处理,提示Script error.
    console.log(msg);   // 获取错误信息
    console.log(url);   // 获取出错的文件路径
    console.log(line);  // 获取错误出错的行数
};

setTimeout(function() {
    console.log(obj);   // 可以被捕获到,并在onerror中处理
}, 200);

  However, when using onerror, it should be noted that the exception objects returned by the function processing in different browsers are different, and if the JavaScript and HTML reporting the error are not under the same domain name, the errorMsg in window.onerror is all script error when the error occurs. It is not a specific error description information. At this time, you need to add cross-domain settings for JavaScript scripts.

<script src="//www.domain.com/main.js" crossorigin></script>

  If the server cannot be set cross-domain for some reason or it is troublesome to set up, it can only be processed by adding try...catch in each referenced file.

  • try-catch runtime solution

  Generally speaking, using try...catch can catch the runtime error of front-end JavaScript, and get the error information, such as error message description, stack, line number, column number, specific error file information, etc. We can also record static content such as user browser information at this stage to quickly locate the cause of the problem. It should be noted that try...catch cannot catch syntax errors, and can only effectively capture error information in a single scope. If it is the content of an asynchronous function, you need to add all the content of the function function block to the try... Execute in catch.

try{
    // 单一作用域try...catch可以捕获错误信息并进行处理
    console.log(obj);
}catch(e){
    console.log(e); //处理异常,ReferenceError: obj is not defined
}

try{
    // 不同作用域不能捕获到错误信息
    setTimeout(function() {
        console.log(obj); // 直接报错,不经过catch处理
    }, 200);
}catch(e){
    console.log(e);
}

// 同一个作用域下能捕获到错误信息
setTimeout(function() {
    try{
        // 当前作用域try...catch可以捕获错误信息并进行处理
        console.log(obj); 
    }catch(e){
        console.log(e); //处理异常,ReferenceError: obj is not defined
    }
}, 200);

  But in the above example, try...catch can't get the error message in the asynchronous function setTimeout or other scopes, so we can only add try...catch in each function.

  Although you can use window.onerror to get the error information, error file and line number of the page, but window.onerror has cross-domain restrictions, if you need to get the specific description of the error, stack content, line number, column number and specific error file, etc. For detailed logs, try...catch must be used, but try...catch cannot handle errors uniformly in multiple scopes.

  Fortunately, we can use try...catch to encapsulate the entry functions of asynchronous methods commonly used in front-end scripts or entry methods of module references, so that we can use try...catch to catch the main errors in the scope of each referenced module. information. For example, we can encapsulate the setTimeout function in the following way and capture error information. In addition, the use of try-catch will bring a certain performance loss. According to the cycle test, the average performance will be lost by 6% to 10%, but in order to improve the quality and stability of the application, these are acceptable.

function wrapFunction(fn) {
    return function() {
        try {
            return fn.apply(this, arguments);
        } catch (e) {
            console.log(e);
            _errorProcess(e);
            return;
        }
    };
}

// 之后fn函数里面的代码运行出错时则是可以被捕获到的了
fn = wrapFunction(fn);

// 或者异步函数里面的回调函数中的错误也可以被捕获到
var _setTimeout = setTimeout;
setTimeout = function(fn, time){
    return _setTimeout(wrapFunction(fn), time);
}

// 模块定义函数也可以做重写定义
var _require = require;
require = function(id, deps, factory) {
    if (typeof(factory) !== 'function' || !factory) {
        return _require(id, deps);
    } else {
        return _require(id, deps, wrapFunction(factory));
    }
};

  This is where we can redefine common module entry functions, including setTimeout, setInterval, define, require, etc., so that exceptions in the main scope of the module can be caught by try-catch. In the previous processing method, this method is very effective, and you can directly get the exception and stack information in most of the error stacks.

  We can use try...catch to encapsulate the introduction of setTimeout parameter functions in different scopes, so that try...catch can catch errors in the setTimeout script and use the setTimeoutTry function instead. Similar encapsulation can also be performed for the asynchronously imported module definition functions require or define, so that the error information of the scope in different modules can be obtained. Therefore, the way to capture errors here can be flexibly selected according to specific conditions and scenarios. In the absence of special restrictions, it is more efficient and convenient to use window.onerror.

3. Improved one-stop solution

  • Exception Catching Scheme of ES6 Class

  Although the combination of window.onerror and try...catch can solve many problems, in the era of React development, this method cannot be used directly. We know that the components of React are all classes, which are actually constructors, which are popularized here. Class and constructor are actually very similar, except that the constructor of class A is class A, other information is similar to function A, and the type obtained by typeof is also the same. But we can't directly load the constructor A into try-catch to run, because it needs to be instantiated through the keyword new and create a new scope.

  The problem we have to deal with at this point is actually to capture errors in property methods in React. You should remember that functions in JavaScript have a special property prototype. When a function is used as a constructor, the properties in the prototype become instantiated. Attribute method, and this attribute also takes effect on the class. Then we can process the content of the special property of class prototype in React, and encapsulate the method functions in Component.


/**
 * 封装React方法的错误处理,改成使用入参的prototype中是否有render生命周期函数来判断
 * @param  {object} Component 传入组件
 * @return {object} 返回包裹后的组件
 */
function _defineReact(Component) {

    var proto = Component.prototype;
    var key;

    // 封装本身constructor中的构造方法,React组件编译为ES5后是一个构造函数,ES6下面为class
    if (_isTrueFunction(Component)) {
        Component = wrapFunction(Component);
    }

    var componnetKeys = Object.getOwnPropertyNames(proto);

    // 支持ES6类的属性方法错误捕获
    for (var i = 0, len = componnetKeys.length; i < len; i++) {
        key = componnetKeys[i];
        proto[key] = wrapFunction(proto[key])
    }

    // 支持ES5下的属性方法错误捕获
    for (key in proto) {
        if (typeof proto[key] === 'function') {
            proto[key] = wrapFunction(proto[key]);
        }
    }

    return Component;
}

/**
 * 判断是否为真实的函数,而不是class
 * @param  {Function} fn [description]
 * @return {Boolean}     [description]
 */
function _isTrueFunction(fn) {

    var isTrueFunction = false;

    try {
        isTrueFunction = fn.prototype.constructor.arguments === null;
    } catch (e) {
        isTrueFunction = false;
    }

    for (var key in fn.prototype) {
        return false;
    }
    return isTrueFunction;
}

  This way errors in internal methods of React components generated by instantiation can be caught. Even if the code is not compiled to ES5 by babel, exceptions in the class can be caught.

class component extends React.Component {
    componentDidMount(){
        var a = {};
        console.log(a.b.c);
    }
    render() {
        return <div>hello world</div>;
    }
}
export default _defineReact(component);

  The operation of adding defineReact here can also be processed in the build packaging tool, which avoids us from directly modifying the code layer.

React directly reporting errors is not conducive to positioning problems

Get stack error directly after encapsulation

  In addition, I have to say that the componentDidCatch method is provided in react 16, which can directly capture the exception in the react component and report it directly. This is actually easier for react scenarios.

  • Error trapping inside promises

  In the front-end code, if you want to browse and run ES6 code directly and use Promise, you have to reconsider the exception capture of Promise. Because window.onerror does not catch the error in the Promise. Yes, not even a script error. That is, onerror does not capture errors in promises. It should be that the early browser onerror design did not take into account the scenario where promises are directly run. But with try-catch encapsulation, we can still do it.


// 如果浏览支持Promise,捕获promise里面then的报错,因为promise里面的错误onerror和try-catch都无法捕获
if (Promise && Promise.prototype.then) {
    var promiseThen = Promise.prototype.then;
    
    Promise.prototype.then = function(resolve, reject) {
        return promiseThen.call(this, _wrapPromiseFunction(resolve), _wrapPromiseFunction(reject));
    }
}

/**
 * 输入一个函数,将函数内代码包裹进try-catch执行,then的resolve、reject和普通函数不一样
 * 
 * @param {any} fn 
 * @returns 一个新的函数
 */
function _wrapPromiseFunction(fn) {

    // 如果fn是个函数,则直接放到try-catch中运行,否则要将类的方法包裹起来,promise中的fn要返回null,不能返回空函数
    if (typeof fn !== 'function') {
        return null;
    }

    return function () {
        try {
            return fn.apply(this, arguments);
        } catch (e) {
            _errorProcess(e);
            throw e;
        }
    };
}

  At this point, both resolve and reject in the promise can run in the try-catch environment, and the errors inside can be successfully caught.

4. Summary

  To sum up, in fact, it is not much different from the original method. The try-catch method is still used to cover the prototype property of the React component for exception capture, which greatly increases the scope of error capture and not only helps us quickly locate problems in development. , which also catches runtime errors in React online applications.

Original address: http://jixianqianduan.com/frontend-weboptimize/2018/02/22/front-end-react-error-capture.html

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=325160484&siteId=291194637
Recommended