前端错误收集(Vue.js微信小程序)

Posted Fundebug

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了前端错误收集(Vue.js微信小程序)相关的知识,希望对你有一定的参考价值。

摘要: 在客户反馈问题之前把BUG提前处理掉。

  • 原文:前端错误收集(Vue.js、微信小程序)

Fundebug经授权转载,版权归原作者所有。

前言

随着公司业务的发展,前端项目也越来越多。有的时候客户反馈的一个问题,需要耗费大量的时间去查。错误信息不能第一时间获取,多少会给公司带来损失。这个时候我们就需要一套错误收集机制,去提前发现代码中存在的问题,在客户反馈之前把问题提前处理掉。或者在收到客户反馈的时候可以查到对应的错误栈来帮助我们快速去定位以及解决问题。下面主要介绍vue和微信小程序错误收集的方法。

Vue错误收集

Vue提供了一个全局配置errorHandler,用于收集Vue运行时发生的错误。

用法:

Vue.config.errorHandler = function (err, vm, info) {
 // handle error
 //`err`是js错误栈信息,可以获取到具体的js报错位置。
 //`vm` vue实例
 //`info` 是 Vue 特定的错误信息,比如错误所在的生命周期钩子
 // 只在 2.2.0+ 可用
}

当然拿到vue对应的实例之后,我们就可以轻松获取到vue对应的组件名称以及自定义属性等等(此处可自由发挥)。

以下是获取组件名称的方法:(来源: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)
           : '')
   );
}

这个时候我们代码就可以改写成这样:

Vue.config.errorHandler = function(err, vm, info) {
   if (vm) {
       var componentName = formatComponentName(vm);
       //调用错误日志收集接口
   } else {
       //调用错误日志收集接口
   }
};

知道了怎么收集错误信息,接下来只需要简单的包装一下就可以应用到项目中去了。(当然你还需要一个保存错误信息的接口和展示错误信息的界面,网上也有一些成熟的日志处理框架,类似:ELK)

新建一个文件debug.js

let debugConfig = {
   Vue: null,
   //项目名称
   entryName: 'entryName',
   //脚本版本
   scriptVersion: '1.0',
   // 环境
   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 });
   }
};

// 日志上报
function _logReport({ type, severity, error, metaData, message, lineNumber, columnNumber, fileName }) {

   let { silentDev, Vue } = debugConfig;

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

   //这里可以做一个灰度控制

   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';

   //此处可以给你的页面进行分级
   pageLevel = 'p0';//getPageLevel();

   //此处http请求使用的是vue-resource,可以根据各自的情况进行调整
   Vue.http.post(logReportUrl, {
       entryName,
       scriptVersion,
       message,
       metaData,
       name,
       releaseStage,
       severity,
       stacktrace,
       time,
       title,
       type,
       url,
       client,
       lineNumber,
       columnNumber,
       fileName,
       pageLevel//页面等级
   });

}

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

   debugConfig = Object.assign(debugConfig, { Vue, ...option });
   
   //如果你想在开发环境不去捕获错误信息 可以在此处加上环境判断

   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可以存一些额外数据,比如:用户信息等
               }
           });
       } else {
           debug.notifyError({
               error: err,
               metaData: {
                   userToken: { userId: 1 }//metaData可以存一些额外数据,比如:用户信息等
               }
           });
       }
   };
   
   window.onerror = function(msg, url, lineNo, columnNo, error) {
       debug.notifyError({
           type: 'uncaught',
           error,
           metaData: {
               userToken: { userId: 1 }//metaData可以存一些额外数据,比如:用户信息等
           },
           message: msg,
           lineNumber: lineNo,
           columnNumber: columnNo,
           fileName: url
       });
   }

}

//最后我们把debug抛到外面供其他地方调用
export { debug }

当然你还可以捕获Promise、网络请求、图片等异常。此处推荐一篇比较全的文章,大家可自行去查看juejin.im/post/5bd2db…

初始化:

//错误日志收集
import debug from './debug';
//初始化错误处理
Vue.use(ngmmdebug, { entryName: 'webmall' });

如果你想自己上报错误可以通过:

import { debug } from './debug';
debug.notifyError({ messag: '发生错误了' });

微信小程序错误收集

用法:

App({
 onError (msg) {
   console.log(msg);//msg就是报错信息
 }
})

如果你想让代码移植性更高一点可以通过这样做:

//将App暂存起来
let _App = App;

function HookParams(_appParams, eventName, eventFn) {
 if (_appParams[eventName]) {
   let _eventFn = _appParams[eventName];
   //如果参数中已经存在onError函数,则保留并且添加错误收集
   _appParams[eventName] = function (error) {
     eventFn.call(this, error, eventName);
     return _eventFn.call.apply(_eventFn, [this].concat(Array.prototype.slice.call(arguments)))
   }
 } else {
   //如果参数中不存在onError函数,那比较简单直接定义一个,并且加入错误收集
   _appParams[eventName] = function (error) {
     eventFn.call(this, error, eventName)
   }
 }
}

function onErrorFn(error, eventName) {
 //收集错误
}

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

原理其实很简单,通过改写App函数,再通过Hook的手段来处理传入的参数即可。

同样,我们现在已经知道了如何收集错误,接下来只需要简单包装一下 我们的代码即可。

新建一个文件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);
}

// 将App暂存起来
let _App = App;

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

//config
let debugConfig = {
 entryName: 'entryName',
 releaseStage: 'pro',
 scriptVersion: '1.0',
 client: {}
}

//获取设备信息
wx.getSystemInfo({
 success: function (res) {
   debugConfig.client = res;
 }
});

//拼装postData
function getPostData(error = '') {
 let {
   entryName,
   releaseStage,
   scriptVersion,
   client
 } = debugConfig,
 curPage = getCurrentPages()[getCurrentPages().length - 1],
   url = '',
   urlParams = '',
   name = 'error',
   postData = "postData",
   metaData = {},
   pageLevel = 'p0';

 //处理url
 if (curPage) {
   url = curPage.route;

   //此处自己根据实际项目给页面定级
   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;
}

//控制错误的发送
function _logReport(error) {
   //灰度控制自行加
   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;

修改app.js:

import ngmmdebug from './utils/ngmmdebug.js';
//初始化
ngmmdebug.init({
 entryName: 'mall-wxapp',
 scriptVersion: '2.6.3'
})

手动上报错误

import ngmmdebug from './utils/ngmmdebug.js';
ngmmdebug.notifyError('发生错误了~');

小程序有几个比较特别的点可以看一下:

  • 通过wx.getSystemInfo获取设备信息,当然你还可以通过wx.getUserInfo 获取用户信息等。

  • 小程序完整的链接需要通过页面options属性自行组装。

参考

收集字段参考

比较好用的日志收集系统

如果你不想自己搭系统的话,可以试试Fundebug

总结

前端收集错误还是比较简单的,如何利用好这些收集来的错误就需要自己慢慢去琢磨了。文中如果有哪些不对的地方可以在评论区指出。

署名

by:Tao

以上是关于前端错误收集(Vue.js微信小程序)的主要内容,如果未能解决你的问题,请参考以下文章

前端 Vue.js 快速开发微信小程序,基于 Vue 的小程序开发框架:Megalo发布!

小程序开发用啥框架

微信小程序的动画效果

微信小程序的动画效果

微信小程序代码片段

微信小程序代码片段分享