349 lines
10 KiB
Markdown
349 lines
10 KiB
Markdown
# 分布式追踪(Distributed Tracing)设计文档
|
||
|
||
## 一、背景与目标
|
||
|
||
### 1.1 背景
|
||
当前 SDK 已支持错误采集、性能指标(Web Vitals)、网络请求监控、用户行为采集等能力,但缺乏**前后端链路打通**的分布式追踪能力。当用户遇到"接口慢"或"报错"时,无法关联前端操作与后端服务的内部调用链路,排查问题效率低。
|
||
|
||
### 1.2 目标
|
||
- **第一阶段(P0)**:实现 Trace/Span 核心模型 + 前后端 Trace 传播 + 手动 API
|
||
- **第二阶段(P1)**:自动 Instrumentation(Page Load、路由变化、用户交互)
|
||
- **第三阶段(P2)**:采样策略升级 + Baggage 支持 + 完整开发者体验
|
||
|
||
---
|
||
|
||
## 二、核心概念
|
||
|
||
### 2.1 Trace
|
||
一条完整的分布式调用链路,全局唯一 `trace_id` 标识。一条 Trace 跨越多个服务、多个进程。
|
||
|
||
### 2.2 Transaction
|
||
Trace 中的"根 Span",代表一个完整的用户操作或业务流程。前端常见的 Transaction 类型:
|
||
- `pageload`:页面加载
|
||
- `navigation`:路由切换(SPA)
|
||
- `user-interaction`:用户交互(点击、提交等)
|
||
- `custom`:自定义事务
|
||
|
||
### 2.3 Span
|
||
Transaction 中的具体操作步骤,有父子关系。常见 Span 类型:
|
||
- `http`:HTTP 请求
|
||
- `resource`:资源加载
|
||
- `browser`:浏览器内部操作(DOM 解析、脚本执行等)
|
||
- `db`:数据库查询(后端)
|
||
- `middleware`:中间件(后端)
|
||
|
||
### 2.4 Span 关系
|
||
```
|
||
Trace (trace_id = abc123)
|
||
└── Transaction: "page-load" (span_id = s1)
|
||
├── Span: "http GET /api/user" (span_id = s2, parent = s1)
|
||
│ └── 后端服务的多个 Span(通过 trace header 关联)
|
||
├── Span: "resource /static/app.js" (span_id = s3, parent = s1)
|
||
└── Span: "dom-parse" (span_id = s4, parent = s1)
|
||
```
|
||
|
||
---
|
||
|
||
## 三、数据模型设计
|
||
|
||
### 3.1 Span 接口
|
||
```typescript
|
||
interface Span {
|
||
trace_id: string; // 16/32 位十六进制,全局唯一
|
||
span_id: string; // 16 位十六进制,Span 唯一
|
||
parent_span_id?: string; // 父 Span ID
|
||
op?: string; // 操作类型:http、resource、browser 等
|
||
description?: string; // 描述信息
|
||
start_timestamp: number; // 开始时间(毫秒时间戳)
|
||
timestamp: number; // 结束时间(毫秒时间戳)
|
||
status?: SpanStatus; // 状态
|
||
tags?: Record<string, string>; // 标签
|
||
data?: Record<string, unknown>; // 附加数据
|
||
same_process_as_parent?: boolean;
|
||
}
|
||
|
||
type SpanStatus =
|
||
| 'ok'
|
||
| 'cancelled'
|
||
| 'unknown'
|
||
| 'invalid_argument'
|
||
| 'deadline_exceeded'
|
||
| 'not_found'
|
||
| 'already_exists'
|
||
| 'permission_denied'
|
||
| 'resource_exhausted'
|
||
| 'failed_precondition'
|
||
| 'aborted'
|
||
| 'out_of_range'
|
||
| 'unimplemented'
|
||
| 'internal_error'
|
||
| 'unavailable'
|
||
| 'data_loss'
|
||
| 'unauthenticated';
|
||
```
|
||
|
||
### 3.2 Transaction 接口
|
||
```typescript
|
||
interface Transaction extends Span {
|
||
name: string; // 事务名称(如 "GET /api/users")
|
||
spans: Span[]; // 子 Span 列表
|
||
transaction: string; // 事务名称冗余字段
|
||
type?: string; // 事务类型:page-load、navigation 等
|
||
sampled?: boolean; // 是否采样
|
||
}
|
||
```
|
||
|
||
### 3.3 上报事件类型
|
||
新增 `transaction` 事件类型:
|
||
```typescript
|
||
interface TransactionEvent extends BaseEvent {
|
||
type: 'transaction';
|
||
transaction: string;
|
||
spans: Span[];
|
||
start_timestamp: number;
|
||
timestamp: number;
|
||
// ...
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## 四、前后端 Trace 传播
|
||
|
||
### 4.1 协议选择
|
||
采用 **Sentry Trace 协议**(`sentry-trace` header),同时支持 W3C Trace Context 作为兼容。
|
||
|
||
### 4.2 sentry-trace Header 格式
|
||
```
|
||
sentry-trace: {trace_id}-{span_id}-{sampled}
|
||
```
|
||
- `trace_id`: 32 位十六进制字符串
|
||
- `span_id`: 16 位十六进制字符串
|
||
- `sampled`: 可选,`1` = 采样,`0` = 不采样
|
||
|
||
示例:
|
||
```
|
||
sentry-trace: 8839b8a3914c485bb1f52ce3c08c9c0b-b89e261f348c022b-1
|
||
```
|
||
|
||
### 4.3 baggage Header(后续阶段)
|
||
```
|
||
baggage: sentry-trace_id=xxx,sentry-public_key=xxx,sentry-sample_rate=1.0
|
||
```
|
||
|
||
### 4.4 追踪域名白名单
|
||
为避免向第三方接口泄漏 trace 信息,需要配置 `tracingOrigins`:
|
||
```typescript
|
||
LightSentry.init({
|
||
dsn: '...',
|
||
tracingOrigins: [
|
||
'api.example.com',
|
||
/^https:\/\/internal\./,
|
||
'localhost',
|
||
],
|
||
});
|
||
```
|
||
默认匹配同源域名。
|
||
|
||
---
|
||
|
||
## 五、核心模块设计
|
||
|
||
### 5.1 TracingManager
|
||
职责:
|
||
- 维护当前活跃的 Transaction
|
||
- 维护 Span 栈(当前执行上下文)
|
||
- 生成 trace_id / span_id
|
||
- 管理 Span 的父子关系
|
||
|
||
关键方法:
|
||
```typescript
|
||
class TracingManager {
|
||
startTransaction(context: TransactionContext): Transaction;
|
||
getCurrentTransaction(): Transaction | null;
|
||
setCurrentTransaction(transaction: Transaction | null): void;
|
||
startSpan(context: SpanContext): Span | null;
|
||
getCurrentSpan(): Span | null;
|
||
finishSpan(span: Span): void;
|
||
}
|
||
```
|
||
|
||
### 5.2 Span 类
|
||
```typescript
|
||
class Span {
|
||
traceId: string;
|
||
spanId: string;
|
||
parentSpanId?: string;
|
||
op?: string;
|
||
description?: string;
|
||
startTimestamp: number;
|
||
endTimestamp?: number;
|
||
status?: string;
|
||
tags: Record<string, string>;
|
||
data: Record<string, unknown>;
|
||
|
||
setTag(key: string, value: string): this;
|
||
setData(key: string, value: unknown): this;
|
||
setStatus(status: string): this;
|
||
startChild(context: SpanContext): Span;
|
||
finish(endTimestamp?: number): void;
|
||
toJSON(): Span;
|
||
toTraceparent(): string; // 生成 sentry-trace header 值
|
||
}
|
||
```
|
||
|
||
### 5.3 Transaction 类
|
||
```typescript
|
||
class Transaction extends Span {
|
||
name: string;
|
||
spans: Span[];
|
||
type?: string;
|
||
sampled?: boolean;
|
||
|
||
finish(endTimestamp?: number): void;
|
||
toJSON(): Transaction;
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## 六、插件改造
|
||
|
||
### 6.1 NetworkPlugin 改造
|
||
|
||
**改造前**:上报独立的 `network` 事件
|
||
**改造后**:创建 `http` Span,关联到当前 Transaction,并注入 trace header
|
||
|
||
具体改动:
|
||
1. 请求开始时,如果存在活跃 Transaction,创建 Span
|
||
2. 向匹配的域名注入 `sentry-trace` header
|
||
3. 请求结束时,设置 Span status 和 data,finish Span
|
||
4. 保持原有 `network` 事件上报(兼容)
|
||
|
||
### 6.2 PerformancePlugin 改造(第二阶段)
|
||
|
||
改造为 Page Load Transaction:
|
||
- Navigation Timing → 拆解为多个 Span
|
||
- Resource Timing → 作为 resource Span
|
||
- Web Vitals → 作为标记点或 measurement
|
||
|
||
### 6.3 新增 RouterPlugin(第二阶段)
|
||
|
||
监听路由变化:
|
||
- History API(pushState / replaceState / popstate)
|
||
- hashchange
|
||
- 每次路由变化创建新的 navigation Transaction
|
||
|
||
---
|
||
|
||
## 七、对外 API
|
||
|
||
### 7.1 顶层 API
|
||
```typescript
|
||
// 开始事务
|
||
LightSentry.startTransaction({
|
||
name: 'search-products',
|
||
op: 'user-interaction',
|
||
});
|
||
|
||
// 获取当前 Span
|
||
const span = LightSentry.getCurrentSpan();
|
||
|
||
// 创建子 Span(基于当前活跃 Span)
|
||
const childSpan = LightSentry.startSpan({
|
||
op: 'http',
|
||
description: 'GET /api/search',
|
||
});
|
||
|
||
// 手动设置 Span
|
||
LightSentry.setSpan(span);
|
||
```
|
||
|
||
### 7.2 配置项
|
||
```typescript
|
||
interface LightConfig {
|
||
// ...
|
||
tracesSampleRate?: number; // 全局 Trace 采样率 0~1,默认 0
|
||
tracesSampler?: (context: any) => number; // 动态采样函数
|
||
tracingOrigins?: (string | RegExp)[]; // 追踪域名白名单
|
||
tracePropagationTargets?: (string | RegExp)[]; // trace header 传播目标
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## 八、采样策略(第一阶段简化版)
|
||
|
||
第一阶段采用简单策略:
|
||
1. 配置 `tracesSampleRate`(默认 0 = 关闭)
|
||
2. Transaction 创建时决定是否采样
|
||
3. 采样决策通过 `sentry-trace` 的 `sampled` 标志传递给后端
|
||
4. 未采样的 Transaction 不上报,但仍传递 trace_id(用于关联)
|
||
|
||
---
|
||
|
||
## 九、实施计划
|
||
|
||
### 第一阶段:核心能力(已完成)
|
||
- [x] 设计文档
|
||
- [x] 新增 Trace/Span 类型定义
|
||
- [x] 实现 Span 类
|
||
- [x] 实现 Transaction 类
|
||
- [x] 实现 TracingManager
|
||
- [x] 改造 NetworkPlugin(注入 trace header + 创建 HTTP Span)
|
||
- [x] 对外暴露 startTransaction / startSpan API
|
||
- [x] 集成到 Client
|
||
- [x] 编译验证
|
||
- [x] 错误事件自动关联 Trace 上下文
|
||
- [x] Transaction 完成后自动上报
|
||
- [x] Span 栈生命周期管理
|
||
- [x] 未采样 Transaction 不收集 Span(性能优化)
|
||
|
||
### 第二阶段:自动 Instrumentation(已完成)
|
||
- [x] PerformancePlugin 改造:Page Load Transaction
|
||
- [x] Navigation Timing 拆解为 browser.* Span 树
|
||
- [x] Resource Timing 转为 resource.* Span
|
||
- [x] Web Vitals 作为 measurements 附加到 Transaction
|
||
- [x] LCP/FP/FCP 等关键指标标记
|
||
- [x] 新增 RouterPlugin(路由变化追踪)
|
||
- [x] History API 监听(pushState / replaceState / popstate)
|
||
- [x] Hash 路由监听(hashchange)
|
||
- [x] 路由切换期间的 Span 自动关联
|
||
- [x] 新增 InteractionPlugin(用户交互追踪)
|
||
- [x] 点击/表单提交等交互自动创建 Transaction
|
||
- [x] 交互期间的网络请求自动关联
|
||
- [x] Error 与 Trace 深度集成
|
||
- [x] 错误事件自动附加 contexts.trace
|
||
- [ ] 错误作为 error Span 加入当前 Transaction(可选)
|
||
|
||
### 第三阶段:进阶能力(已完成)
|
||
- [x] Baggage header 支持
|
||
- [x] Transaction 携带 user、release 等信息
|
||
- [x] NetworkPlugin 自动注入 baggage header
|
||
- [x] 后端可以读取 baggage 传递的信息
|
||
- [x] 动态采样 tracesSampler 实现
|
||
- [x] tracesSampler 函数支持
|
||
- [x] 根据 transaction 上下文动态决定采样率
|
||
- [x] 完整 Span Status 映射增强
|
||
- [x] 数据库操作状态映射(dbStatusToSpanStatus)
|
||
- [x] 自定义操作状态映射(statusToSpanStatus)
|
||
- [x] Span status 与 error 关联(setStatusFromResponse)
|
||
- [x] 错误作为 error Span 加入 Transaction(可选功能)
|
||
|
||
---
|
||
|
||
## 十、兼容性考虑
|
||
|
||
1. **向后兼容**:默认 `tracesSampleRate = 0`,即默认关闭,不影响现有功能
|
||
2. **事件类型新增**:新增 `transaction` 事件类型,后端需支持
|
||
3. **NetworkPlugin**:保持原有 `network` 事件上报,同时新增 Span(可配置开关)
|
||
4. **浏览器兼容性**:不依赖新 API,使用现有能力实现
|
||
|
||
---
|
||
|
||
## 十一、参考资料
|
||
|
||
- [Sentry Tracing 文档](https://docs.sentry.io/product/sentry-basics/concepts/tracing/)
|
||
- [Sentry Performance 数据模型](https://develop.sentry.dev/sdk/performance/)
|
||
- [W3C Trace Context](https://www.w3.org/TR/trace-context/)
|