前言:在上一篇中,我们实现了离线存储和自动重试,让 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》系列的最后一篇。