preface

With the development of the company’s business, there are more and more front-end projects. Sometimes a customer feedback problem, need to spend a lot of time to check. Error information can not be obtained at the first time, which can cause losses to the company. This is where we need an error collection mechanism to detect problems in the code ahead of time and fix them before the customer responds. Or when receiving customer feedback, we can find the corresponding error stack to help us quickly locate and solve the problem. The following mainly introduces the vUE and wechat small program error collection methods.

Error collection method

Vue error collection

Vue provides a globally configured errorHandler that collects errors that occur while Vue is running. API cn.vuejs.org/v2/api/#err…

Usage:

Vue.config.errorHandler = function (err, vm, info) {
  // handle error
  // 'err' is the js error stack information, can obtain the specific js error location.
  / / ` vm ` vue instance
  // 'info' is Vue specific error information, such as the lifecycle hook where the error is located
  // Available in 2.2.0+ only
}
Copy the code

Of course, after getting the vUE corresponding instance, we can easily get the vUE corresponding component name and custom attributes, etc. (here you can play freely).

Here’s how to get the component name: (Source: fundebug)

function formatComponentName(vm) {
    if (vm.$root === vm) return 'root';
    var name = vm._isVue
        ? (vm.$options && vm.$options.name) ||
        (vm.$options && vm.$options._componentTag)
        : vm.name;
    return (
        (name ? 'component <' + name + '>' : 'anonymous component') +
        (vm._isVue && vm.$options && vm.$options.__file
            ? ' at ' + (vm.$options && vm.$options.__file)
            : ' ')); }Copy the code

At this point, we can rewrite the code like this:

