本文作者:cjinhuo,未经受权禁止转载。

概要

已开源的前端监控SDK:mitojs,有趣味的小伙伴能够去瞅瞅~(SDK在线Demo)

<!-- 弄个动态图 -->

来到注释,本文分成三个局部

  • 背景
  • 微信小程序的监控实现
  • 微信小程序的埋点实现
  • 结尾

背景

接着前端监控系列的内容:

  • 前端监控:监控SDK手摸手Teach-架构篇(已开源)
  • 前端监控:监控SDK手摸手Teach-实现篇(已开源)

本文的次要目标讲下微信小程序(wx-mini)监控Web监控的区别,以及如何编写小程序的埋点和谬误监控

微信小程序的监控实现

微信小程序监控也是通过劫持微信官网抛出全局对象的办法,因为小程序的运行环境并没有windowdocument对象,它只裸露了一个wx全局对象,比方我要拦挡页面的ajax申请,在web端重写window.XMLHttpRequestfetch,在微信小程序端则须要重写wx.request

监控微信小程序的网络申请

微信小程序常见的网络申请有:

  • wx.request
  • wx.downloadFile
  • wx.uploadFile

所以咱们就重写这三个进行重写,并拿到入参:

enum WxXhrTypes {  request = 'request',  downloadFile = 'downloadFile',  uploadFile = 'uploadFile'}function monitorWxXhr(this: WxClient, notify: (eventName: WxEventTypes, data: any) => void) {  const hookMethods = Object.keys(WxXhrTypes)  const that = this  hookMethods.forEach((hook) => {    const originRequest = wx[hook]    Object.defineProperty(wx, hook, {      writable: true,      enumerable: true,      configurable: true,      value: function (...args: any[]) {        const options = args[0]        // 获取须要的数据信息        const { url, method, header, reqData } = options        // 收集小程序的申请信息        const httpCollect = {          request: {            httpType: HttpTypes.XHR,            url,            method,            data: reqData          },          response: {},          time: Date.now()        }        // 胜利回调        const successHandler = function (res) {          httpCollect.response.data = res.data          // 告诉订阅核心          notify(WxBaseEventTypes.REQUEST, httpCollect)          return options.success(res)        }        const _fail = options.fail        // 失败回调        const failHandler = function (err) {          // 零碎和网络层面的失败          httpCollect.errMsg = err.errMsg          // 告诉订阅核心          notify(WxBaseEventTypes.REQUEST, httpCollect)          return _fail(err)        }        const actOptions = {          ...options,          success: successHandler,          fail: failHandler        }        // return 原始函数        return originRequest.call(this, actOptions)      }    })  })}

残缺的代码请点击这里

监控小程序路由切换

首先要列出可能触发小程序路由切换的事件:

const enum WxRouteEvents {  SwitchTab = 'switchTab',  ReLaunch = 'reLaunch',  RedirectTo = 'redirectTo',  NavigateTo = 'navigateTo',  NavigateBack = 'navigateBack',  NavigateToMiniProgram = 'navigateToMiniProgram',  RouteFail = 'routeFail'}

下一步是对下面这些事件重写,就以NavigateTo为例:

  const methods = [WxRouteEvents.NavigateTo]  methods.forEach((method) => {    const originMethod = wx[method] as Function    Object.defineProperty(wx, method, {      writable: true,      enumerable: true,      configurable: true,      value: function (options) {        const toUrl = (options as WechatMiniprogram.SwitchTabOption).url        const data = {          from: getCurrentRoute(),          to: toUrl        }        notify(WxBaseEventTypes.ROUTE, data)        return originMethod.call(this, options)      }    })  })

其余的办法也是如此,不过须要留神失败回调信息的获取,残缺代码请点击这里

监控onerror

相似Webwindow.onerror,不过小程序返回的是不是一个谬误对象,而是一个字符串,须要自行解析其中的值(留神:小程序官网文档上写的是string,然而我也有在某个开发者电脑中呈现过Error对象)

const originApp = AppApp = function (appOptions: WechatMiniprogram.App.Option) {  replaceOld(    appOptions,    'onError',    function (originMethod: voidFun) {      return function (...args: any): void {        // 让本来的函数比抛出的hooks先执行,便于埋点判断是否反复        if (originMethod) {          originMethod.apply(this, args)        }        // 拿到args信息        // notify      }    },    true  )  return originApp(appOptions)}

重写App中的onError办法, 并拿到错误信息随后进行数据解析,残缺代码

获取小程序的tab、touch等事件

因为小程序是不能全局监听tabtouch等事件和获取不到页面的dom构造的,所以只能从办法参数动手。次要思路:所有的事件会有个e的参数,这个e中会有类型、节点信息等等,这个e个别状况是函数的第一个参数,所以只有重写Page下的所有函数,并判断函数的第一个参数是否蕴含typecurrentTarget,比方这样判断:e && e.type && e.currentTarget

const originPage = PagePage = function (options) {  const linstenerTypes = [LinstenerTypes.Touchmove, LinstenerTypes.Tap]  if (options) {    Object.keys(options).forEach((m) => {      if ('function' !== typeof options[m]) {        return      }      // 专用办法,便于重写操作      replaceOld(        options,        m,        function (originMethod: (args: any) => void) {          return function (...args: any): void {            const e = args[0]            // 判断是否是事件            if (e && e.type && e.currentTarget && !e.mitoWorked) {              if (linstenerTypes.indexOf(e.type) > -1) {                // 拿到e,e会包含class id data属性                // notify(e)              }            }            return originMethod.apply(this, args)          }        },        true      )    })  }  return originPage(pageOptions)}

在小程序中测试能够收集到的信息,如下图所示:

微信小程序的埋点实现

埋点其实就是在各种生命周期中存储信息和获取信息并将有用信息上报的过程。

场景一:须要上报页面的曝光时长和PV

做法:在页面刚加载时记录开始工夫startTime并将以后路由记住,在页面切换时用以后工夫减去开始工夫:Date.now() - startTime,并连带上个页面的路由上报

这里须要用到两个钩子函数:

  1. 页面加载时
  2. 页面卸载时

所以@mitojs/wx-mini提供了一些列的hooks供开发者调用,比方下面的两个hooks对应小程序的

  1. 页面加载时pageOnLoad
  2. 页面卸载时pageOnUnload

这时有人就要说了,这不是微信小程序自带的钩子函数么?是的,必定是微信自身是自带了这些钩子,我在能力重写并且抛出。失常状况下是每个Page上面都有很多钩子函数,如果不稍作解决,你须要在很多Page下写很多个钩子,而用了SDK后,只须要写一遍即可

埋点示例:

const MitoInstance = MITO.init({  pageOnShow,  pageOnHide})wx.MitoInstance = MitoInstanceconst currentPage = {  startTime: 0,  page: null}function pageOnShow(page) {  // 进入页面埋点  wx.MitoInstance.trackSend({    // 可自定义    actionType: 'PAGE',    route: page.route  })  currentPage.startTime = Date.now()  currentPage.page = page}function pageOnHide(page) {  // 来到页面埋点  const endTime = Date.now()  const elapsedTime = endTime - currentPage.startTime  // 拿到信息并上报  console.log('currentPage', currentPage)  wx.MitoInstance.trackSend({    // 可自定义    actionType: 'DURATION',    // 曝光工夫    elapsedTime,    // 页面路由    route: currentPage.page.route  })}

结尾

小结

小程序谬误监控和web类似,留神下在重写原生事件时留神下返回值,防止篡改原始值。

埋点次要是在各个hooks中拿到埋点信息并上报的一个过程,所以先拿到大部分的hooks,而后就能够DIY你的埋点。

开源

小程序监控文档,目前有局部人在用mitojs在做本人的监控平台或者埋点相干业务,如果你感兴趣能够,无妨过去瞅瞅

分割&内推

如果你对字节前端(内推)、谬误监控、埋点感兴趣、也间接分割我的微信:cjinhuo

Have A Good Day!!!