背景
传统方式下一个前端项目发到正式环境后,所有报错信息只能通过用户使用时截图、口头描述发送到开发者,然后开发者来根据用户所描述的场景去模拟这个错误的产生,这效率肯定超级低,所以很多开源或收费的前端监控平台就应运而生,我司用的一款webfunny,代码加密,没法二次扩展,而且只能接入web vue版本,本节先说说微信小程序端搭建监控sdk
做之前我也调研过其他很多开源的一些sdk及服务,但都没有满足我司需求:
监控sdk:monitor-sdk
先来讲讲js 错误收集吧
小程序文档中是靠着APP中的onError来收集的,如何扩展到sdk,我这边方法是重写->发布订阅来收集错误,当然方法有很多,大家都可以尝试
发布订阅
const handlers = {}
const appHandles = {}
const pageHandles = {}
/**
* @param {*} handler
* @param {*} handleType
*/
export function subscribeEvent(handler, handleType = '') {
if (!handler) {
return
}
switch (handleType) {
case 'app':
appHandles[handler.type] = appHandles[handler.type] || []
appHandles[handler.type].push(handler.callback)
break
case 'page':
pageHandles[handler.type] = appHandles[handler.type] || []
pageHandles[handler.type].push(handler.callback)
break
default:
handlers[handler.type] = handlers[handler.type] || []
handlers[handler.type].push(handler.callback)
}
}
/**
* @param {*} type
* @param {*} data
* @param {*} handleType
*/
export function triggerHandlers(type, data, handleType = '') {
switch (handleType) {
case 'app':
if (!type || !appHandles[type]) return
appHandles[type].forEach((callback) => {
callback(data)
})
break
case 'page':
if (!type || !pageHandles[type]) return
pageHandles[type].forEach((callback) => {
callback(data)
})
break
default:
if (!type || !handlers[type]) return
handlers[type].forEach((callback) => {
callback(data)
})
}
}
重写对象上面某个属性:
/**
* 重写对象上面的某个属性
* @param {*} source 需要被重写的对象
* @param {*} name 需要被重写对象的key
* @param {*} replacement 以原有的函数作为参数,执行并重写原有函数
* @param {*} isForced
*/
export function replaceOld(source, name, replacement, isForced = false) {
if (name in source || isForced) {
const original = source[name]
const wrapped = replacement(original)
if (typeof wrapped === 'function') {
source[name] = wrapped
}
}
}
重写APP:
const HandleWxPageEvents = {
onLoad() {
let vm = this.wxMonitor,
toUrl = util.getPage()
let data = {
simpleUrl: toUrl,
referrer: vm.referrerPage || "",
}
vm.logSave('page_pv', data)
vm.referrerPage = toUrl
}
}
export function replaceApp(wxMonitor) {
if (!App) {
return
}
HandleWxAppEvents.wxMonitor = wxMonitor
const originApp = App
App = function (appOptions) {
let methods = config.APP_CONFIG
methods.forEach((method) => {
addReplaceHandler({
callback: (data) => HandleWxAppEvents[method.replace('AppOn', 'on')](data),
type: method
}, 'app')
replaceOld(
appOptions,
method.replace('AppOn', 'on'),
function (originMethod) {
return function (...args) {
triggerHandlers.apply(null, [method, ...args, 'app'])
if (originMethod) {
originMethod.apply(this, args)
}
}
},
true
)
})
return originApp(appOptions)
}
}
当然Page 页面首页pv和收集错误是一样的,这边不一一贴代码了
如何收集wx.request 来做请求数,请求耗时呢
这块可以发现wx.request 可以使用Object.defineProperty来重新给这个对象附上描述,贴代码吧:
/**
* 代理请求
* @param {*} wxMonitor
*/
export function replaceNetwork(wxMonitor) {
let vm = wxMonitor
let WxHookMethods = config.WxHookMethods
WxHookMethods.forEach(hook => {
let originRequest = wx[hook];
Object.defineProperty(wx, hook, {
writable: true,
enumerable: true,
configurable: true,
value: function () {
let args = [];
let startTime = new Date().getTime()
for (let _i = 0; _i < arguments.length; _i++) {
args[_i] = arguments[_i];
}
let options$1 = args[0];
let url = options$1.url || ""
let reqData;
if (hook === 'request') {
reqData = options$1.data;
}
let successHandler = function (res) {
try {
// 上报接口报警
if (!!res && res.statusCode && res.statusCode != 200) {
let data = {
simpleUrl: util.getPage(),
httpUrl: options$1.url || "",
httpUploadType: config.HTTP_ERROR,
responseText: JSON.stringify(res),
httpStatus: res.statusCode
}
if (!!url && url != `${vm.queue.baseUrl}${vm.queue.api}`) {
vm.logSave('http_log', data)
}
} else {
let endTime = new Date().getTime()
let consumeData = {
simpleUrl: util.getPage(),
loadTime: endTime - startTime,
httpUrl: options$1.url || "",
httpUploadType: config.HTTP_SUCCESS,
responseText: JSON.stringify(res),
httpStatus: res.statusCode || 200
}
if (!!url && url != `${vm.queue.baseUrl}${vm.queue.api}`) {
vm.logSave('http_log', consumeData)
}
}
} catch (e) {
util.warn('[cloudMonitor] http error')
}
if (typeof options$1.success === 'function') {
return options$1.success(res);
}
};
let failHandler = function (err) {
try {
let data = {
simpleUrl: util.getPage(),
httpUrl: options$1.url || "",
httpUploadType: config.HTTP_ERROR,
responseText: JSON.stringify(err),
httpStatus: '0'
}
if (!!url && url != `${vm.queue.baseUrl}${vm.queue.api}`) {
vm.logSave('http_log', data)
}
} catch (e) {
util.warn('[cloudMonitor] http error')
}
if (typeof options$1.fail === 'function') {
return options$1.fail(err);
}
};
let actOptions = util.__assign(util.__assign({}, options$1), { success: successHandler, fail: failHandler });
return originRequest.call(this, actOptions);
}
})
})
}
收集信息平台展示
后面文章我会讲下服务端搭建
服务端web:github.com/fonitor/web…
服务端server:github.com/fonitor/web…