小程序发布之后,用户开始使用,这时候开发者最担心什么?担心某个页面打不开了,担心某个按钮点了没反应,担心在某些机型上直接闪退。更让人焦虑的是,用户不会主动告诉你出了什么问题,他们只会默默地关掉小程序,再也不回来。
性能监控和错误上报,就是解决这个问题的工具。它让开发者在用户遇到问题的时候能够第一时间知道,甚至在用户自己都没意识到的时候,就已经收集到了线索。
线上监控不是越多越好,抓大放小才是正道。对于小程序来说,最值得关注的是三个维度。
第一个维度是错误。包括脚本报错、接口报错、页面渲染失败。这些会直接影响用户能不能正常使用。第二个维度是性能。包括页面打开速度、切换速度、滑动流畅度。慢不一定不能用,但会劝退用户。第三个维度是行为。用户走到哪一步就流失了,哪个按钮点击率异常低。这类数据帮助定位体验问题。
错误和性能用代码监控,行为用埋点分析,三者结合起来才有价值。
小程序提供了原生的错误监听接口。在app.js中声明onError方法,可以捕获大部分运行时的脚本错误。
// app.js App({ onError(error) { console.error('捕获到错误:', error) // 上报到自己的监控服务 this.reportError({ type: 'script', message: error.message, stack: error.stack, timestamp: Date.now() }) } })
onError能捕获的是脚本执行错误。对于接口请求错误,需要自己在封装的请求函数中统一处理。
function request(options) { return new Promise((resolve, reject) => { wx.request({ ...options, success: (res) => { if (res.statusCode >= 400) { reportError({ type: 'api', status: res.statusCode, url: options.url }) } resolve(res) }, fail: (err) => { reportError({ type: 'network', url: options.url, errMsg: err.errMsg }) reject(err) } }) }) }
还有一个容易被忽略的错误来源:Promise中未捕获的reject。小程序不会自动上报这类错误,需要开发者手动添加全局的unhandledrejection监听,但这个API在小程序环境支持度不一,稳妥的做法是在每个Promise链末尾都加上catch。
启动耗时是最重要的性能指标。从用户点击小程序图标,到首页内容渲染完成,这个时间决定了用户的第一印象。
小程序的性能面板和wx.getPerformance API可以获取到详细的启动阶段数据。
// 在首页的onReady中记录 const performance = wx.getPerformance() const observer = performance.createObserver((entryList) => { entryList.getEntries().forEach(entry => { if (entry.entryType === 'navigation') { // 上报启动耗时 reportPerformance({ type: 'launch', duration: entry.duration, path: entry.name }) } }) }) observer.observe({ entryTypes: ['navigation', 'render'] })
页面切换耗时同样重要。用户在列表中点击一项进入详情页,这个跳转过程如果超过300毫秒,就会有明显的等待感。在A页面的跳转代码处记录开始时间,在B页面的onReady中计算差值。
// 列表页 const startTime = Date.now() wx.navigateTo({ url: '/pages/detail/detail?id=123', events: { pageReady: () => { const duration = Date.now() - startTime reportPerformance({ type: 'navigate', duration, from: 'list', to: 'detail' }) } } }) // 详情页 onReady() { const eventChannel = this.getOpenerEventChannel() if (eventChannel) { eventChannel.emit('pageReady') } }
界面流畅度可以用帧率来衡量。scroll-view组件提供了bindscroll事件,可以记录滚动时间戳,但计算精确帧率比较麻烦。一个简化的做法是监控滚动事件触发的频率,正常情况下每帧都会触发一次,如果频率明显下降,说明存在掉帧。
错误和性能数据告诉开发者在哪出问题,行为数据帮助理解用户做了什么才触发这些问题。
在关键的页面和按钮上做埋点:
// 封装一个埋点函数 function track(eventName, properties = {}) { const data = { event: eventName, properties: properties, page: getCurrentPages().pop()?.route, timestamp: Date.now(), sessionId: getSessionId() // 会话标识 } // 发送到分析平台 sendToAnalytics(data) } // 使用示例 onLoad() { track('page_view', { from: this.options.from }) } onTapBuy() { track('click_buy', { productId: this.data.product.id, price: this.data.product.price }) }
行为数据的一大价值是计算转化漏斗。比如商品详情页到下单页的转化率突然下降,可能是下单按钮出了问题。结合错误监控数据,就能快速定位。
实时上报会带来额外的网络请求,密集的埋点甚至可能影响小程序本身的性能。常用的优化策略是批量上报。
在内存中维护一个队列,累积到一定数量(比如10条)或者间隔一定时间(比如5秒),再将队列中的数据打包一次性发送。
class Reporter { constructor() { this.queue = [] this.timer = null } add(data) { this.queue.push(data) if (this.queue.length >= 10) { this.flush() } else if (!this.timer) { this.timer = setTimeout(() => this.flush(), 5000) } } flush() { if (this.queue.length === 0) return const data = this.queue.slice() this.queue = [] if (this.timer) clearTimeout(this.timer) this.timer = null wx.request({ url: 'https://monitor.example.com/report', method: 'POST', data: { events: data } }) } }
上报请求本身也可能失败。这时候应该放弃这批数据,而不是无限重试。因为上报失败意味着当前网络环境可能很差,继续重试只会增加负担。丢数据总比影响主功能好。
注意上报的数据量。每条上报信息附带用户标识、设备信息、页面路径等基础字段是必要的,但不要在单个事件中携带整个data对象的副本。把无关的冗余数据去掉。
自己搭建一整套监控系统工作量大,大部分小程序团队会选择现成的工具。
微信小程序后台自带数据分析功能,包括访问分析、实时日志、错误日志。优点是零接入成本,缺点是功能有限、数据不开放、不支持自定义维度的聚合查询。
腾讯云和阿里云都有小程序监控产品,提供错误聚类、性能分析、用户行为追踪等功能。接入方式通常是在项目中引入一个SDK,配置好AppKey即可。
开源方案有Sentry,它提供了小程序SDK,支持sourcemap解析,错误堆栈可读性高。适合已经有自建Sentry服务的团队。
数据收集只是第一步。只有建立起从监控到修复的闭环,监控才有意义。
设置合理的告警阈值。启动耗时超过3秒的占比超过5%,触发告警。报错率超过1%,触发告警。告警要发给对的人,不要半夜吵醒所有人。
每周复盘监控数据,找出最频繁的三个错误,安排修复。下个月再看这三个错误的出现频率是否下降。
监控不是上线后才做的事。从开发阶段就应该接入测试环境的上报,让测试人员跑出来的问题也能被记录和分析。