Vue.config.errorHandler = function(err, vm, info) {
    if (vm) {
        var componentName = formatComponentName(vm);
        // Call the error log collection interface
    } else {
        // Call the error log collection interface}};Copy the code

Now that you know how to collect error information, you can simply wrap it up and apply it to your project. (Of course, you also need an interface to save error messages and display error messages. There are some mature logging frameworks online, such as ELK.)

Create a new file debug.js

let debugConfig = {
    Vue: null.// Project name
    entryName: 'entryName'.// Script version
    scriptVersion: '1.0'./ / environment
    releaseStage: 'pro'
},
debug = {
    notifyWarn({ message, metaData }) {
        let type = 'caught',
            severity = 'warn';
        
        _logReport({ type, severity, message, metaData });
    },
    notifyError({ type = 'caught', error, message, metaData, lineNumber, columnNumber, fileName }){
        let severity = 'error'; _logReport({ type, severity, error, metaData, message, lineNumber, columnNumber, fileName }); }};// Log report
function _logReport({ type, severity, error, metaData, message, lineNumber, columnNumber, fileName }) {

    let { silentDev, Vue } = debugConfig;

    message = message || error && error.message || ' ';

    // Here we can do a grayscale control

    let { entryName, releaseStage, severity, scriptVersion } = debugConfig,
        name = error && error.name || 'error',
        stacktrace = error && error.stack || ' ',
        time = Date.now(),
        title = document.title,
        url = window.location.href,
        client = {
            userAgent: window.navigator.userAgent,
            height: window.screen.height,
            width: window.screen.width,
            referrer: window.document.referrer
        },
        pageLevel = 'p4';

    // This is where you can rank your pages
    pageLevel = 'p0';//getPageLevel();

    // The HTTP request uses vue-resource, which can be adjusted according to the situation
    Vue.http.post(logReportUrl, {
        entryName,
        scriptVersion,
        message,
        metaData,
        name,
        releaseStage,
        severity,
        stacktrace,
        time,
        title,
        type,
        url,
        client,
        lineNumber,
        columnNumber,
        fileName,
        pageLevel// Page level
    });

}

export default function(Vue, option = {}){

    debugConfig = Object.assign(debugConfig, { Vue, ... option });// If you don't want to catch errors in the development environment, you can add environment judgment here

    function formatComponentName(vm) {
        if (vm.$root === vm) return 'root';
        let name = vm._isVue
            ? (vm.$options && vm.$options.name) ||
            (vm.$options && vm.$options._componentTag)
            : vm.name;
        return (
            (name ? 'component <' + name + '>' : 'anonymous component') +
            (vm._isVue && vm.$options && vm.$options.__file
                ? ' at ' + (vm.$options && vm.$options.__file)
                : ' ')); } Vue.config.errorHandler =function(err, vm, info) {
        if (vm) {
            let componentName = formatComponentName(vm);
            let propsData = vm.$options && vm.$options.propsData;
            debug.notifyError({
                error: err,
                metaData: {
                    componentName,
                    propsData,
                    info,
                    userToken: { userId: 1 }//metaData can store additional data, such as user information}}); }else {
            debug.notifyError({
                error: err,
                metaData: {
                    userToken: { userId: 1 }//metaData can store additional data, such as user information}}); }};window.onerror = function(msg, url, lineNo, columnNo, error) {
        debug.notifyError({
            type: 'uncaught', 
            error, 
            metaData: {
                userToken: { userId: 1 }//metaData can store additional data, such as user information
            }, 
            message: msg, 
            lineNumber: lineNo, 
            columnNumber: columnNo, 
            fileName: url }); }}// Finally, we throw the debug out to be called elsewhere
export { debug }
Copy the code

Of course, you can also catch promises, network requests, images, and other exceptions. Recommend a more complete article here, everyone can go to juejin.cn/post/684490 by themselves…

Initialization:

// Collect error logs import debug from'./debug'; Use (ngmmdebug, {entryName:'webmall' });
Copy the code

If you want to report errors yourself, you can do so by:

import { debug } from './debug';
debug.notifyError({ messag: 'An error has occurred' });
Copy the code

Wechat small program error collection

Wechat small program to collect error information is also more convenient, just need to call the App function passed in the object to achieve onError method. Address: document developers.weixin.qq.com/miniprogram…

Usage:

App({
  onError (msg) {
    console.log(msg);// Error message}})Copy the code

If you want to make your code more portable you can do this:

// Save the App temporarily
let _App = App;

function HookParams(_appParams, eventName, eventFn) {
  if (_appParams[eventName]) {
    let _eventFn = _appParams[eventName];
    // If the onError function already exists in the argument, the error collection is retained and added
    _appParams[eventName] = function (error) {
      eventFn.call(this, error, eventName);
      return _eventFn.call.apply(_eventFn, [this].concat(Array.prototype.slice.call(arguments)))}}else {
    // If there is no onError function in the argument, it is easy to define one and add an error collection
    _appParams[eventName] = function (error) {
      eventFn.call(this, error, eventName)
    }
  }
}

function onErrorFn(error, eventName) {
  // Collect errors
}

App = function (_appParams) {
  HookParams(_appParams, "onError", onErrorFn);
  _App(_appParams);
};
Copy the code

The principle is actually very simple. You can rewrite the App function and handle the incoming parameters by Hook method.

Again, now that we know how to collect errors, we just need to wrap up our code a little.

Create a new file debug.js

function HookParams(_appParams, eventName, eventFn) {
  if (_appParams[eventName]) {
    let _eventFn = _appParams[eventName];
    _appParams[eventName] = function (error) {
      eventFn.call(this, error, eventName);
      return _eventFn.call.apply(_eventFn, [this].concat(Array.prototype.slice.call(arguments)))}}else {
    _appParams[eventName] = function (error) {
      eventFn.call(this, error, eventName)
    }
  }
}

function objToParam(options = {}) {
    let params = ' ';
    for (let key in options) {
        params += '&' + key + '=' + options[key];
    }
    return params.substring(1);
}

function onErrorFn(error, eventName) {
    _logReport(error);
}

// Save the App temporarily
let _App = App;

App = function (_appParams) {
  HookParams(_appParams, "onError", onErrorFn);
  _App(_appParams);
};

//config
let debugConfig = {
  entryName: 'entryName'.releaseStage: 'pro'.scriptVersion: '1.0'.client: {}}// Obtain the device information
wx.getSystemInfo({
  success: function (res) { debugConfig.client = res; }});/ / assembled postData
function getPostData(error = ' ') {
  let {
    entryName,
    releaseStage,
    scriptVersion,
    client
  } = debugConfig,
  curPage = getCurrentPages()[getCurrentPages().length - 1],
    url = ' ',
    urlParams = ' ',
    name = 'error',
    postData = "postData",
    metaData = {},
    pageLevel = 'p0';

  / / processing the url
  if (curPage) {
    url = curPage.route;

    // Here I grade the page according to the actual project
    pageLevel = 'p0'; //getPageLevel(url);

    urlParams = objToParam(curPage.options);
    if (urlParams) {
      url += '? ' + urlParams;
    }
  }

  name = error.split('\n') [0] | |'error';
  metaData = {
    userToken: getHeaders()
  }

  try {
    postData = {
      data: JSON.stringify({
        entryName,
        type: 'caught',
        scriptVersion,
        releaseStage,
        name,
        stacktrace: error,
        time: Date.now(),
        client,
        url,
        metaData,
        pageLevel,
        serviceLevel
      })
    };
  } catch (e) {
    console.error(e);
  }

  return postData;
}

// Control the sending of errors
function _logReport(error) {
    // The gray control is automatically added
    wx.request({
        header: {
        'content-type': 'application/x-www-form-urlencoded'
        },
        method: 'POST'.url: logReportUrl,
        data: getPostData(error)
    });
}

let debug = {
  init(option = {}) {
    debugConfig = Object.assign({}, debugConfig, option);
  },
  notifyError(error) {
    _logReport(error)
  }
}

module.exports = debug;
Copy the code

Modify the app. Js:

import ngmmdebug from './utils/ngmmdebug.js';
/ / initialization
ngmmdebug.init({
  entryName: 'mall-wxapp'.scriptVersion: '2.6.3'
})
Copy the code

Manual error reporting

import ngmmdebug from './utils/ngmmdebug.js';
ngmmdebug.notifyError('An error has occurred ~');
Copy the code

Small program has a few more special points can look at:

GetSystemInfo for device information, of course, you can also wx.getUserInfo for user information.

2, the small program complete link needs to be self-assembled through the page options property.

reference

Collection Field Reference

{"entryName":" project name ", "ScriptVersion ":"1.0",// scriptVersion" message":"aler is not defined",// error description "metaData":{// user-defined field "componentName":"anonymous" Component at/Users/taoxinhua/git/webmall/SRC/components/app. Vue ", / / component name "info", "created the hooks", / / the vue error message "UserToken ":{// user login information "user_id": 1}}, "name" : "ReferenceError," name "releaseStage" / / error: "local", / / an error environment pre | beta | local "stacktrace" : "ReferenceError: aler is not defined at VueComponent.created (webpack-internal:///370:32:9) at callHook (webpack-internal:///1:2666:21) at VueComponent.Vue._init (webpack-internal:///1:4227:5) at new VueComponent (webpack-internal:///1:4397:12) at createComponentInstanceForVnode (webpack-internal:///1:3679:10) at init (webpack-internal:///1:3496:45) at createComponent (webpack-internal:///1:5148:9) at createElm (webpack-internal:///1:5091:9) at Vue$3.patch [as __patch__] (webpack-internal:///1:5607:9) at Vue$3.vue._update (webpack-internal:///1:2415:19)",// error stack "Time ":1544437068009,// Client time when the error occurred "title":" mother preferred ",// page title" type":"caught",// Error type refer to fundebug "Url" : "http://localhost:3200/test", / / page address "client" : {/ / client information "userAgent" : "Mozilla / 5.0 (Macintosh; Intel Mac OS X 10_13_1) Chrome/70.0.3538.110 Safari/537.36", "height":800, "Width" : 1280, "the referrer" : "http://localhost:3200/test"}, "pageLevel" : "p4" / / level of a page fault, convenient query bug} and prioritize the main pageCopy the code

A better log collection system

If your system is small and you want to use it for free, try FunDebug.

conclusion

Collecting errors on the front end is relatively simple, and it’s up to you to figure out how to take advantage of the collected errors. If there is something wrong with the article, you can point it out in the comments section.

A signature

by:Tao