light-sentry-sdk/docs/07-tracing.md

10 KiB
Raw Blame History

分布式追踪Distributed Tracing设计文档

一、背景与目标

1.1 背景

当前 SDK 已支持错误采集、性能指标Web Vitals、网络请求监控、用户行为采集等能力但缺乏前后端链路打通的分布式追踪能力。当用户遇到"接口慢"或"报错"时,无法关联前端操作与后端服务的内部调用链路,排查问题效率低。

1.2 目标

  • 第一阶段P0:实现 Trace/Span 核心模型 + 前后端 Trace 传播 + 手动 API
  • 第二阶段P1:自动 InstrumentationPage 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 类型:

  • httpHTTP 请求
  • 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 接口

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 接口

interface Transaction extends Span {
  name: string;                  // 事务名称(如 "GET /api/users"
  spans: Span[];                 // 子 Span 列表
  transaction: string;           // 事务名称冗余字段
  type?: string;                 // 事务类型page-load、navigation 等
  sampled?: boolean;             // 是否采样
}

3.3 上报事件类型

新增 transaction 事件类型:

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

LightSentry.init({
  dsn: '...',
  tracingOrigins: [
    'api.example.com',
    /^https:\/\/internal\./,
    'localhost',
  ],
});

默认匹配同源域名。


五、核心模块设计

5.1 TracingManager

职责:

  • 维护当前活跃的 Transaction
  • 维护 Span 栈(当前执行上下文)
  • 生成 trace_id / span_id
  • 管理 Span 的父子关系

关键方法:

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 类

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 类

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 和 datafinish Span
  4. 保持原有 network 事件上报(兼容)

6.2 PerformancePlugin 改造(第二阶段)

改造为 Page Load Transaction

  • Navigation Timing → 拆解为多个 Span
  • Resource Timing → 作为 resource Span
  • Web Vitals → 作为标记点或 measurement

6.3 新增 RouterPlugin第二阶段

监听路由变化:

  • History APIpushState / replaceState / popstate
  • hashchange
  • 每次路由变化创建新的 navigation Transaction

七、对外 API

7.1 顶层 API

// 开始事务
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 配置项

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-tracesampled 标志传递给后端
  4. 未采样的 Transaction 不上报,但仍传递 trace_id用于关联

九、实施计划

第一阶段:核心能力(已完成)

  • 设计文档
  • 新增 Trace/Span 类型定义
  • 实现 Span 类
  • 实现 Transaction 类
  • 实现 TracingManager
  • 改造 NetworkPlugin注入 trace header + 创建 HTTP Span
  • 对外暴露 startTransaction / startSpan API
  • 集成到 Client
  • 编译验证
  • 错误事件自动关联 Trace 上下文
  • Transaction 完成后自动上报
  • Span 栈生命周期管理
  • 未采样 Transaction 不收集 Span性能优化

第二阶段:自动 Instrumentation已完成

  • PerformancePlugin 改造Page Load Transaction
    • Navigation Timing 拆解为 browser.* Span 树
    • Resource Timing 转为 resource.* Span
    • Web Vitals 作为 measurements 附加到 Transaction
    • LCP/FP/FCP 等关键指标标记
  • 新增 RouterPlugin路由变化追踪
    • History API 监听pushState / replaceState / popstate
    • Hash 路由监听hashchange
    • 路由切换期间的 Span 自动关联
  • 新增 InteractionPlugin用户交互追踪
    • 点击/表单提交等交互自动创建 Transaction
    • 交互期间的网络请求自动关联
  • Error 与 Trace 深度集成
    • 错误事件自动附加 contexts.trace
    • 错误作为 error Span 加入当前 Transaction可选

第三阶段:进阶能力(已完成)

  • Baggage header 支持
    • Transaction 携带 user、release 等信息
    • NetworkPlugin 自动注入 baggage header
    • 后端可以读取 baggage 传递的信息
  • 动态采样 tracesSampler 实现
    • tracesSampler 函数支持
    • 根据 transaction 上下文动态决定采样率
  • 完整 Span Status 映射增强
    • 数据库操作状态映射dbStatusToSpanStatus
    • 自定义操作状态映射statusToSpanStatus
    • Span status 与 error 关联setStatusFromResponse
  • 错误作为 error Span 加入 Transaction可选功能

十、兼容性考虑

  1. 向后兼容:默认 tracesSampleRate = 0,即默认关闭,不影响现有功能
  2. 事件类型新增:新增 transaction 事件类型,后端需支持
  3. NetworkPlugin:保持原有 network 事件上报,同时新增 Span可配置开关
  4. 浏览器兼容性:不依赖新 API使用现有能力实现

十一、参考资料