Skip to content

前端错误收集 SDK 开发技术报告

  • 基于 Monorepo + TypeScript 的现代化架构设计与实现指南**
  • 日期:2025年10月28日

1. 背景与目标

随着前端应用复杂度的不断提升,错误监控与收集已成为保障用户体验和系统稳定性的核心能力。 构建一个可靠、轻量、可扩展的前端错误收集 SDK,能够帮助团队快速定位线上问题、提升研发效率。

本报告旨在指导你使用 Monorepo 架构 + TypeScript 开发一个现代化的前端错误收集 SDK, 涵盖架构设计、模块划分、错误捕获、数据上报、性能优化及最佳实践。

2. 技术选型与架构设计

2.1 核心技术栈

  • 语言:TypeScript(强类型保障,提升 SDK 可维护性)
  • 架构:Monorepo(使用 TurborepoNx 管理多包)
  • 构建工具:Vite 或 Rollup(支持多格式输出:ESM、CJS、UMD)
  • 包管理:pnpm(高效依赖管理,天然支持 Monorepo)
  • 测试:Jest + Playwright(单元测试 + 端到端模拟)
  • CI/CD:GitHub Actions(自动化测试、构建、发布)

2.2 Monorepo 包结构设计

text
packages/
├── core/               # 核心逻辑:错误捕获、上下文管理、上报机制
├── browser/            # 浏览器环境适配层(DOM 事件绑定、Performance API)
├── react/              # React 错误边界集成(可选)
├── vue/                # Vue 全局错误处理器集成(可选)
├── utils/              # 公共工具函数(防抖、节流、序列化、UUID 生成等)
├── types/              # 全局类型定义(TS 类型共享)
├── plugin-*/           # 插件体系(如 source-map 解析、用户行为追踪)
└── demo/               # 示例应用,用于本地调试与集成测试

优势:模块解耦、独立发布、共享类型、统一构建流程。

3. 核心功能设计

3.1 错误捕获能力

3.1.1 全局异常捕获

  • window.onerror:捕获 JavaScript 运行时错误
  • window.addEventListener('unhandledrejection'):捕获未处理的 Promise 异常
  • console.error 代理(可选):捕获开发者主动输出的错误
ts
// packages/core/src/errorHandler.ts
export function setupGlobalErrorHandler() {
  window.onerror = (message, source, lineno, colno, error) => {
    reportError({
      type: 'runtime',
      message,
      stack: error?.stack,
      filename: source,
      line: lineno,
      column: colno,
      timestamp: Date.now(),
    });
    return true;
  };

  window.addEventListener('unhandledrejection', (event) => {
    reportError({
      type: 'promise',
      message: event.reason?.message || String(event.reason),
      stack: event.reason?.stack,
      timestamp: Date.now(),
    });
  });
}

3.1.2 资源加载错误

ts
// 捕获 <script>、<img> 等资源加载失败
window.addEventListener('error', (event) => {
  if (event.target && (event.target as HTMLElement).src || (event.target as HTMLElement).href) {
    reportError({
      type: 'resource',
      target: (event.target as HTMLElement).tagName,
      url: (event.target as any).src || (event.target as any).href,
    });
  }
}, true);

3.2 上下文信息收集

为错误提供丰富的上下文,便于定位问题:

信息类型数据来源
用户信息UID、设备 ID(可匿名)
设备信息UserAgent、屏幕尺寸、网络类型
页面信息URL、referrer、title
性能指标FP、LCP、FID(通过 Performance API)
自定义标签业务自定义 metadata
ts
interface ErrorEvent {
  message: string;
  stack?: string;
  type: 'runtime' | 'promise' | 'resource';
  context: {
    url: string;
    userAgent: string;
    timestamp: number;
    performance?: Record<string, number>;
    custom?: Record<string, any>;
  };
}

3.3 数据上报策略

3.3.1 上报方式

  • Navigator.sendBeacon:页面卸载时可靠上报(推荐)
  • fetch + keepalive:现代浏览器支持
  • Image Ping:兼容性最好,但无状态反馈
