微信小程序中通用埋点信息的统一采集

在本文开始之前,我假定你已经实现了一个用于上报埋点信息的函数pushLog, 毕竟本文的主题是一些通用埋点信息的统一采集,不是上报,说起通用埋点信息,那么哪些是通用埋点信息呢?比如用户进入小程序某一个页面总共呆了多长时间,什么时候进来的,什么时候离开的,再比如用户在小程序获取某个信息发起的请求是成功了还是失败了如果成功花费了多长的时间等等,如果我们在每个函数调用的地方单独的去实现这些信息的采集未免太过繁琐,所以我们不得不考虑在某个公共的地方去统一的手机这些信息并上报。

下面我们从网络请求,上传图片,生命周期等函数着手,一步步实现通用埋点信息的统一采集。

在开始之前我们需要明确一点,信息的统一采集是通过重写小程序默认api的方式进行的。我们可以根据覆盖目标的不同将其分为直接调用式的和生命周期式的。

1、直接调用式

这里和直接调用相关的api主要是wx.request,wx.uploadFile,一个负责发起请求,一个专职上传图片。

(1)发起请求
对于请求相关的,我们主要采集的内容是发起请求到响应的花费时间还有就是请求成功与否,通过wx.request的success和fail回调我们可以知道请求是否成功了,对于请求耗时我们可以放在complete回调中执行,因为成功或失败我们都需要统计时间,下面是具体的代码:

function getNewRequest(request) {
  return function(opt) {
    // 请求开始时间
    const startTime = Date.now();

    const { url } = opt;

    request({
      ...opt
      success: (result) => {
        if (success) {
          success(result);
        }

        // todo 发送埋点前需要检查当前url是不是埋点上报的地址 如果是则不发送
        pusgLog({
          url,
          name: 'InterfaceSuccess',
          cost: Date.now() - startTime
        });
      },
      fail: (error) => {
        if (fail) {
          fail(error);
        }

        // todo 发送埋点前需要检查当前url是不是埋点上报的地址 如果是则不发送
        pusgLog({
          url,
          name: 'InterfaceFail',
          cost: Date.now() - startTime,
          ...error
        });
      }
    });
  };
}

wx.request = getNewRequest(wx.request);

上面需要注意的是一定要排除掉发送埋点相关的url

(2) 上传图片,上传图片相关的埋点采集和上面的发起请求是一样的,这里直接贴出代码:

function getNewUploadFile(uploadFile) {
  return function(opt) {
    // 请求开始时间
    const startTime = Date.now();

    const { url } = opt;

    uploadFile({
      ...opt,
      success: (result) => {
        if (success) {
          success(result);
        }
        pushLog({
          url,
          name: 'CompleteUploadEachPhoto',
          cost: Date.now() - startTime
        });
      },
      fail: (error) => {
        if (fail) {
          fail(error);
        }

        pushLog({
          url,
          name: 'FailedUploadEachPhoto',
          cost: Date.now() - startTime,
          ...error
        });
      }
    });
  };
}

wx.uploadFile = getNewRequest(wx.uploadFile);

2、生命周期式的

对于生命周期式的,我们主要的目标是重写App和Page类下的生命周期函数,其实本质还是覆盖函数,所以和上面的直接调用式的没啥太大的区别。

(1)重写App

function getNewApp() {
  const oldApp = App;

  return (args) => {
    const { onLaunch, onShow, onHide, onError, onPageNotFound } = args;

    // 重写onLaunch
    args.onLaunch = function(opt) {
      // 自定义行为

      pushLog({
        page: opt.path,
        name: 'app-launch'
      });

      if (onLaunch) {
        return onLaunch.call(this, opt);
      }
    };

    // 重写onShow
    args.onShow = function(opt) {
      // 自定义行为

      pushLog({
        page: opt.path,
        name: 'app-show'
      });

      if (onShow) {
        return onShow.call(this, opt);
      }
    };

    // 重写onHide
    args.onHide = function(opt) {
      // 自定义行为

      pushLog({
        page: opt.path,
        name: 'app-hide'
      });

      if (onHide) {
        return onHide.call(this, opt);
      }
    };

    // 重写onError
    args.onError = function(opt) {
      // 自定义行为

      pushLog({
        page: opt.path,
        name: 'app-error'
      });

      if (onError) {
        return onError.call(this, opt);
      }
    };

    // 重写onPageNotFound
    args.onPageNotFound = function(opt) {
      // 自定义行为

      pushLog({
        page: opt.path,
        name: 'app-page-not-found'
      });

      if (onPageNotFound) {
        return onPageNotFound.call(this, opt);
      }
    };

    return oldApp.call(this, args);
  };
}

