乐趣区

关于前端:前端监控监控SDK手摸手Teach微信小程序篇已开源

本文作者: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 = App
App = 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 = Page
Page = 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 = MitoInstance

const 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!!!

退出移动版