上篇文章分析了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 。
这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。
V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。
V2EX is a community of developers, designers and creative people.