App = getNewApp();

(2)重写Page

function getNewPage() {
  const oldPage = Page;

  return (args) => {
    const {
      onLoad,
      onReady,
      onShow,
      onHide,
      onUnload,
      onPullDownRefresh,
      onReachBottom,
      onShareAppMessage,
      onPageScroll,
      onResize,
      onTabItemTap
    } = args;

    // 重写 onLoad
    args.onLoad = function(opt) {
      this.loadTime = Date.now();

      // 自定义行为

      pushLog({
        page: this.route,
        name: 'page-loaded',
        time: this.loadTime
      });

      if (onLoad) {
        return onLoad.call(this, opt);
      }
    };

    // 重写 onReady
    args.onReady = function(opt) {
      this.readyTime = Date.now();

      // 自定义行为

      pushLog({
        page: this.route,
        name: 'page-dom-loaded',
        cost: this.readyTime - this.loadTime
      });

      if (onReady) {
        return onReady.call(this, opt);
      }
    };

    // 重写 onShow
    args.onShow = function(opt) {
      this.showTime = Date.now();

      // 自定义行为

      pushLog({
        page: this.route,
        name: 'page-visible',
        time: this.showTime
      });

      if (onShow) {
        return onShow.call(this, opt);
      }
    };

    // 重写onHide
    args.onHide = function(opt) {
      this.hideTime = Date.now();

      // 自定义行为

      pushLog({
        page: this.route,
        name: 'page-duration',
        cost: this.hideTime - this.showTime
      });

      if (onHide) {
        return onHide.call(this, opt);
      }
    };

    // 重写 onUnload
    args.onUnload = function(opt) {
      this.unloadTime = Date.now();

      // 自定义行为

      pushLog({
        page: this.route,
        name: 'page-unloaded',
        cost: this.hideTime - this.showTime
      });

      if (onUnload) {
        return onUnload.call(this, opt);
      }
    };

    // 重写 onPullDownRefresh
    args.onPullDownRefresh = function(opt) {
      this.pullDownRefreshTime = Date.now();

      // 自定义行为

      if (onPullDownRefresh) {
        return onPullDownRefresh.call(this, opt);
      }
    };

    // 重写 onReachBottom
    args.onReachBottom = function(opt) {
      this.reachBottomTime = Date.now();

      // 自定义行为

      if (onReachBottom) {
        return onReachBottom.call(this, opt);
      }
    };

    // 重写 onShareAppMessage
    args.onShareAppMessage = function(opt) {
      this.shareAppMessageTime = Date.now();

      // 自定义行为

      instance.addPageEvent({
        page: this.route,
        name: 'share-app-message',
        time: this.shareAppMessageTime,
      });

      if (onShareAppMessage) {
        return onShareAppMessage.call(this, opt);
      }
    };

    // 重写 onPageScroll
    args.onPageScroll = function(opt) {
      this.pageScrollTime = Date.now();

      // 自定义行为

      if (onPageScroll) {
        return onPageScroll.call(this, opt);
      }
    };

    // 重写 onResize
    args.onResize = function(opt) {
      this.resizeTime = Date.now();

      // 自定义行为

      if (onResize) {
        return onResize.call(this, opt);
      }
    };

    // 重写 onTabItemTap
    args.onTabItemTap = function(opt) {
      this.tabItemTapTime = Date.now();

      // 自定义行为

      if (onTabItemTap) {
        return onTabItemTap.call(this, opt);
      }
    };

    return oldPage.call(this, args);
  };
}

Page = getNewPage();

这里需要注意的是在重写App和重写Page时的生命周期方法一定要返回,不然某些功能会出问题,比如分享功能。

将上面的稍微代码组织下,比如将重写请求的代码放在override/request.js,重写App的代码放在override/app.js,重写Page的代码放在override/page.js中,然后以override.js为重写入口文件并导入调用上面的重写方法,再在小程序入口文件app.js导入overrider.js方法即可使用我们重写过的App,Page,wx.request.wx.requestFile,并自动采集我们需要的信息。

  • 支付宝二维码 支付宝
  • 微信二维码 微信
相关文章