ts
export function sendReport(data: ErrorEvent) {
  if (navigator.sendBeacon) {
    navigator.sendBeacon('/sdk/report', new Blob([JSON.stringify(data)], { type: 'application/json' }));
  } else {
    fetch('/sdk/report', {
      method: 'POST',
      body: JSON.stringify(data),
      keepalive: true,
      headers: { 'Content-Type': 'application/json' }
    }).catch(() => {
      // fallback to Image ping
    });
  }
}

3.3.2 上报优化

  • 节流与防抖:避免短时间内大量上报
  • 队列缓存:网络异常时暂存 localStorage
  • 采样率控制:支持按比例采样(如 10%)

3.4 Source Map 支持(可选插件)

  • 在构建时上传 Source Map 到服务端
  • SDK 上报 sourcemapUrlstack
  • 服务端解析原始代码位置(需后端配合)

4. SDK 初始化与配置

ts
import { init } from '@your-sdk/core';

init({
  appId: 'your-app-id',
  reportUrl: 'https://monitor.your-domain.com',
  sampleRate: 0.1, // 10% 采样
  enableConsoleCapture: false,
  beforeSend: (event) => {
    // 可修改或过滤事件
    if (event.message.includes('third-party-error')) return null;
    return event;
  },
  plugins: [sourceMapPlugin, userActionPlugin]
});

5. TypeScript 最佳实践

5.1 类型共享

packages/types/ 中定义全局类型:

ts
// packages/types/index.ts
export interface SDKOptions {
  appId: string;
  reportUrl: string;
  sampleRate?: number;
  beforeSend?: (event: ErrorEvent) => ErrorEvent | null;
  plugins?: Plugin[];
}

export interface ErrorEvent {
  message: string;
  stack?: string;
  type: ErrorType;
  context: Record<string, any>;
}

其他包通过 @your-sdk/types 引入,避免重复定义。

5.2 严格模式

  • 启用 strict: truenoImplicitAnystrictNullChecks
  • 使用 const assertions 保证字面量类型

6. 构建与发布

6.1 多格式输出

使用 Rollup 或 Vite 构建:

  • dist/esm/index.js —— 供现代浏览器/打包工具使用
  • dist/cjs/index.js —— 兼容 CommonJS
  • dist/umd/index.js —— 全局变量挂载(window.YourSDK

6.2 自动化发布

  • 使用 changesets 管理版本与 changelog
  • CI 流程自动发布到私有 npm 或公共 registry

7. 测试策略

测试类型工具目标
单元测试Jest核心逻辑、工具函数
集成测试Playwright模拟真实浏览器错误上报
类型测试dtslint验证 .d.ts 文件正确性
兼容性测试BrowserStack多浏览器验证

8. 安全与隐私

  • 禁止收集敏感信息:如密码、token、个人身份信息(PII)
  • 支持 GDPR:提供 disableTracking() API
  • CSP 兼容:上报域名白名单配置
  • HTTPS 强制:仅允许安全上下文上报

9. 性能考量

  • 启动性能:SDK 初始化应异步、非阻塞
  • 内存占用:避免闭包泄漏,及时清理事件监听
  • 体积控制:Gzip 后 < 5KB(核心包)
  • Tree-shaking:确保模块化设计,支持按需引入

10. 扩展性设计

插件机制

ts
interface Plugin {
  name: string;
  init: (sdk: SDK) => void;
  beforeReport?: (event: ErrorEvent) => ErrorEvent | null;
}

// 示例:用户行为插件
const userActionPlugin: Plugin = {
  name: 'user-action',
  init(sdk) {
    document.addEventListener('click', (e) => {
      sdk.addBreadcrumb({ type: 'click', target: e.target });
    });
  }
};

11. 总结与建议

你正在构建的不仅仅是一个 SDK,而是一个前端可观测性基础设施。建议:

  1. 先做 MVP:从 core + browser 包开始,实现基本错误捕获与上报
  2. 渐进式增强:逐步添加 React/Vue 集成、插件系统
  3. 文档驱动开发:为每个包编写 README 和 API 文档
  4. 监控 SDK 自身:记录 SDK 初始化失败、上报失败率

附录:推荐工具链

  • Monorepo 管理:Turborepo + pnpm
  • 构建:Vite(快)或 Rollup(成熟)
  • 类型检查:tsc + eslint
  • 版本管理:Changesets
  • 发布:GitHub Actions + npm publish