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

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


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

真实场景

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

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

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

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

swift 复制代码
{
  "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 运行时错误

javascript 复制代码
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);
});

触发场景

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

2.2 unhandledrejection - Promise 异常

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

触发场景

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

3. 实现 ErrorObserver

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

3.1 为什么要单独一个类?

职责分离

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

3.2 核心实现

typescript 复制代码
// 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

typescript 复制代码
// ❌ 错误写法
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 增加配置项

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

4.2 初始化 ErrorObserver

kotlin 复制代码
// 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 页面

javascript 复制代码
// 初始化时开启错误监控
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 面板,点击触发错误的按钮,应该看到:

bash 复制代码
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 构建产物

arduino 复制代码
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

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

php 复制代码
# 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 发布到私服

bash 复制代码
# 升级版本号
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》系列的最后一篇。

相关推荐
2601_948606181 小时前
从 jQuery → V/R → Lit:前端架构的 15 年轮回
前端·架构·jquery
wuhen_n1 小时前
Vite 核心原理:ESM 带来的开发时“瞬移”体验
前端·javascript·vue.js
nibabaoo1 小时前
前端开发攻略---vue3长列表性能优化终极指南:虚拟滚动、分页加载、时间分片等6种方案详解与代码实现
前端·javascript·vue.js·虚拟滚动·分页加载·长列表·时间分片
未完成的歌~2 小时前
前端 AJAX 详解 + 动态页面爬虫实战思路
前端·爬虫·ajax
Mintopia2 小时前
时间源不统一 + 网络延迟 + 客户端时钟偏移
前端·架构
不甜情歌2 小时前
拆解JS原型核心:显式原型(prototype)+ 隐式原型(__proto__)+原型链,解锁JS继承的关键密码
前端·javascript
香草泡芙2 小时前
解锁AI Agent潜能:基于Langchain组件库的落地指南(2)
前端·javascript·人工智能
wuhen_n2 小时前
函数式组件 vs 有状态组件:何时使用更高效?
前端·javascript·vue.js
小码哥_常2 小时前
Kotlin开发秘籍:解锁Android编程新姿势
前端