light-sentry-sdk/docs/01-sdk-design.md

1246 lines
35 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 前端 SDK 设计文档
## 一、设计原则
### 1.1 核心原则
- **极致轻量**:核心 < 5KB gzip完整功能 < 15KB gzip
- **零侵入**不影响业务代码不修改原生对象原型
- **高性能**所有耗时操作异步化不阻塞主线程
- **插件化**功能模块化按需加载可扩展
- **容错性**SDK 自身错误不影响页面运行
- **兼容 Sentry**DSN 格式事件格式上报协议兼容
### 1.2 体积预算
| 模块 | 体积 (gzip) | 说明 |
|------|-------------|------|
| Core 核心 | ~3KB | 事件总线队列上报工具函数 |
| Error 插件 | ~3KB | JS 错误Promise资源错误 |
| Performance 插件 | ~4KB | Web Vitals长任务资源时序 |
| Network 插件 | ~2KB | fetch / xhr 监控 |
| Behavior 插件 | ~2KB | PV点击路由 |
| **完整 SDK** | **~12KB** | 核心 + 所有插件 |
---
## 二、核心架构
### 2.1 整体结构
```
┌─────────────────────────────────────────────────┐
│ LightSDK │
│ ┌───────────────────────────────────────────┐ │
│ │ Core (核心) │ │
│ │ ┌──────────┐ ┌──────────┐ ┌─────────┐ │ │
│ │ │ Config │ │ EventBus │ │ Queue │ │ │
│ │ │ 配置管理 │ │ 事件总线 │ │ 事件队列 │ │ │
│ │ └──────────┘ └──────────┘ └────┬────┘ │ │
│ │ │ │ │
│ │ ┌──────────┐ ┌──────────┐ │ │ │
│ │ │ Reporter │ │ Plugins │◄──────┘ │ │
│ │ │ 上报器 │ │ 插件系统 │ │ │
│ │ └──────────┘ └──────────┘ │ │
│ └───────────────────────────────────────────┘ │
│ ▲ ▲ │
└─────────┼───────────────────┼───────────────────┘
│ │
┌─────┴─────┐ ┌──────┴──────┐
│ Plugins │ │ Hooks │
│ (插件) │ │ (生命周期) │
└───────────┘ └─────────────┘
```
### 2.2 插件系统
每个插件实现标准接口
```typescript
interface LightPlugin {
name: string; // 插件唯一标识
version: string; // 插件版本
setup(client: LightClient): void; // 安装时调用
destroy?(): void; // 销毁时调用
// 可选钩子
beforeReport?(event: Event): Event | null; // 上报前拦截
afterReport?(event: Event): void; // 上报后回调
}
```
内置插件列表
| 插件名 | 功能 | 体积 | 默认开启 |
|--------|------|------|----------|
| `error` | JS 错误Promise 异常资源加载错误 | ~3KB | |
| `performance` | Web Vitals长任务性能指标 | ~4KB | |
| `network` | fetch / xhr 请求监控 | ~2KB | |
| `behavior` | PV点击路由用户行为 | ~2KB | |
| `sampling` | 采样控制流量控制 | ~1KB | |
| `offline` | 离线缓存网络恢复后补发 | ~2KB | |
| `breadcrumbs` | 用户行为面包屑 | ~2KB | |
---
## 三、核心模块详细设计
### 3.1 Config - 配置管理
#### 配置项
```typescript
interface LightConfig {
// ===== 基础配置 =====
dsn: string; // DSN: https://{publicKey}@{host}/{projectId}
release?: string; // 版本号,如 1.0.0
environment?: string; // 环境: production / development / staging
enabled?: boolean; // 是否启用,默认 true
// ===== 上报配置 =====
sampleRate?: number; // 全局采样率 0-1默认 1
maxQueueSize?: number; // 队列最大长度,默认 100
flushInterval?: number; // 定时上报间隔(ms),默认 5000
maxRetries?: number; // 失败重试次数,默认 3
retryDelay?: number; // 重试基础延迟(ms),默认 1000
// ===== 数据配置 =====
ignoreErrors?: (string | RegExp)[]; // 忽略的错误
ignoreUrls?: (string | RegExp)[]; // 忽略的 URL
includePaths?: (string | RegExp)[]; // 只收集的路径
beforeSend?: (event: Event) => Event | null; // 发送前回调
// ===== 用户配置 =====
user?: {
id?: string;
username?: string;
email?: string;
[key: string]: any;
};
// ===== 插件配置 =====
plugins?: (LightPlugin | string)[]; // 插件列表
[pluginName: string]: any; // 各插件自定义配置
}
```
#### DSN 解析
格式`{protocol}://{publicKey}@{host}/{projectId}`
```
https://proj_abc123@log.example.com/1001
│ │ │ └── projectId: 1001
│ │ └────────────────── host: log.example.com
│ └────────────────────────────── publicKey: proj_abc123
└──────────────────────────────────────── protocol: https
```
解析后
- `projectId`: 项目 ID纯数字兼容 Sentry
- `publicKey`: 认证公钥项目内部 ID
- `host`: 上报域名
- `protocol`: http / https
### 3.2 EventBus - 事件总线
简单的发布订阅模式用于插件间通信
```typescript
class EventBus {
private handlers: Map<string, Function[]> = new Map();
on(event: string, handler: Function): void; // 订阅
off(event: string, handler: Function): void; // 取消订阅
emit(event: string, data?: any): void; // 发布
once(event: string, handler: Function): void; // 一次性订阅
}
```
内置事件
| 事件名 | 触发时机 | 数据 |
|--------|----------|------|
| `event` | 新事件产生 | Event 对象 |
| `report` | 事件上报前 | Event[] 数组 |
| `reported` | 上报成功 | Event[] 数组 |
| `error` | SDK 自身错误 | Error |
| `ready` | SDK 初始化完成 | - |
### 3.3 Queue - 事件队列
#### 队列策略
```
新事件 → 入队 → 检查是否触发上报 → 是 → 上报
└─ 否 → 等待
```
触发上报的条件
1. **队列满**达到 `maxQueueSize` 阈值
2. **定时** `flushInterval` 毫秒
3. **页面隐藏**`visibilitychange` hidden
4. **页面卸载**`beforeunload` / `pagehide`
5. **手动调用**`client.flush()`
#### 事件去重
- 同一错误相同指纹1 分钟内最多上报 3
- 防止死循环导致的事件风暴
### 3.4 Reporter - 上报器
#### 上报策略(优先级从高到低)
| 方式 | 适用场景 | 优点 | 缺点 |
|------|----------|------|------|
| `navigator.sendBeacon` | 页面卸载隐藏 | 异步不阻塞可靠 | POST大小限制 64KB |
| `fetch + keepalive` | 现代浏览器 | 灵活支持自定义 header | 不支持跨域某些场景 |
| `XMLHttpRequest` | 兼容旧浏览器 | 兼容性好 | 同步会阻塞 |
| `Image 标签` | 极端兜底 | 兼容性最好 | GET 请求大小有限 |
#### 上报重试
- 失败后指数退避重试1s 2s 4s
- 最多重试 `maxRetries`
- 4xx 错误不重试客户端错误
- 5xx / 网络错误才重试
#### 上报格式(兼容 Sentry Envelope
```
{"event_id":"abc123","sent_at":"2024-01-01T00:00:00Z"}
{"type":"event","length":123}
{"level":"error","message":"...","exception":{...}}
{"type":"transaction","length":456}
{"transaction":"...","spans":[...]}
```
轻量模式下可用简化格式
```json
{
"events": [
{ "type": "error", "data": {...} },
{ "type": "performance", "data": {...} }
]
}
```
---
## 四、内置插件详细设计
### 4.1 Error 插件(错误采集)
#### 采集内容
| 错误类型 | 监听方式 | 说明 |
|----------|----------|------|
| JS 运行时错误 | `window.onerror` | 普通 JS 错误 |
| Promise 异常 | `unhandledrejection` | Promise 未捕获 |
| 资源加载错误 | `error` 事件捕获阶段 | imgscriptlink |
| iframe 错误 | 冒泡 + postMessage | 同域 iframe |
| 框架错误 | 钩子函数Vue/React | 需手动接入 |
#### 错误事件格式
```typescript
interface ErrorEvent {
type: 'error';
level: 'fatal' | 'error' | 'warning' | 'info' | 'debug';
message: string; // 错误消息
exception?: {
type: string; // 错误类型: TypeError, ReferenceError...
value: string; // 错误消息
stacktrace?: {
frames: StackFrame[]; // 堆栈帧,倒序(最近的调用在前)
};
};
timestamp: number; // 时间戳
release?: string; // 版本
environment?: string; // 环境
user?: UserInfo; // 用户信息
tags?: Record<string, string>; // 标签
extra?: Record<string, any>; // 额外数据
breadcrumbs?: Breadcrumb[]; // 面包屑
request?: { // 请求信息
url: string;
method?: string;
headers?: Record<string, string>;
};
}
interface StackFrame {
filename: string; // 文件名
function?: string; // 函数名
lineno?: number; // 行号
colno?: number; // 列号
in_app?: boolean; // 是否业务代码
}
```
#### 错误指纹
用于错误去重和聚合
```
指纹 = md5(错误类型 + 错误消息(脱敏后) + 堆栈前3帧的文件名+行号)
```
### 4.2 Performance 插件(性能采集)
#### 采集指标
| 指标 | 全称 | 说明 | 采集方式 |
|------|------|------|----------|
| FP | First Paint | 首次绘制 | PerformanceObserver |
| FCP | First Contentful Paint | 首次内容绘制 | PerformanceObserver |
| LCP | Largest Contentful Paint | 最大内容绘制 | PerformanceObserver |
| CLS | Cumulative Layout Shift | 累计布局偏移 | PerformanceObserver |
| FID | First Input Delay | 首次输入延迟 | PerformanceObserver |
| TTI | Time to Interactive | 可交互时间 | 长任务 + 网络静默 |
| TBT | Total Blocking Time | 总阻塞时间 | 长任务累加 |
| Long Tasks | - | 长任务列表 | PerformanceObserver |
| Navigation Timing | - | 页面加载各阶段 | performance.timing |
| Resource Timing | - | 资源加载时序 | performance.getEntriesByType |
#### 性能事件格式
```typescript
interface PerformanceEvent {
type: 'performance';
metric: string; // 指标名: lcp, fcp, cls...
value: number; // 指标值
unit: string; // 单位: ms, score...
rating: 'good' | 'needs-improvement' | 'poor'; // 评级
timestamp: number;
release?: string;
environment?: string;
tags?: {
page_url: string; // 页面 URL
route?: string; // 路由路径
[key: string]: string;
};
extra?: Record<string, any>;
}
```
#### 性能评级标准Google Web Vitals
| 指标 | Good | Needs Improvement | Poor |
|------|------|-------------------|------|
| LCP | < 2.5s | 2.5s - 4.0s | > 4.0s |
| FID | < 100ms | 100ms - 300ms | > 300ms |
| CLS | < 0.1 | 0.1 - 0.25 | > 0.25 |
#### 采样策略
- 错误事件100% 采样
- 性能指标:默认 10% 采样,可配置
- 长任务:默认 5% 采样
- 资源时序:默认 1% 采样
### 4.3 Network 插件(网络监控)
#### 采集内容
- **fetch**:通过 monkey patch 包装 `window.fetch`
- **XMLHttpRequest**:通过 monkey patch 包装 `XHR.prototype.open/send`
#### 采集字段
```typescript
interface NetworkEvent {
type: 'network';
sub_type: 'fetch' | 'xhr';
method: string; // 请求方法
url: string; // 请求 URL
status_code?: number; // 状态码
duration?: number; // 耗时(ms)
request_size?: number; // 请求大小
response_size?: number; // 响应大小
success: boolean; // 是否成功
error?: string; // 错误信息
timestamp: number;
release?: string;
tags?: {
route?: string;
[key: string]: string;
};
}
```
#### 脱敏处理
- URL 中的敏感参数自动脱敏password、token、key 等)
- 请求/响应 body 默认不上报(避免数据泄漏)
- 可配置白名单上报特定接口的 body
### 4.4 Behavior 插件(行为采集)
#### 采集内容
| 行为 | 采集方式 | 说明 |
|------|----------|------|
| PV / UV | 页面加载 + 路由变化 | 页面浏览量 |
| 点击事件 | `click` 事件委托 | 按钮、链接点击 |
| 路由变化 | History / Hash 变化 | SPA 路由跳转 |
| 停留时长 | visibilitychange + 计时 | 页面停留时间 |
| 滚动深度 | scroll 事件 + 防抖 | 页面滚动百分比 |
#### 行为事件格式
```typescript
interface BehaviorEvent {
type: 'behavior';
sub_type: 'pv' | 'click' | 'route' | 'scroll' | 'duration';
page_url: string;
referrer?: string;
timestamp: number;
properties?: Record<string, any>; // 各子类型的属性
}
```
#### 点击事件属性
```
- element: 元素标签名 + id + class如 button#submit.btn-primary
- text: 元素文本(截断前 50 字符)
- xpath: 元素 XPath可选默认关
```
### 4.5 Sampling 插件(采样控制)
#### 采样维度
- **全局采样率**:所有事件的总采样率
- **按事件类型采样**:错误 100%,性能 10%,行为 1%
- **按用户采样**:按 user.id 哈希,稳定采样同一用户
- **按页面采样**:重要页面 100%,普通页面 10%
- **错误突增降级**:错误率过高时自动降采样
#### 采样算法
```javascript
// 基于用户 ID 的稳定采样
function shouldSample(userId, rate) {
const hash = murmurhash(userId || 'anonymous');
return (hash % 10000) < (rate * 10000);
}
```
### 4.6 Offline 插件(离线缓存)
- 离线时事件存入 IndexedDB
- 网络恢复后自动补发
- 最多缓存 1000 条,超出丢弃最旧的
- 补发时添加 `offline: true` 标记
### 4.7 Breadcrumbs 插件(面包屑)
记录用户操作轨迹,错误发生时一并上报:
```typescript
interface Breadcrumb {
type: 'navigation' | 'click' | 'http' | 'console' | 'user';
message: string;
timestamp: number;
category?: string;
data?: Record<string, any>;
level: 'info' | 'warning' | 'error';
}
```
默认保留最近 20 条面包屑。
---
## 五、SDK API
### 5.1 初始化
```javascript
import LightSDK from 'light-sentry';
LightSDK.init({
dsn: 'https://proj_abc123@log.example.com/1001',
release: '1.0.0',
environment: 'production',
sampleRate: 1,
// 插件配置
plugins: ['error', 'performance', 'network'],
// 性能插件配置
performance: {
sampleRate: 0.1,
captureLongTasks: true,
},
// 网络插件配置
network: {
ignoreUrls: ['/health', '/metrics'],
captureBody: false,
},
// 发送前回调
beforeSend(event) {
// 可修改或过滤事件
if (event.message.includes('ignore')) return null;
return event;
},
});
```
### 5.2 手动 API
```javascript
// 捕获错误
LightSDK.captureException(error);
LightSDK.captureMessage('Something happened', 'warning');
// 设置用户
LightSDK.setUser({ id: '123', username: 'test' });
// 设置标签
LightSDK.setTag('page', 'home');
LightSDK.setTags({ platform: 'ios', version: '2.0' });
// 添加上下文
LightSDK.setExtra('order_id', '12345');
// 添加面包屑
LightSDK.addBreadcrumb({
type: 'user',
message: 'User clicked button',
category: 'action',
});
// 手动上报
LightSDK.captureEvent({
type: 'custom',
message: 'custom event',
level: 'info',
extra: { foo: 'bar' },
});
// 强制刷新队列
LightSDK.flush();
// 禁用 / 启用
LightSDK.disable();
LightSDK.enable();
```
### 5.3 框架集成
#### Vue 2
```javascript
import LightSDK from 'light-sentry';
import { VueIntegration } from 'light-sentry/vue';
LightSDK.init({
dsn: '...',
plugins: [new VueIntegration(Vue)],
});
```
#### Vue 3
```javascript
app.use(LightSDK.vuePlugin, { dsn: '...' });
```
#### React
```javascript
// 错误边界
import { ErrorBoundary } from 'light-sentry/react';
<ErrorBoundary fallback={<p>出错了</p>}>
<App />
</ErrorBoundary>
```
---
## 六、性能优化
### 6.1 不影响页面性能的设计
1. **全部异步**SDK 初始化、事件处理、上报全异步
2. **requestIdleCallback**:非紧急操作放在空闲时间
3. **防抖节流**scroll、resize 等高频事件防抖
4. **不修改原型**用包装方式fetch/xhr不污染原型
5. **微任务批量**:同一批事件合并处理,减少事件循环开销
6. **Web Worker**:可选,重计算(指纹、聚合)放到 Worker
### 6.2 数据体积优化
这是轻量 SDK 与 Sentry 的核心区别之一——通过策略大幅减少上报数据量。
#### 6.2.1 错误堆栈精简
| 项目 | Sentry 默认 | 轻量方案 | 节省 |
|------|------------|----------|------|
| 堆栈深度 | 50 帧 | **3-5 帧**in_app | ~80% |
| node_modules 帧 | 保留 | **过滤掉** | ~50% |
| 列号 | 保留 | 可选,默认开 | - |
| 完整文件名 | 完整 URL | **只留相对路径** | ~30% |
**为什么 3-5 帧就够了?**
- 90% 的错误,看前 3 帧就能定位问题
- 真正需要深度堆栈的错误很少,遇到了再开完整模式
- 业务代码的调用链通常不深
**配置方式**
```javascript
LightSDK.init({
error: {
maxStackFrames: 5, // 最多 5 帧
captureNodeModules: false, // 不上报 node_modules 里的帧
captureColumn: true, // 保留列号
relativePathOnly: true, // 只保留相对路径,去掉域名
}
});
```
**堆栈对比**
Sentry2KB+
```
at XMLHttpRequest.onloadend (http://localhost:5173/node_modules/.vite/deps/axios.js?v=6b332a51:1845:7)
at settle (http://localhost:5173/node_modules/.vite/deps/axios.js?v=6b332a51:1441:12)
at http://localhost:5173/node_modules/.vite/deps/axios.js?v=6b332a51:1245:12
at wrapFn (http://localhost:5173/node_modules/.vite/deps/axios.js?v=6b332a51:2312:45)
at fetchUser (http://localhost:5173/src/api/user.js:23:15)
at UserList.vue:45:8
...(还有 40 多帧)
```
轻量版(~200B
```
at fetchUser (src/api/user.js:23)
at UserList.vue:45
```
#### 6.2.2 环境信息结构化browser / os / device
**问题**:每条事件都带完整的环境信息,数据冗余严重。
```
// 每条都带:~200 bytes
"browser": { "name": "Chrome", "version": "120.0.0.0" },
"os": { "name": "Mac OS X", "version": "10.15.7" },
"device": { "family": "MacBook Pro", "model": "MacBook Pro" }
```
**优化策略:三级方案**
##### 方案 1字典编码SDK 端,最简单)
把常用的浏览器/OS 映射成短编码,减少传输体积。
```javascript
// 内置字典
const BROWSER_MAP = {
'Chrome': 'c',
'Safari': 's',
'Firefox': 'f',
'Edge': 'e',
'Mobile Chrome': 'cm',
'Mobile Safari': 'sm',
// ...
};
const OS_MAP = {
'Windows': 'w',
'Mac OS X': 'm',
'Linux': 'l',
'iOS': 'i',
'Android': 'a',
// ...
};
// 上报时用编码
{
"env": {
"b": "c", // browser = Chrome
"bv": "120", // 只留主版本号
"os": "m", // os = Mac OS X
"osv": "15", // 只留大版本
"dv": "mbp" // device = MacBook Pro
}
}
```
**效果**:环境信息从 ~200B → ~50B减少 75%。
##### 方案 2会话级共享进一步优化
同一个用户同一次会话里,环境信息是不变的,不需要每条都带。
```
┌─────────────────────────────────────────┐
│ 第一次上报:带完整环境信息 + session_id │
│ 后续上报:只带 session_id │
└─────────────────────────────────────────┘
```
服务端根据 `session_id` 关联环境信息。
**效果**:每条事件节省 50-100B百万级事件能省几十 GB。
##### 方案 3服务端 UA 解析(最极致)
SDK 完全不解析 UA只上报原始 User-Agent 字符串,服务端统一解析后写入维度表。
```
SDK 上报:只带 1 个 ua 字段(原始 UA 字符串)
服务端:解析后存入维度表,日志里只存维度 ID
```
| 维度 | 说明 |
|------|------|
| 优点 | SDK 更小(不用带 UA 解析库),解析逻辑集中维护 |
| 缺点 | 服务端多了一层解析开销 |
**推荐组合**SDK 做字典编码 + 只保留主版本号 → 简单、够用、SDK 体积增加很少。
#### 6.2.3 上下文信息分层策略
不是所有事件都需要完整上下文,按级别区分:
| 事件级别 | 堆栈 | 环境信息 | breadcrumbs | request 信息 | 用户信息 |
|----------|------|----------|-------------|-------------|----------|
| fatal | 5 帧 | 完整 | 20 条 | 完整 | 完整 |
| error | 5 帧 | 精简 | 10 条 | URL+状态码 | id 即可 |
| warning | 3 帧 | 精简 | 5 条 | URL 即可 | id 即可 |
| info | 无 | 编码 | 无 | 无 | 无 |
| debug | 无 | 无 | 无 | 无 | 无 |
**配置方式**
```javascript
LightSDK.init({
contextLevel: {
fatal: 'full',
error: 'standard',
warning: 'minimal',
info: 'none',
}
});
```
#### 6.2.4 批量上报共享元数据
同批次的事件共享相同的元数据release、environment、user 等),不用每条都重复带。
**Sentry 方式(每条都带)**
```
event1: { release: "1.0.0", environment: "prod", user: {...}, ... }
event2: { release: "1.0.0", environment: "prod", user: {...}, ... }
event3: { release: "1.0.0", environment: "prod", user: {...}, ... }
```
**轻量方式(共享头)**
```json
{
"meta": {
"release": "1.0.0",
"environment": "prod",
"user": { "id": "123" },
"env": { "b": "c", "os": "m" }
},
"events": [
{ "type": "error", "message": "...", "stack": [...] },
{ "type": "performance", "metric": "lcp", "value": 2500 },
{ "type": "error", "message": "...", "stack": [...] }
]
}
```
**效果**:批量 10 条的话,元数据部分减少约 90%。
#### 6.2.5 业务错误智能过滤
401、403、表单校验失败这类「预期内的业务错误」不需要完整上报
```javascript
network: {
// 忽略的 HTTP 状态码(业务预期错误)
ignoreStatusCodes: [401, 403, 422, 404],
// 错误请求只上报精简信息URL + 状态码 + 耗时)
errorRequestDetail: 'minimal',
// 成功请求完全不上报
captureSuccess: false,
// 只上报 5xx 和网络错误为「错误事件」
// 4xx 只计数,不上报详情
}
```
**对比**
| 方式 | 一个 401 错误上报体积 |
|------|---------------------|
| Sentry | ~2-3 KB完整堆栈+上下文) |
| 轻量版(标准) | ~200B堆栈裁剪+精简) |
| 轻量版(业务错误过滤) | ~50B只计数甚至 0B忽略 |
#### 6.2.6 请求头 / 响应头精简
请求头和响应头是「隐藏的体积大户」——Sentry 默认会带一堆 headers一对请求响应头就能有 1-2KB。
**问题**
```json
// Sentry 默认带上的 headers节选完整有 20+ 个)
{
"request": {
"headers": {
"User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7)...",
"Content-Type": "application/json",
"Accept": "application/json, text/plain, */*",
"Accept-Language": "zh-CN,zh;q=0.9",
"Accept-Encoding": "gzip, deflate, br",
"Cache-Control": "no-cache",
"Pragma": "no-cache",
"Referer": "https://example.com/page",
"Origin": "https://example.com",
"Connection": "keep-alive",
"X-Request-Id": "abc123...",
"Authorization": "Bearer xxx..." // ⚠️ 敏感信息!
}
}
}
```
**核心策略:白名单 + 编码 + 不上报 body**
##### 策略 1白名单机制默认只带必要的
不是所有 header 都有价值,默认只上报对排查问题有用的几个:
```javascript
network: {
// 请求头白名单(默认)
captureRequestHeaders: [
'content-type',
'user-agent', // 或者干脆不带,环境信息里已有
'x-request-id',
],
// 响应头白名单(默认)
captureResponseHeaders: [
'content-type',
'content-length',
'x-request-id',
'server',
],
// ❌ 绝对禁止上报的 header黑名单防止敏感信息泄露
forbiddenHeaders: [
'authorization',
'cookie',
'set-cookie',
'token',
'api-key',
'x-api-key',
'x-auth-token',
],
}
```
**效果**:从 20+ 个 header → 3-5 个,体积减少 80%+。
##### 策略 2Header 名称短编码
把常见的 header 名称映射成短编码,进一步减少体积。
```javascript
const HEADER_NAME_MAP = {
'content-type': 'ct',
'user-agent': 'ua',
'x-request-id': 'rid',
'content-length': 'cl',
'server': 'sv',
'accept': 'ac',
'referer': 'ref',
'origin': 'org',
// ...
};
```
**编码前后对比**
```json
// 编码前(~150B
{ "headers": { "content-type": "application/json", "x-request-id": "abc123" } }
// 编码后(~70B
{ "h": { "ct": "application/json", "rid": "abc123" } }
```
##### 策略 3Header 值字典化
对于值相对固定的 header也可以做字典映射
```javascript
// Content-Type 的常见值
const CONTENT_TYPE_MAP = {
'application/json': '1',
'text/html': '2',
'text/plain': '3',
'application/x-www-form-urlencoded': '4',
'multipart/form-data': '5',
'image/png': '6',
// ...
};
```
**效果**:每个 header 值从十几个字符 → 1-2 个字符。
##### 策略 4body 默认不上报
请求体和响应体是最大的体积来源,而且:
- 可能包含敏感数据密码、token、用户信息
- 体积大(几 KB 到几 MB 都有可能)
- 大部分场景下,排查问题不需要看 body
```javascript
network: {
// ❌ 默认不上报请求/响应体
captureRequestBody: false,
captureResponseBody: false,
// (可选)只对特定接口上报 body且做脱敏
captureBodyForUrls: [
'/api/debug/*',
],
// body 大小限制(即使开启也截断)
maxBodySize: 2048, // 最多 2KB
}
```
#### 6.2.7 进一步精简:协议层与传输层
前面的优化都是「内容层面」的精简,协议和传输层面还有很大空间。
##### 优化 1全量字段短编码
不只是 header**所有字段名**都可以用短编码,减少 JSON key 的重复开销。
```javascript
// 字段名映射表
const FIELD_MAP = {
// 通用字段
'type': 't',
'level': 'l',
'message': 'm',
'timestamp': 'ts',
'release': 'r',
'environment': 'e',
'platform': 'p',
// 错误相关
'exception': 'ex',
'stacktrace': 'st',
'frames': 'fr',
'filename': 'fn',
'function': 'fc',
'lineno': 'ln',
'colno': 'cn',
'in_app': 'ia',
// 用户
'user': 'u',
'user.id': 'uid',
// 标签/扩展
'tags': 'tg',
'extra': 'xt',
// 请求
'request': 'req',
'url': 'u',
'headers': 'h',
// 性能
'metric': 'mt',
'value': 'v',
'unit': 'un',
'rating': 'rt',
};
```
**编码前后对比**
```json
// 编码前(~120B
{
"type": "error",
"level": "error",
"message": "Something broke",
"timestamp": 1704067200000,
"release": "1.0.0"
}
// 编码后(~65B
{
"t": "e",
"l": "e",
"m": "Something broke",
"ts": 1704067200000,
"r": "1.0.0"
}
```
**效果**:字段名部分减少 50-60%。
##### 优化 2时间戳相对化
同一批事件的时间戳都很接近,用「相对于批次起始时间」的差值表示,数字更小、字符更少。
```json
// 传统方式(每条都是完整时间戳)
{
"events": [
{ "ts": 1704067200123, ... },
{ "ts": 1704067200456, ... },
{ "ts": 1704067200789, ... }
]
}
// 相对时间戳(批次头存基准时间,每条存差值)
{
"base_ts": 1704067200000,
"events": [
{ "ts": 123, ... },
{ "ts": 456, ... },
{ "ts": 789, ... }
]
}
```
**效果**:每条事件时间戳从 13 位数字 → 2-4 位,减少 60-80%。
##### 优化 3批量内字符串去重
同一批事件里,很多字符串是重复的(同一页面、同一浏览器、同一版本),用「字符串表 + 索引」的方式。
```json
// 传统方式(每条都带完整字符串)
{
"events": [
{ "page": "/home", "release": "1.0.0", "browser": "Chrome" },
{ "page": "/home", "release": "1.0.0", "browser": "Chrome" },
{ "page": "/about", "release": "1.0.0", "browser": "Safari" }
]
}
// 字符串表方式(共享字符串,只用索引引用)
{
"strings": ["/home", "1.0.0", "Chrome", "/about", "Safari"],
"events": [
{ "page": 0, "release": 1, "browser": 2 },
{ "page": 0, "release": 1, "browser": 2 },
{ "page": 3, "release": 1, "browser": 4 }
]
}
```
**效果**批量越大、重复越多节省越明显。10 条以上批量时,减少 30-50%。
##### 优化 4二进制协议替代 JSON
JSON 是文本格式,冗余度很高。用二进制协议可以进一步压缩:
| 协议 | 体积 | 解析速度 | 可读性 |
|------|------|----------|--------|
| JSON | 100%(基准) | 慢 | 好 |
| MessagePack | 约 60-70% | 快 | 差(需工具解码) |
| Protobuf | 约 40-50% | 最快 | 最差 |
| 自定义二进制 | 约 30-40% | 快 | 最差 |
**推荐策略**
- 默认用 JSON简单、调试方便
- 高性能模式下可选 MessagePack
- 一般项目 JSON 足够,二进制增加复杂度收益不一定高
##### 优化 5智能采样不是全量 10%,而是动态调整)
不是所有事件都值得同样的采样率,按「价值」动态调整:
```javascript
sampling: {
// 错误事件100% 采样
error: 1.0,
// 性能指标:默认 10%,但 LCP > 4s 的 100% 采样(慢的全要)
performance: {
default: 0.1,
poor: 1.0, // 评级为 poor 的全采
needsImprovement: 0.5,
good: 0.05,
},
// 网络请求:错误 100%,成功 1%
network: {
error: 1.0,
success: 0.01,
// 慢请求也全采
slowThreshold: 3000,
slow: 1.0,
},
// 同一个用户全采或全不采(保证会话完整)
userBased: true,
}
```
**为什么智能采样更好?**
- 「好的」性能数据 99% 都是正常的,采多了没用
- 「差的」性能数据才是我们真正想看的,要全采
- 同样,成功的请求采 1% 就够统计了,失败的才需要详细排查
**效果**:总体数据量再减 50-70%,同时保证「有价值的数据」不丢。
##### 优化 6相同错误聚合上报最狠的一招
同一个错误 1 分钟内出现 100 次,没必要上报 100 次完整堆栈。
```
第 1 次:上报完整信息(堆栈 + 上下文)
第 2~N 次:只上报计数 + 时间戳(几十字节)
```
```json
{
"events": [
// 完整错误(首次出现)
{
"t": "e",
"m": "Cannot read property 'foo' of undefined",
"st": [...],
"full": true
},
// 聚合计数(后续同指纹错误)
{
"fp": "a1b2c3d4", // 错误指纹
"cnt": 15, // 15 次
"ts_start": 123, // 起始时间偏移
"ts_end": 456 // 结束结束
}
]
}
```
**效果**:对于高频错误,减少 90%+ 的上报量。
#### 6.2.8 传输层优化
数据体积之外,传输效率也很重要:
##### 优化 1gzip 压缩
所有上报请求开启 gzip 压缩:
- JSON 数据压缩率通常有 60-80%
- 体积从 300B → 60-120B
- Nginx 层自动解压或直接透传
##### 优化 2HTTP 连接复用
- 开启 `keep-alive`,复用 TCP 连接
- 减少 TCP 握手开销
- 用同一个连接发多批数据
##### 优化 3HTTP/2
- 多路复用,多个请求并行
- 头部压缩HPACK
- 服务端支持的话自动启用
##### 优化 4上报时机优化
- 尽量在网络空闲时上报(`navigator.onLine` + `requestIdleCallback`
- 页面加载高峰期不上报,避免影响页面性能
- 网络从离线变在线时,补发离线缓存的数据
#### 6.2.9 服务端二次精简
SDK 已经精简过了,服务端入库前还可以再来一刀:
1. **字段过滤**:再次检查,去掉不需要的字段
2. **堆栈再裁剪**:生产环境再裁掉几帧
3. **敏感信息二次脱敏**:防止 SDK 端漏网之鱼
4. **采样降频**:超过配额的项目,服务端再降采样
5. **冷热分层**热数据7天存完整信息冷数据30天+)只存聚合统计
---
#### 6.2.10 体积优化总览
| 优化项 | Sentry 基线 | 优化后 | 减少比例 |
|--------|------------|--------|---------|
| 堆栈深度 | 50 帧 | 5 帧 | 80% |
| node_modules 帧 | 保留 | 过滤 | 50% |
| 环境信息 | 完整明细 | 字典编码 + 维度表 | 75-95% |
| 请求头/响应头 | 20+ 个全量 | 白名单 + 短编码 | 80-90% |
| 请求/响应 body | 默认带上 | 默认不上报 | 100% |
| 字段名 | 完整名称 | 短编码t/l/m/ts... | 50-60% |
| 时间戳 | 完整 13 位 | 相对时间戳 | 60-80% |
| 批量元数据 | 每条都带 | 共享头 + 字符串表 | 70-90% |
| 业务错误 | 完整上报 | 只计数/忽略 | 80-100% |
| 相同错误 | 每条都完整上报 | 第1条完整 + 其余计数 | 90%+ |
| 采样策略 | 固定采样率 | 智能采样(按价值) | 50-70% |
| 传输压缩 | 无 | gzip 压缩 | 60-80% |
| **综合普通错误gzip前** | **~3KB/条** | **~100-200B/条** | **93-97%** |
| **综合普通错误gzip后** | **~1KB/条** | **~20-50B/条** | **95-98%** |
---
### 6.3 错误安全
- SDK 自身 try-catch 包裹,错误不抛出
- 无限循环检测:相同错误 1 秒内 > 10 次自动暂停
- 递归保护:防止 SDK 错误触发新的 SDK 错误
---
## 七、与 Sentry SDK 的兼容性
### 7.1 兼容的部分
- DSN 格式完全一致
- 事件数据结构兼容exception、stacktrace、user、tags 等)
- envelope 上报格式兼容
- API 风格类似captureException、captureMessage、setUser 等)
### 7.2 不兼容的部分
- 没有 Scope 概念(简化设计)
- 没有 Hub 概念(单实例设计)
- 没有 Integration 概念(用 Plugin 替代)
- 不支持完整的分布式追踪(轻量版)
- 不支持 Session/Replay可插件扩展
### 7.3 迁移指南
从 Sentry 迁移到 Light-Sentry
```diff
- import * as Sentry from '@sentry/browser';
+ import LightSDK from 'light-sentry';
- Sentry.init({ dsn: '...', ... });
+ LightSDK.init({ dsn: '...', ... });
// API 基本一致
- Sentry.captureException(err);
+ LightSDK.captureException(err);
- Sentry.setUser({ id: '1' });
+ LightSDK.setUser({ id: '1' });
```