阅读 88

使用TS开发前端错误及性能监控SDK:小程序篇

前言

上篇文章分析了web端错误及性能监控SDK的实现方式,本篇则聚焦于小程序。尽管微信小程序本身就自带了监控能力,但如果我们需要更完善的数据,比如在出错时展示更多的信息如函数调用栈、行为轨迹、缓慢请求等,则需要自己去监控收集。

原来在开发这套前端监控SDK时,我将web和小程序的监控糅合在了一起,但后来发现平台差异使得同一个模块产生很多异化的逻辑,甚至在初始化的时候也要增加环境的判断,这种异化的处理降低了后续的可维护性,因此最终还是将SDK拆分成两套。

在开发小程序监控SDK时,首先明确的是,SDK需要提供什么样的能力,或者说帮助我们获取什么数据。由于要获取的数据上篇已提及,这里看看我们设计的小程序端的SDK需要提供什么能力。

SDK提供的能力

基础监控

  • 错误监控:包括js、promise、http
  • 性能监控:页面性能

附加能力

  • 缓慢HTTP请求监控
  • 用户行为轨迹监控
  • 错误过滤、错误抽样(本篇不作介绍,SDK已实现)

SDK在最终使用上依然采用基于事件订阅的方式,下面分析下这些能力在小程序端如何实现

错误监控

使用小程序生命周期提供了onError,重写Page即可

App = function (appOptions) {
  appHooks.forEach((methodName) => {
    const originMethod = appOptions[methodName];

    (appOptions as any)[methodName] = function (param: any) {
      const error = param as Error;

      if (methodName === "onError") {
        monitor.handleErrorEvent(TrackerEvents.jsError, error);
      }
      
      return originMethod && originMethod.call(this, param);
    };
  });

  return originApp(appOptions);
};
复制代码

对于promise错误,小程序提供onUnhandledRejection,但官方文档指出此事件当前在安卓平台并不会生效,因此需要做一下hack处理,通过console的劫持进行判断

export function rewriteConsole(monitor: Monitor) {
  for (const key of Object.keys(console)) {
    if (key in console) {
      const methodName = key as KeyofConsole;

      if (typeof console[methodName] !== "function") {
        continue;
      }

      if (!hackConsoleFn.includes(methodName)) {
        continue;
      }

      const originMethod = console[methodName];
      console[methodName] = function (...args: any[]) {
        /**
         * 若是安卓手机则在此捕获 unhandled promise rejection 错误
         */
        if (args[0] === "Unhandled promise rejection") {
          const error = args[1] as Error;

          monitor.getSystemInfo().then((res) => {
            const isNeedEmit = hackUnhandledRejectionPlatform.includes(
              res.platform
            );
            if (isNeedEmit) {
              monitor.handleErrorEvent(TrackerEvents.unHandleRejection, error);
            }
          });
        }

        originMethod.call(this, ...args);
      };
    }
  }
}
复制代码

性能监控

使用小程序提供的performance api

export function observePagePerformance(monitor: Monitor): void {
  const canIUse = wx.canIUse("Performance");

  if (monitor.performanceData.length) return;
  if (!canIUse) {
    return;
  }

  const performance = wx.getPerformance();

  const observer = performance.createObserver(
    (entryList: WechatMiniprogram.EntryList) => {
      const performanceData: PerformanceData = entryList.getEntries();
      // ,,,
    }
  );
  observer.observe({ entryTypes: ["render", "script"] });
}

复制代码

HTTP监控

拦截wx.request取值,并且对options.success及options.fail进行重写

export function interceptRequest(monitor: Monitor) {
  const originRequest = wx.request;
  Object.defineProperty(wx, "request", {
    configurable: false,
    enumerable: false,
    writable: false,
    value: function (options: WechatMiniprogram.RequestOption) {
      const originSuccess = options.success;
      const originFail = options.fail;

      options.success = function (...args) {
      };

      options.fail = function (...args) {
      };

      return originRequest.call(this, options);
    }
  });
}
复制代码

用户行为轨迹

在小程序中,用户行为轨迹我定义为以下类型,并用队列保存:

  • 函数调用
  • console信息
  • http请求
  • 页面元素点击
  • 埋点行为(自定义行为)
export enum IBehaviorItemType {
  fn = "function",
  console = "console",
  http = "http",
  tap = "tap",
  custom = "custom"
}
复制代码

我们通过重写API就能获取以上的信息

  • 函数调用:重写App,重写Page
  • console信息:重写console
  • http请求:重写wx.request
  • 页面元素点击:重写Page

值得注意的是,SDK要监控页面元素点击仍需要我们做些手动工作。

由于小程序不存在dom,并不具备类似web提供window.addEventListener的能力,通过重写Page只是为了给PageOptions注入一个事件处理方法onElementTrack,因此在页面根节点需要对元素做事件绑定才能触达SDK收集。

// index.wxml
<view class="container" catchtap="onElementTrack"></view>
复制代码

自定义行为

某些情况下,你需要收集特定地方的埋点行为,只需要在埋点处调用pushBehavior方法即可。

public pushBehaviorItem(item: IBehaviorItem): IBehaviorItem {
    if (!item.type) {
      item.type = IBehaviorItemType.custom;
    }

    if (!item.time) {
      item.time = Date.now();
    }

    const { queueLimit } = this.$options.behavior;

    if (this.behavior.length >= queueLimit) {
      this.behavior.shift();
    }

    this.behavior.push(item);

    return item;
}
复制代码

小程序SDK完整代码实现:欢迎star、fork、issue。

github.com/alex1504/fe…

如果本文对你有帮助,欢迎点赞、收藏及转发,也欢迎在下方评论区一起交流,你的支持是我前进的动力。

文章分类
前端
文章标签