主题切换
异常监控与日志
预计阅读 15 分钟
版本信息
最后更新:2026-07-09 · 适用基础库:≥ 3.9.0(最低兼容 ≥ 2.30.0) · 微信开发者工具:稳定版 1.06+
异常监控是线上游戏稳定运行的保障。本章讲解微信小游戏中异常捕获、日志上报和性能监控的实现方案。
异常捕获体系
typescript
class ErrorMonitor {
private enabled = true
init(): void {
// 1. 全局 JS 错误
wx.onError((error: string) => {
this.report({
type: 'jsError',
message: error,
stack: '',
timestamp: Date.now(),
})
})
// 2. 未处理的 Promise 拒绝
wx.onUnhandledRejection((res: any) => {
this.report({
type: 'unhandledRejection',
message: String(res.reason),
stack: '',
timestamp: Date.now(),
})
})
// 3. 全局 try-catch 包装(游戏主循环)
this.wrapGameLoop()
}
/** 安全的游戏循环包装 */
private wrapGameLoop(): void {
const originalLoop = requestAnimationFrame
let lastFrame = Date.now()
const safeLoop = (cb: FrameRequestCallback): number => {
return originalLoop.call(null, (time) => {
try {
cb(time)
// 帧耗时监控
const frameDuration = Date.now() - lastFrame
if (frameDuration > 30) {
// 超过 30ms 视为慢帧
this.reportSlowFrame(frameDuration)
}
lastFrame = Date.now()
} catch (err) {
this.report({
type: 'gameLoopError',
message: String(err),
stack: (err as Error).stack || '',
timestamp: Date.now(),
})
}
})
}
// 替换全局 requestAnimationFrame(微信小游戏环境中使用 globalThis)
;(globalThis as any).requestAnimationFrame = safeLoop
}
/** 上报异常数据 */
private report(payload: {
type: string
message: string
stack: string
timestamp: number
}): void {
if (!this.enabled) return
const systemInfo = wx.getSystemInfoSync()
const reportData = {
...payload,
platform: systemInfo.platform,
system: systemInfo.system,
brand: systemInfo.brand,
model: systemInfo.model,
sdkVersion: systemInfo.SDKVersion,
appVersion: '1.0.0', // 当前游戏版本号
openId: '', // 从登录态获取
}
// 发送到服务端或第三方监控平台
this.sendToServer(reportData)
// 本地缓存,防止网络异常时丢失
this.cacheLocally(reportData)
}
private sendToServer(data: Record<string, unknown>): void {
wx.request({
url: 'https://your-monitor-api.com/errors',
method: 'POST',
data,
// 不阻塞游戏渲染
complete: () => {},
})
}
/** 本地缓存未上报成功的日志 */
private cacheLocally(data: Record<string, unknown>): void {
try {
const cached = wx.getStorageSync('errorLogCache') || []
cached.push(data)
// 最多缓存 100 条
if (cached.length > 100) cached.splice(0, cached.length - 100)
wx.setStorageSync('errorLogCache', cached)
} catch {
// 存储满则放弃缓存
}
}
/** 慢帧上报(采样,避免频繁上报)*/
private slowFrameCounter = 0
private reportSlowFrame(duration: number): void {
this.slowFrameCounter++
if (this.slowFrameCounter % 10 === 0) {
// 每 10 次上报一次
this.report({
type: 'slowFrame',
message: `帧耗 ${duration}ms`,
stack: '',
timestamp: Date.now(),
})
}
}
}微信原生日志 API
除了自定义的异常捕获体系,微信小游戏还提供了官方的日志方案,适合不同场景。
三种官方日志 API 对比
| API | 基础库要求 | 数据去向 | 适用场景 |
|---|---|---|---|
wx.getLogManager() | 早期即支持 | 本地缓存(最多 5MB),用户反馈时上传 | 常规业务日志、Debug 日志 |
wx.getRealtimeLogManager() | ≥ 2.14.4 | 实时上传到小游戏后台 | 关键流程(支付、启动)、线上问题排查 |
wx.getGameLogManager() | ≥ 3.7.4 | 实时汇聚至「游戏日志分析」页 | 推荐:专为小游戏设计的实时日志方案 |
基础用法
typescript
// ===== 1. 常规日志(本地缓存,用户反馈时上传)=====
const canUseLogManage = wx.canIUse('getLogManager')
// ⚠️ level 参数仅接受 0 或 1(不是日志严重级别!)
// - 0(默认):日志包含 App/Page 生命周期及 wx 函数调用信息
// - 1:排除上述额外信息,仅记录开发者主动打的日志
// 日志严重级别通过调用不同方法区分:.info() / .warn() / .error()
const logger = canUseLogManage ? wx.getLogManager({ level: 0 }) : null
// 在关键位置打日志(不同方法对应不同严重级别)
logger?.info('游戏启动', { scene: wx.getLaunchOptionsSync().scene })
logger?.warn('帧率偏低', { fps: 30 })
logger?.error('支付失败', { errorCode: -1 })
// ===== 2. 实时日志(需要时实时上报,无需用户反馈)=====
const realtimeLogger = wx.getRealtimeLogManager?.()
// 仅在关键流程使用(有运营成本)
realtimeLogger?.info('支付开始', { orderId: 'xxx' })
realtimeLogger?.error('支付异常', { orderId: 'xxx', reason: 'timeout' })
// 设置过滤标签,方便在后台筛选日志
realtimeLogger?.setFilterMsg?.('payment')
// ===== 3. 小游戏专用日志(基础库 ≥ 3.7.4,推荐)=====
const gameLogger = wx.getGameLogManager?.()
// 用法类似 RealtimeLogManager,但专为小游戏场景优化
// 日志会实时汇聚到 MP 后台 → 基础数据 → 游戏日志分析
gameLogger?.info('关卡开始', { level: 5, difficulty: 'hard' })
gameLogger?.warn('资源加载超时', { url: 'cdn.example.com/bg.png' })与自定义监控的配合
typescript
// 将微信日志 API 与自定义 ErrorMonitor 结合
class UnifiedLogger {
private logManager = wx.canIUse('getLogManager') ? wx.getLogManager({ level: 0 }) : null
private realtimeLogger = wx.getRealtimeLogManager?.()
private gameLogger = wx.getGameLogManager?.()
/** 常规日志 → 本地缓存 */
debug(tag: string, msg: string, data?: Record<string, unknown>) {
this.logManager?.debug(`${tag}: ${msg}`, data)
}
/** 关键业务日志 → 本地缓存 + 实时上传(双重保险) */
info(tag: string, msg: string, data?: Record<string, unknown>) {
this.logManager?.info(`${tag}: ${msg}`, data)
// 小游戏专用日志优先,不可用时回退到 RealtimeLogManager
if (this.gameLogger) {
this.gameLogger.info(`${tag}: ${msg}`, data)
} else {
this.realtimeLogger?.info(`${tag}: ${msg}`, data)
}
}
/** 错误日志 → 本地缓存 + 实时上传 + 可选的第三方上报 */
error(tag: string, msg: string, err?: Error, data?: Record<string, unknown>) {
const errorData = { ...data, stack: err?.stack || '' }
this.logManager?.error(`${tag}: ${msg}`, errorData)
if (this.gameLogger) {
this.gameLogger.error(`${tag}: ${msg}`, errorData)
} else {
this.realtimeLogger?.error(`${tag}: ${msg}`, errorData)
}
}
}
// 全局单例
export const uLogger = new UnifiedLogger()日志 API 使用原则
- 日志分级使用:debug/info 用
getLogManager(本地缓存),error 兼用实时 API(双重记录)。 getGameLogManager优先:基础库 ≥ 3.7.4 时优先使用,专为小游戏优化。- 控制实时日志量:
getRealtimeLogManager有后台配额限制,仅在支付、启动、崩溃等关键路径使用。 - 敏感信息脱敏:日志中不要包含手机号、身份证、openId 等敏感信息,遵循《小程序隐私保护指引》。
性能指标采集
typescript
class PerformanceMonitor {
private metrics: Array<{
name: string
value: number
timestamp: number
}> = []
/** 每 30 秒上报一次性能快照 */
startPeriodicReport(intervalMs = 30000): void {
setInterval(() => {
this.collectAndReport()
}, intervalMs)
}
private collectAndReport(): void {
const memInfo = this.getMemoryUsage()
const fpsInfo = this.getFPS()
wx.request({
url: 'https://your-monitor-api.com/performance',
method: 'POST',
data: {
type: 'perfSnapshot',
fps: fpsInfo,
memoryMB: memInfo,
timestamp: Date.now(),
...this.getDeviceInfo(),
},
complete: () => {},
})
}
/** 获取内存情况(注意:performance.memory 为 Chrome 特有 API,微信 V8 环境可用,其他环境可能不支持) */
private getMemoryUsage(): { used: number; total: number } | null {
try {
const g = globalThis as any
if (g.performance?.memory) {
return {
used: g.performance.memory.usedJSHeapSize / 1024 / 1024,
total: g.performance.memory.jsHeapSizeLimit / 1024 / 1024,
}
}
} catch {
// 部分环境访问 performance.memory 会抛出异常
}
return null
}
/** 获取设备信息 */
private getDeviceInfo() {
const info = wx.getSystemInfoSync()
return {
platform: info.platform,
model: info.model,
brand: info.brand,
system: info.system,
pixelRatio: info.pixelRatio,
}
}
/** 获取实时 FPS(基于帧时间戳滑动窗口)*/
private frameTimestamps: number[] = []
private fps = 0
recordFrame(): void {
const now = performance.now()
this.frameTimestamps.push(now)
// 保留最近 1 秒的时间戳
const oneSecondAgo = now - 1000
this.frameTimestamps = this.frameTimestamps.filter((t) => t > oneSecondAgo)
// 每秒更新一次 FPS 值
this.fps = this.frameTimestamps.length
}
private getFPS(): number {
return this.fps
}
}启动耗时监测
typescript
class LaunchMonitor {
/** 标记启动时间点 */
static reportLaunchMetrics(): void {
const launchInfo = wx.getLaunchOptionsSync?.()
const scene = launchInfo?.scene || 0
// 获取小程序启动性能数据
const perf = wx.getPerformance?.()
if (!perf) return
// 使用微信小程序性能 API
// 在基础库 2.25.0+ 可用
const observer = perf.createObserver?.({ entryTypes: ['navigation'] })
observer?.observe({ entryTypes: ['navigation'] })
// 也可以通过手动计时:
// - appLaunchStart: 在 game.js 第一行记录
// - firstFrameReady: 在首帧渲染完成后记录
const elapsed = Date.now() - (globalThis as any).__LAUNCH_TIME__
wx.request({
url: 'https://your-monitor-api.com/launch',
method: 'POST',
data: {
scene,
launchTimeMs: elapsed,
timestamp: Date.now(),
},
complete: () => {},
})
}
}
// game.js 入口第一行
;(globalThis as any).__LAUNCH_TIME__ = Date.now()第三方监控平台接入
| 平台 | 特性 | 接入方式 |
|---|---|---|
| WeTest 质量监控 | 微信官方,崩溃/卡顿/网络 | SDK 集成 |
| Sentry | 全平台,源码映射 | sentry-miniapp 包 |
| Fundebug | BUG 监控 + 用户行为回放 | 插件集成 |
| 阿拉丁 (Ald) | 小程序专属,数据分析强 | SDK 集成 |
| 自建 | 完全可控 | wx.request 上报 + 自建服务端 |
Sentry 接入示例
typescript
// 安装: pnpm add sentry-miniapp
import * as Sentry from 'sentry-miniapp'
Sentry.init({
dsn: 'https://xxx@sentry.io/xxx',
environment: 'production',
release: '1.0.0',
// 只上报 N% 的样本(降低费用)
sampleRate: 0.5,
// 过滤非游戏相关的异常
beforeSend(event) {
// 忽略微信 SDK 内部错误
if (event.exception?.values?.[0]?.type?.includes('WX')) {
return null
}
return event
},
})监控最佳实践
- 致命错误立即通知 — 崩溃率超过阈值时触发告警(企微/钉钉/邮件)。
- 采样上报 — 日志量巨大时仅上报 N% 数据,降低服务端成本。
- 用户 ID 关联 — 每条日志带上 openId,便于排查特定用户问题。
- 版本对比 — 新版本上线后,监控异常率是否异常升高。
- 隐私合规 — 上报的数据不可包含用户敏感信息(手机号、身份证等)。
📝 课后练习
练习 1:错误采样策略
题目: 设计一个采样率方案:致命错误 100% 上报,业务警告 10% 采样,慢帧 1% 采样。
参考答案: 在 report() 中根据 payload.type 决定 Math.random() 阈值;jsError 必报,businessWarning < 0.1,slowFrame < 0.01。
练习 2:第三方监控集成
题目: 在示例基础上接入 Sentry,并配置 beforeSend 过滤掉非游戏业务错误。
参考答案思路: 参考本章 Sentry 接入示例,在 beforeSend 中过滤 event.exception.values[0].type 为内部 WX 错误的类型。