# 分布式追踪(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; // 标签 data?: Record; // 附加数据 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; data: Record; 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/)