小程序的一次白屏、一次接口超时,可能就是一批用户的流失。更棘手的是,小程序运行在用户的微信环境里,线上问题往往发生在开发者根本看不见的角落:不同机型、不同基础库、不同网络下的行为千差万别。监控告警与错误上报就是给小程序装上的「黑匣子」和「仪表盘」——先能看见问题,才能谈解决问题。本文从错误捕获、上报链路、性能监控、告警配置到稳定性治理,给出完整的落地体系。
一、监控体系全景
1.1 小程序需要监控什么
| 监控维度 | 关键指标 | 反映的问题 |
|---|---|---|
| 崩溃与错误 | 崩溃率、JS 错误数、接口失败率 | 版本质量与稳定性 |
| 性能 | 启动耗时、首屏耗时、setData 耗时 | 用户体验劣化 |
| 网络 | 请求成功率、耗时分布、慢请求 | 后端与网络质量 |
| 业务 | 转化率、漏斗、关键事件 | 业务是否正常运转 |
| 运营 | 日活、留存、页面访问 | 大盘健康度 |
一句话:监控的最终目的不是「记录」,而是让团队在用户投诉之前就发现并修复问题。
1.2 监控的两种角色
- 实时告警:问题发生时立即通知到人,追求响应速度,对应线上 SLA。
- 离线分析:沉淀历史数据做趋势与归因,追求全面,对应版本迭代复盘。
两者缺一不可:没有告警,问题会蔓延;没有分析,问题会反复。
二、错误捕获:把异常一网打尽
2.1 全局兜底捕获
小程序提供了 App.onError 和 wx.onError 用于捕获未处理的异常,应在 app.js 启动时统一挂载:
// app.js 全局错误兜底
App({
onLaunch() {
// 捕获页面未处理异常
wx.onError((error) => {
reportError({
type: 'js_error',
message: error.message,
stack: error.stack,
page: currentPageRoute()
});
});
// 捕获小程序运行时的全局错误
wx.onUnhandledRejection((res) => {
reportError({
type: 'unhandled_rejection',
message: String(res.reason)
});
});
}
});
function currentPageRoute() {
const pages = getCurrentPages();
const current = pages[pages.length - 1];
return current ? current.route : 'unknown';
}
2.2 页面级错误补漏
wx.onError 只能捕获全局异常,页面回调里的错误(如 setData 抛错)建议在 Page 层做统一包装。更实用的是封装一套请求与渲染的守卫:
// utils/guard.js:统一 try-catch 包装异步逻辑
function safe(fn, context) {
return async function (...args) {
try {
return await fn.apply(this, args);
} catch (err) {
reportError({
type: 'async_error',
name: err.name,
message: err.message,
stack: err.stack,
context: context || 'unknown'
});
throw err;
}
};
}
// 使用示例:包住页面方法
Page({
onLoad: safe(function () {
// 页面初始化逻辑
}, 'home_onload')
});
2.3 接口错误分类捕获
接口失败是线上最常见的错误类型,需要区分「网络层失败」「协议层失败」「业务失败」并分别上报:
| 错误层 | 例子 | 上报重点 |
|---|---|---|
| 网络层 | 超时、无网络、DNS 失败 | 失败阶段、重试次数 |
| 协议层 | HTTP 5xx、JSON 解析失败 | 状态码、耗时 |
| 业务层 | code 非 0、业务校验失败 | 业务 code、错误信息 |
// 统一请求封装中的错误上报
function request(options) {
return new Promise((resolve, reject) => {
wx.request({
...options,
success(res) {
if (res.statusCode >= 500) {
reportError({
type: 'http_5xx',
url: options.url,
statusCode: res.statusCode,
costMs: res.costMs
});
} else if (res.data && res.data.code !== 0) {
reportError({
type: 'biz_error',
url: options.url,
bizCode: res.data.code
});
}
resolve(res.data);
},
fail(err) {
reportError({
type: 'network_fail',
url: options.url,
errMsg: err.errMsg
});
reject(err);
}
});
});
}
三、异常上报链路
3.1 上报 SDK 的设计
上报链路一般分为「采集 → 加工 → 批量发送 → 服务端落库」四步。客户端 SDK 要做三件事:抽样、合并、脱敏。
// utils/reporter.js:批量上报 + 本地缓冲
class Reporter {
constructor() {
this.queue = [];
this.maxBatch = 20;
this.flushInterval = 3000; // 3 秒批量发送一次
this.start();
}
push(payload) {
// 限流:单用户单日最多上报 200 条
const key = `report_count_${today()}`;
const count = wx.getStorageSync(key) || 0;
if (count >= 200) return;
wx.setStorageSync(key, count + 1);
// 脱敏:去掉 openid、手机号等敏感字段
payload.openid = mask(payload.openid);
this.queue.push(payload);
if (this.queue.length >= this.maxBatch) {
this.flush();
}
}
flush() {
if (this.queue.length === 0) return;
const batch = this.queue.splice(0);
wx.request({
url: 'https://monitor.example.com/report',
method: 'POST',
data: { logs: batch },
fail() {
// 失败时写回本地,下次补发
wx.setStorageSync('pending_logs', batch);
}
});
}
}
一句话:上报本身不能拖垮业务,抽样、合并、限流是把监控成本压在可接受范围的前提。
3.2 上报的采样与降级策略
| 场景 | 采样策略 | 原因 |
|---|---|---|
| JS 错误 | 全量上报 | 频率低、价值高 |
| 网络错误 | 全量上报 | 需要完整故障画像 |
| 性能数据 | 按 10%-50% 采样 | 数据量大、统计意义足够 |
| 调试日志 | 仅开发版上报 | 线上不采集避免噪音 |
四、性能监控关键指标
4.1 核心性能指标
小程序性能监控通常围绕「启动、渲染、交互」三条主线:
| 指标 | 采集方式 | 告警基线建议 |
|---|---|---|
| 冷启动耗时 | wx.getPerformance navigation 事件 | 均值 P75 < 3s |
| 首屏渲染 | 页面 onReady 与首帧埋点 | 均值 < 1.5s |
| setData 耗时 | 包装 setData 统计 | P95 < 100ms |
| 页面帧率 | 滚动/动画期间的 FPS 采样 | 低于 40fps 占比 < 10% |
| 内存占用 | wx.getPerformance memory | 峰值不持续增长 |
4.2 性能数据采集示例
// utils/perf.js:启动耗时采集
function reportLaunchPerformance() {
const perf = wx.getPerformance();
const nav = perf.getEntriesByType('navigation')[0];
if (!nav) return;
reportMetric('launch', {
appLaunch: nav.appLaunch, // 小程序启动到 onLaunch
pageRender: nav.pageRender, // 页面首次渲染
jsBundle: nav.jsBundle, // 代码注入
network: nav.network, // 首屏网络
ttfb: nav.ttfb
});
}
// 性能数据上报结构
{
"metric": "launch",
"page": "pages/index/index",
"values": {
"appLaunch": 320,
"pageRender": 780,
"jsBundle": 210
},
"device": {
"model": "iPhone 15",
"system": "iOS 18.0",
"wechat": "8.0.50"
},
"networkType": "wifi"
}
五、告警配置
5.1 告警分级
不是所有异常都要立刻叫醒值班人,告警必须分级:
| 级别 | 阈值示例 | 通知方式 | 响应时间 |
|---|---|---|---|
| P0 严重 | 崩溃率 > 2%、核心接口失败率 > 30% | 电话 + 短信 | 立即 |
| P1 紧急 | 错误率超过基线 2 倍、白屏率上升 | 企业微信 + 短信 | 15 分钟 |
| P2 一般 | 单接口失败率 > 5% | 企业微信群 | 2 小时 |
| P3 提示 | 指标趋势缓慢上升 | 日报汇总 | 次日 |
5.2 告警去重与收敛
线上异常通常是集群式爆发:一个版本缺陷导致成百上千个相同错误。告警必须去重,否则值班人会被消息淹没:
去重维度:错误指纹(message + stack 前几帧 + 页面)
收敛策略:同一指纹进入静默窗口(如 5 分钟),窗口内只发一次
升级策略:静默窗口内重复次数超过 N 次,自动升级 P 级
5.3 阈值动态基线
静态阈值在流量波动时会产生大量误报。更稳妥的做法是以历史基线为参照:告警条件是「当前值偏离基线超过 X 倍」,而非「绝对值超过 Y」:
// 告警判定伪代码
function shouldAlert(metric, currentValue, baseline, ratio = 2) {
if (baseline <= 0) return false;
return currentValue > baseline * ratio;
}
六、稳定性治理闭环
6.1 从发现到治理
告警只是起点,稳定性治理需要形成「发现 → 定位 → 修复 → 验证 → 预防」的闭环:
告警触发(发现)
↓ 聚合堆栈与上下文(定位)
↓ 修复并灰度发布(修复)
↓ 观察告警是否消失(验证)
↓ 补测试与回归用例(预防)
6.2 崩溃分析与版本归因
每次告警都应该能回答三个问题:哪个版本?哪次提交?影响多大? 因此上报数据必须带上版本号与构建号:
{
"appVersion": "1.2.1",
"buildNo": "20260930-001",
"commitSha": "a3f2c9e",
"grayGroup": "experiment"
}
结合灰度分桶信息,可以判断问题是新版本引入还是旧版本存量,从而决定是回退还是修复。
6.3 稳定性指标看板
| 指标 | 统计口径 | 健康线 |
|---|---|---|
| 崩溃率 | 崩溃用户数 / 活跃用户数 | < 0.5% |
| JS 错误率 | 错误页面数 / 页面浏览量 | < 1% |
| 接口成功率 | 成功请求 / 总请求 | > 99.5% |
| 告警响应时长 | 告警到确认的平均时长 | < 15 分钟 |
| 缺陷修复时长 | 发现到修复上线的平均时长 | < 8 小时 |
一句话:监控体系的价值最终由「平均修复时长」衡量,而不是「告警条数」。
七、总结
小程序的监控告警与错误上报,本质上是在「用户可见」和「开发者可见」之间架起桥梁。错误捕获要把所有异常类型都兜住,上报链路要兼顾完整性与成本,告警配置要分级、去重并基于动态基线,稳定性治理则要形成发现到预防的完整闭环。特别要强调的是:监控能力要与发布流程绑定,每个版本上线都要带着明确的监控看板和告警规则。结合小程序灰度发布与版本管理做版本归因,配合性能优化守住体验基线,再衔接后端接口设计定位服务端问题,才能构建真正可控的线上质量体系。
继续阅读
探索更多技术文章
浏览归档,发现更多关于系统设计、工具链和工程实践的内容。