从零打造专业级前端 SDK (四):错误监控与生产发布

0 阅读4分钟

前言:在上一篇中,我们实现了离线存储和自动重试,让 SDK 具备了强大的容错能力。今天,我们将为 SDK 增加最后一块拼图——错误监控,并完成生产发布的准备工作。


1. 为什么需要错误监控?

真实场景

你的停车场系统上线后,用户反馈:"点击支付后页面白屏了"。

你打开监控平台,却发现:

  • ❌ 没有错误日志
  • ❌ 不知道是哪个用户
  • ❌ 不知道错误堆栈
  • ❌ 无法复现问题

如果有错误监控,你会看到:

{
  "eventName": "sys_error",
  "userId": "user_12345",
  "properties": {
    "type": "js_error",
    "message": "Cannot read property 'amount' of undefined",
    "filename": "payment.js",
    "lineno": 42,
    "stack": "TypeError: Cannot read property...\n    at handlePay (payment.js:42:10)"
  }
}

5 秒内定位问题payment.js 第 42 行,amount 属性未定义。


2. 浏览器提供的错误捕获 API

2.1 window.onerror - JS 运行时错误

window.addEventListener('error', (event) => {
  console.log('错误信息:', event.message);
  console.log('文件:', event.filename);
  console.log('行号:', event.lineno);
  console.log('列号:', event.colno);
  console.log('堆栈:', event.error?.stack);
});

触发场景

// 这会触发 window.onerror
throw new Error('支付金额不能为空');

2.2 unhandledrejection - Promise 异常

window.addEventListener('unhandledrejection', (event) => {
  console.log('Promise 异常:', event.reason);
});

触发场景

// 这会触发 unhandledrejection
fetch('/api/pay').then(res => {
  throw new Error('支付失败');
});

3. 实现 ErrorObserver

我们创建一个独立的 ErrorObserver 类,负责监听和上报错误。

3.1 为什么要单独一个类?

职责分离

  • Tracker 负责核心上报逻辑
  • ErrorObserver 负责错误监听
  • 未来可以轻松扩展 PerformanceObserver(性能监控)

3.2 核心实现

// src/observers/ErrorObserver.ts
export class ErrorObserver {
  private tracker: Tracker;

  constructor(tracker: Tracker) {
    this.tracker = tracker;
  }

  enable() {
    window.addEventListener('error', this.handleError);
    window.addEventListener('unhandledrejection', this.handleRejection);
  }

  // 使用箭头函数绑定 this
  private handleError = (event: ErrorEvent) => {
    this.tracker.track('sys_error', {
      type: 'js_error',
      message: event.message,
      filename: event.filename,
      lineno: event.lineno,
      colno: event.colno,
      stack: event.error?.stack || ''
    }, 'immediate'); // 错误很重要,立即发送
  };

  private handleRejection = (event: PromiseRejectionEvent) => {
    this.tracker.track('sys_error', {
      type: 'promise_error',
      message: String(event.reason),
      stack: event.reason?.stack || ''
    }, 'immediate');
  };
}

3.3 关键设计点

Q: 为什么用箭头函数?

A: 绑定 this

// ❌ 错误写法
private handleError(event: ErrorEvent) {
  this.tracker.track(...); // this 可能是 window
}

// ✅ 正确写法
private handleError = (event: ErrorEvent) => {
  this.tracker.track(...); // this 永远是 ErrorObserver
};

Q: 为什么用 immediate 优先级?

A: 错误事件非常重要,必须立即发送。

  • 如果用 batch,可能要等 3 秒或攒够 10 条。
  • 如果用户刷新了页面,错误就丢失了。

4. 集成到 Tracker

4.1 增加配置项

// src/types/index.ts
export interface TrackerConfig {
  appId: string;
  endpoint: string;
  debug?: boolean;
  enableErrorTracking?: boolean; // 新增
}

4.2 初始化 ErrorObserver

// src/core/Tracker.ts
import { ErrorObserver } from '../observers/ErrorObserver';

export class Tracker {
  private errorObserver: ErrorObserver | null = null;

  public init(config: TrackerConfig) {
    // ... 其他初始化

    // 错误监控
    if (this.config.enableErrorTracking) {
      this.errorObserver = new ErrorObserver(this);
      this.errorObserver.enable();
      Logger.info('Error Tracking Enabled');
    }
  }
}

5. 验证效果

5.1 Demo 页面

// 初始化时开启错误监控
Tracker.init({
  appId: 'parking-terminal-001',
  endpoint: 'http://localhost:3000/track',
  enableErrorTracking: true // 开启
});

// 触发错误
document.getElementById('btn-error').addEventListener('click', () => {
  throw new Error('这是一个测试错误');
});

5.2 查看上报数据

打开浏览器 Network 面板,点击触发错误的按钮,应该看到:

POST /track
{
  "eventName": "sys_error",
  "appId": "parking-terminal-001",
  "properties": {
    "type": "js_error",
    "message": "这是一个测试错误",
    "filename": "http://localhost:5173/examples/index.html",
    "lineno": 125,
    "colno": 15,
    "stack": "Error: 这是一个测试错误\n    at HTMLButtonElement..."
  },
  "timestamp": 1701590400000
}

6. 发布准备

6.1 构建产物

npm run build

生成:

  • dist/index.es.js (ESM)
  • dist/index.cjs.js (CommonJS)
  • dist/index.umd.js (UMD)
  • dist/index.d.ts (TypeScript 类型)

6.2 README.md

我们创建了一份完整的使用文档:

# Parking Tracker SDK

## 安装
npm install parking-tracker-sdk

## 快速开始
import Tracker from 'parking-tracker-sdk';

Tracker.init({
  appId: 'your-app-id',
  endpoint: 'https://api.example.com/track',
  enableErrorTracking: true
});

Tracker.track('view_home');

6.3 发布到私服

# 升级版本号
npm version patch  # 0.1.0 -> 0.1.1

# 构建
npm run build

# 发布到 Verdaccio
npm publish --registry http://localhost:4873

7. 总结

经过四个阶段的开发,我们的 SDK 已经具备了:

阶段核心功能关键技术
Phase 1工程化基础TypeScript + Vite + 单例模式
Phase 2网络发送Fetch + 策略模式 + Context
Phase 3离线存储IndexedDB + Queue + 自动重试
Phase 4错误监控ErrorObserver + 发布准备

核心特性

数据可靠性: IndexedDB 离线存储,断网不丢数据
性能优化: 批量发送,减少 90% 请求数
错误监控: 自动捕获 JS 错误,快速定位问题
易用性: TypeScript 类型提示,API 简洁

生产级标准

✅ 完整的类型定义
✅ 完善的文档
✅ 构建产物齐全
✅ 已发布到私服


8. 未来展望

虽然 SDK 已经可以投入生产使用,但还有很多可以优化的方向:

  • 性能监控: 采集 LCP, FID, CLS 等 Web Vitals 指标
  • 跨平台: 支持 Node.js、小程序、React Native
  • 数据加密: 敏感数据加密传输
  • 采样率: 高流量场景下的智能采样

从零到一,我们一起打造了一个专业级的埋点 SDK。 🎉

这不仅仅是一个工具,更是一次完整的工程化实践。希望这个系列能帮助你理解:

  • 如何设计一个可扩展的架构
  • 如何应用设计模式解决实际问题
  • 如何处理复杂的异步和错误场景

代码已开源,欢迎 Star


本文是《从零打造专业级前端 SDK》系列的最后一篇。