上篇文章分析了web 端错误及性能监控 SDK的实现方式,本篇则聚焦于小程序。尽管微信小程序本身就自带了监控能力,但如果我们需要更完善的数据,比如在出错时展示更多的信息如函数调用栈、行为轨迹、缓慢请求等,则需要自己去监控收集。
原来在开发这套前端监控 SDK 时,我将 web 和小程序的监控糅合在了一起,但后来发现平台差异使得同一个模块产生很多异化的逻辑,甚至在初始化的时候也要增加环境的判断,这种异化的处理降低了后续的可维护性,因此最终还是将 SDK 拆分成两套。
在开发小程序监控 SDK 时,首先明确的是,SDK 需要提供什么样的能力,或者说帮助我们获取什么数据。由于要获取的数据上篇已提及,这里看看我们设计的小程序端的 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"] }); }
拦截 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); } }); }
在小程序中,用户行为轨迹我定义为以下类型,并用队列保存:
export enum IBehaviorItemType { fn = "function", cOnsole= "console", http = "http", tap = "tap", custom = "custom" }
我们通过重写 API 就能获取以上的信息
值得注意的是,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 。