Skip to content

异常监控与日志

预计阅读 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 使用原则
  1. 日志分级使用:debug/info 用 getLogManager(本地缓存),error 兼用实时 API(双重记录)。
  2. getGameLogManager 优先:基础库 ≥ 3.7.4 时优先使用,专为小游戏优化。
  3. 控制实时日志量getRealtimeLogManager 有后台配额限制,仅在支付、启动、崩溃等关键路径使用。
  4. 敏感信息脱敏:日志中不要包含手机号、身份证、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
FundebugBUG 监控 + 用户行为回放插件集成
阿拉丁 (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
  },
})

监控最佳实践
  1. 致命错误立即通知 — 崩溃率超过阈值时触发告警(企微/钉钉/邮件)。
  2. 采样上报 — 日志量巨大时仅上报 N% 数据,降低服务端成本。
  3. 用户 ID 关联 — 每条日志带上 openId,便于排查特定用户问题。
  4. 版本对比 — 新版本上线后,监控异常率是否异常升高。
  5. 隐私合规 — 上报的数据不可包含用户敏感信息(手机号、身份证等)。

📝 课后练习

练习 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 错误的类型。

📚 相关阅读

用心学习,持续实践