npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@autoxing/front-trace

v1.0.20

Published

前端链路追踪 SDK,支持浏览器 / UniApp / Vue 插件 / Node.js / npm,与 Go Tracer Kafka 格式兼容

Readme

FrontTrace SDK 技术文档

版本:v1.0.14(npm:@autoxing/[email protected]) 更新日期:2026-06-05


目录

  1. 架构概览
  2. 上下文生命周期
  3. 环境兼容性
  4. 多框架接入指南
  5. API 参考
  6. 使用示例
  7. 与 Go Tracer 的链路对接
  8. 上报数据格式
  9. 配置项说明
  10. 性能分析
  11. 已知局限与改进建议

一、架构概览

业务代码
  │
  ├── HTTP 拦截(自动,三选一或叠加使用)
  │     ├── initAxisInterceptor()       → 拦截 Axis 全局对象
  │     ├── initUniRequestInterceptor() → 拦截 uni.request(UniApp 专用)
  │     └── initAxiosInterceptor()      → 拦截 axios 实例或全局 axios
  │           ├── 读取 currentCtx → 创建子 Span(HTTP 状态码在响应中精确记录)
  │           └── 注入链路头 x-trace-id / x-span-id / traceparent / x-trace-sample
  │                 ↑ 仅注入到出站请求,不写入上报数据包
  │
  └── 手动 startSpan(可选)
        ├── 自动更新 currentCtx(HTTP 拦截器自动挂在此下)
        ├── 自动 finish(微任务,同步 Span 专用)
        └── 手动 span.finish()(异步 Span 精确计时)
              │
              └── 自动恢复 currentCtx 为父级
                    │
                    ▼
         ┌ ─ ─ ─ ─ ─ 采样判定 ─ ─ ─ ─ ─ ─ ─ ┐
         │  _shouldSample(trace)               │
         │  ├─ SAMPLE_RATE 概率采样            │
         │  ├─ SAMPLE_STATUS_CODES 状态码采样   │
         │  ├─ SAMPLE_MIN_STATUS_CODE 阈值采样  │
         │  └─ SAMPLE_RES_MIN_DURATION 耗时采样 │
         └ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┘
                    │ 通过
                    ▼
             内存队列 _memQueue(扁平 Span[])
                    │
                    ▼ 定时 / 进程/页面退出
         ┌──────────┴────────────────────┐
         │                               │
      Worker 模式(浏览器)          降级模式(Worker 不可用 / Node.js / UniApp 原生)
      Worker 内执行:                主线程执行:
      ├─ _groupToTraces()           ├─ _groupToTraces()
      ├─ IDB 持久化                 ├─ localStorage/内存持久化
      └─ fetch 上报                 └─ fetch/uni.request 上报
         │                               │
         └──────────────┬────────────────┘
                        │
              ┌─────────▼─────────┐
              │   HTTP POST 上报   │
              │ 解析响应 body 提取  │
              │ data.traceControl  │
              └─────────┬─────────┘
                        │
              ┌─────────▼─────────┐
              │ _parseControlFrom │
              │   Response()      │
              │   → _applyControl │
              └─────────┬─────────┘
                        │
              ┌─────────▼─────────┐
              │  _retryQueue       │  ← 失败时入队重试
              │  内存兜底重试队列   │
              └───────────────────┘

运行模式自动降级链路:

ENABLE=false?
  └── 是 → 所有方法静默,零副作用,零内存占用(短路返回)

Node.js 环境?
  ├── 是 → 内存降级模式(无 Worker / 无 localStorage)
  │         退出钩子:process.on('beforeExit') + SIGTERM + SIGINT
  └── 否 → UniApp 环境?(uni 对象存在)
              ├── 是 → Web Worker 可用?(H5 / WebView)
              │          ├── 是 → Blob URL 内联 Worker(优先,无路径依赖)
              │          │         → CSP 限制 blob: 时降级到 WORKER_PATH 外部文件
              │          │         存储:IndexedDB → 内存
              │          └── 否 → 主线程降级模式(App/小程序原生层)
              │                  存储:uni.setStorageSync
              │                  上报:uni.request
              └── 否 → Web Worker 可用?
                          ├── 是 → Blob URL 内联 Worker(优先,无路径依赖)
                          │         → CSP 限制 blob: 时降级到 WORKER_PATH 外部文件
                          │         存储:IndexedDB → 内存
                          └── 否 → 主线程降级模式
                                    存储:localStorage → 内存

二、上下文生命周期

这是理解 SDK 行为的核心。SDK 维护一个全局属性 currentCtx,它在 startSpan/finish 之间自动流转,保证 HTTP 拦截器请求始终挂在当前业务 Span 下。

2.1 自动流转规则

| 时机 | currentCtx 变化 | |---|---| | SDK 初始化 | null | | startSpan(parentCtx, name) 被调用 | 更新为本次 Span 的 childCtx,同时记录 prevCtx = 调用前的 currentCtx | | span.finish() 被调用 | 恢复为 prevCtx(进入前的值) | | startSpan(null, name)currentCtx 不为 null | 挂在当前上下文下(不创建新链路) | | startSpan(null, name)currentCtx 为 null | 创建新根上下文,开启独立链路 |

2.2 完整推演

场景 A:有业务 Span + HTTP 拦截(标准用法)

初始: currentCtx = null

startSpan(null, '加载用户列表')
  resolvedCtx = createRootCtx() = {T1, ''}
  prevCtx_op = null
  currentCtx = {T1, S_op}

axios.get('/api/users')  [axios 拦截器]
  ctx = currentCtx = {T1, S_op}
  startSpan({T1,S_op}, 'GET:/api/users')
    prevCtx_ax = {T1, S_op}
    currentCtx = {T1, S_ax}
    _noAutoFinish = true

[响应拦截器] span_ax._sc = 200, span_ax.finish()
  currentCtx → {T1, S_op} ✅

span_op.finish()
  currentCtx → null ✅

场景 B:无业务 Span,纯 HTTP 拦截

初始: currentCtx = null

axios.get('/api/users')
  ctx = createRootCtx() = {T1, ''}
  startSpan({T1,''}, 'GET:/api/users')
    prevCtx_ax = null
    currentCtx = {T1, S_ax}, _noAutoFinish = true

[响应拦截器] span_ax.finish()
  currentCtx → null ✅

场景 C:为什么 HTTP 拦截 Span 要 _noAutoFinish(Bug 复盘)

❌ 旧行为(已修复):
  axios.get() → 创建 span,微任务 M1 调度 auto-finish
  [M1 早于网络回调运行]
  M1: span.finish(OK) → _done=true, _sc=0(状态码未知)
  [响应拦截器] span._done=true → 直接跳过 → HTTP 状态码丢失!

✅ 新行为(_noAutoFinish = true):
  axios.get() → 创建 span,_noAutoFinish=true
  [M1 检测到 _noAutoFinish=true,跳过]
  [响应拦截器] span._sc=200, span.finish(OK) → 精确记录 ✅

2.3 并行场景注意事项

并行场景下 currentCtx 会被最后一个 startSpan 覆盖,并行时应显式传入 parentCtx

// ✅ 并行推荐写法
const [rootCtx, rootSpan] = $trace.startSpan(null, 'root');
const [, spanA] = $trace.startSpan(rootCtx, 'A');
const [, spanB] = $trace.startSpan(rootCtx, 'B');

三、环境兼容性

| 环境 | Worker 模式 | 降级模式 | 链路头注入 | 说明 | |---|---|---|---|---| | 现代浏览器(Chrome/Firefox/Safari) | ✅ | — | ✅ | | | Android WebView ≥ 4.4 | ✅ | — | ✅ | | | Android WebView < 4.4 | ❌ | ✅(localStorage) | ✅ | | | iOS WKWebView | ✅ | — | ✅ | | | iOS UIWebView(已废弃) | ❌ | ✅(localStorage) | ✅ | | | Electron Renderer | ✅ | — | ✅ | | | 微信小程序 WebView | ❌ | ✅(内存) | ✅ | | | Node.js ≥ 18 | ❌ | ✅(内存) | ✅ | fetch 原生支持 | | Node.js 16–17 | ❌ | ✅(内存) | ✅ | 需全局注入 fetch polyfill | | UniApp H5 | ✅ | — | ✅ | Worker 模式,与浏览器一致 | | UniApp App(原生) | ❌(WebView ✅) | ✅(uni.setStorageSync) | ✅ | WebView 中 H5 页面支持 Worker | | UniApp 微信小程序 | ❌(WebView ✅) | ✅(uni.setStorageSync) | ✅ | WebView 中 H5 页面支持 Worker | | UniApp 支付宝小程序 | ❌ | ✅(uni.setStorageSync) | ✅ | |

Node.js 注意: 始终走内存降级模式;SIGKILL 时数据不可避免丢失。

UniApp 注意: Worker 仅 H5 端生效;存储/上报层自动切换;页面隐藏需手动调用 onAppHide()


四、多框架接入指南

SDK 已发布至 npm:@autoxing/front-trace

安装

npm install @autoxing/front-trace

4.1 浏览器 Script 标签(CDN,无需安装)

<script src="https://unpkg.com/@autoxing/front-trace/dist/trace-sdk.umd.min.js"></script>
<script>
  window.$trace.init({
    ENABLE:          true,
    SERVICE_NAME:    'my-web-app',
    UPLOAD_URL:      'https://trace.example.com/trace/v1.0/upload',
    UPLOAD_INTERVAL: 5000,
  });
  // 如使用 axios
  window.$trace.initAxiosInterceptor(axios);
</script>

4.2 npm / CommonJS(Node.js / webpack)

const $trace = require('@autoxing/front-trace');

$trace.init({
  ENABLE:       true,
  SERVICE_NAME: 'my-node-service',
  UPLOAD_URL:   'https://trace.example.com/trace/v1.0/upload',
});

// Node.js 16-17 需先注入 fetch polyfill
// globalThis.fetch = require('node-fetch');

4.3 Vue 插件(Vue 2 / Vue 3)

Vue 3(推荐):

// main.js
import { createApp } from 'vue';
import App from './App.vue';
import axios from 'axios';
import $trace from '@autoxing/front-trace';

$trace.init({
  ENABLE:       true,
  SERVICE_NAME: 'my-vue3-app',
  UPLOAD_URL:   'https://trace.example.com/trace/v1.0/upload',
});
$trace.initAxiosInterceptor(axios);

const app = createApp(App);
app.use($trace);   // 注册 Vue 插件,组件内可用 this.$trace
app.mount('#app');

注意: app.use($trace, opts) 内部会调用 init(opts),与手动 init() 二选一即可。推荐先手动 init() + initAxiosInterceptor(),初始化位置更明确。

Vue 2:

import Vue from 'vue';
import axios from 'axios';
import $trace from '@autoxing/front-trace';

$trace.init({ ENABLE: true, SERVICE_NAME: 'my-vue2-app', UPLOAD_URL: '...' });
$trace.initAxiosInterceptor(axios);
Vue.use($trace);

4.4 UniApp(全端兼容)

main.js 初始化(最早执行位置):

// main.js(UniApp Vue 3)
import { createSSRApp } from 'vue';
import App from './App.vue';
import $trace from '@autoxing/front-trace';

$trace.init({
  ENABLE:       true,
  SERVICE_NAME: 'my-uniapp',
  UPLOAD_URL:   'https://trace.example.com/trace/v1.0/upload',
});
// init() 在 UniApp 环境下自动调用 initUniRequestInterceptor(),拦截 uni.request,无需手动调用

// ⚠️ 如果项目中同时使用了 axios,需额外手动调用:
// import axios from 'axios';
// $trace.initAxiosInterceptor(axios);

export function createApp() {
  const app = createSSRApp(App);
  return { app };
}

App.vue 桥接页面隐藏事件(重要):

<!-- App.vue -->
<script>
import $trace from '@autoxing/front-trace';
export default {
  onHide() {
    // UniApp 小程序/App 无 pagehide,需手动桥接触发上报
    $trace.onAppHide();
  },
};
</script>

UniApp 各端能力对比:

| 端 | Worker | 持久化 | 上报方式 | 卸载事件 | |---|---|---|---|---| | H5 | ✅ | IndexedDB → localStorage | fetch | pagehide(自动) | | App(原生) | ❌ | uni.setStorageSync | uni.request | 手动 onAppHide() | | 微信小程序 | ❌ | uni.setStorageSync | uni.request | 手动 onAppHide() | | 支付宝小程序 | ❌ | uni.setStorageSync | uni.request | 手动 onAppHide() |


五、API 参考

5.1 $trace.init(options?)

初始化 SDK。ENABLE 默认为 false,必须显式设为 true 才会真正启动。

npm / UniApp 模式下必须手动调用;浏览器 Script 引入时已自动调用(但仍需配置 ENABLE: true)。

支持多次调用,后调用的配置合并覆盖。配置项详见第九章

5.2 $trace.initAxisInterceptor()

拦截全局 Axis 对象(window.Axis / global.Axis),自动注入链路头并记录 Span。ENABLE=false 时直接 return。

5.3 $trace.initAxiosInterceptor(axiosInstance?)

拦截 axios,通过 axios.interceptors.request/response.use 注入。ENABLE=false 时直接 return。

// 方式1:拦截全局 axios(自动查找 window.axios / 全局 axios)
$trace.initAxiosInterceptor();

// 方式2:直接传入 import 的 axios 对象(推荐,适用于 Vue/UniApp 等模块化项目)
import axios from 'axios';
$trace.initAxiosInterceptor(axios);

// 方式3:拦截自定义 axios 实例
const instance = axios.create({ baseURL: 'https://api.example.com' });
$trace.initAxiosInterceptor(instance);

5.4 $trace.initUniRequestInterceptor()

拦截 uni.request(UniApp 专用)。init() 在 UniApp 环境下自动调用,通常无需手动调用。ENABLE=false 时直接 return。

5.5 $trace.startSpan(parentCtx, name)

开启一个 Span,返回 [childCtx, span]ENABLE=false 时返回无副作用的 noop span(所有方法均可正常调用,但不记录任何数据)。

| 参数 | 类型 | 说明 | |---|---|---| | parentCtx | object \| null | 父上下文。传 null 时:若 currentCtx 不为 null 则挂在当前上下文,否则创建新根链路 | | name | string | Span 名称 |

| 返回值 | 说明 | |---|---| | childCtx | 传给下一个 startSpan:传相同 ctx → 并行;传 childCtx → 嵌套 | | span | 同步操作默认微任务自动 finish;异步操作手动调用 span.finish() 精确计时 |

5.6 Span 方法一览

所有方法均支持链式调用(返回 this),且 ENABLE=false 时均为 noop,不会报错。

| 方法 | 对应 Go 策略 | 上报字段 | 说明 | |---|---|---|---| | span.finish(status?) | span.End() | — | 结束 Span,自动恢复 currentCtxstatus 默认 SpanStatus.OK | | span.setTag(key, value) | StartSpan(tags) | ops[].tgs | 设置单个 Tag | | span.setTags({k:v,...}) | StartSpan(tags) | ops[].tgs | 批量设置 Tags | | span.addEvent(name, data?) | AddSpanEvent() | ops[].evs[]{n,t,d} | 记录事件,t 为 Unix 纳秒 | | span.recordError(err, attrs?) | RecordError() | ops[].ers[]{er,t,as} | 记录错误,同时将首条错误写入顶层 em | | span.setBizStatus(code, msg) | SetBizResponse() | 顶层 bs + bm | 设置业务状态码和消息,仅根 Span 生效 |

5.7 $trace.SpanStatus

$trace.SpanStatus.OK    // 'ok'
$trace.SpanStatus.ERROR // 'error'

5.8 业务身份 API(权限检索的核心)

业务身份字段是 trace-worker 诊断系统按权限检索链路的核心维度。正确设置这三个 ID,可以:

  • 按用户检索:查找某用户的所有历史调用链路
  • 按业务/楼层检索:定位某业务模块的异常分布
  • 按设备/机器人检索:追踪特定机器的所有操作记录
  • 按用户+设备组合过滤:精确分析某用户操作某机器时的完整行为链路

SDK 提供三个身份设置方法,分别对应 trace-worker 中的检索索引,建议在全链路关键节点都正确设置

| 方法 | 对应 Go diagnosis.Span 字段 | trace-worker 检索字段 | 生命周期 | |---|---|---|---| | $trace.setUserId(id) | UserIDuid) | user_id | 登录后调用一次,持久存在,直到退出登录 | | $trace.setBusinessId(id) | BusinessIDbid) | business_id | 事件触发时设置,事件结束后调用 clearEventContext() 清空 | | $trace.setDeviceId(id) | DeviceIDdid) | device_id | 事件触发时设置,事件结束后调用 clearEventContext() 清空 | | $trace.clearEventContext() | — | — | 清空 businessId + deviceId不影响 userId |

设计原则(重要):

  • userId — 登录态唯一标识(如 openId),调用 setUserId() 后在当前页面生命周期内持续有效,每次上报的 Trace 对象中自动附带 uid 字段。退出登录时调用 setUserId('') 清除。
  • businessId / deviceId — 属于 事件级上下文,在触发特定业务操作(如选中某楼层、绑定某台机器人)时设置,操作完成后必须调用 clearEventContext() 清空,否则会污染后续无关事件的 Span(导致诊断系统将不相关的操作归因到错误的业务/设备维度)。
  • 三者可以同时存在,诊断系统支持按任意维度组合过滤。例如 userId + deviceId 的组合可精确检索"某用户在某机器人上的全部操作链路"。

典型业务身份注入链路示例:

// 1. 用户登录 → 全局设置 userId(持续有效)
$trace.setUserId(userInfo.openId);

// 2. 用户进入某个业务楼层 → 设置 businessId
$trace.setBusinessId(currentBiz.id);
// 此时所有 Span 自动携带 uid + bid

// 3. 用户选择某台机器人执行任务 → 设置 deviceId
$trace.setDeviceId(selectedRobot.serialNo);
// 此时所有 Span 携带 uid + bid + did

// 4. 执行任务操作...
const [, span] = $trace.startSpan(null, 'sendTask');
await axios.post('/api/robot/task', { ... });
span.finish();

// 5. 任务完成 → 清空事件级上下文(userId 保留)
$trace.clearEventContext();
// 后续 Span 只携带 uid,不再携带 bid/did

// 6. 用户退出登录 → 清除 userId
$trace.setUserId('');

5.9 $trace.onAppHide()(UniApp 专用)

App.vueonHide 中调用,触发页面隐藏时的立即上报。

5.10 $trace.install(app, options?)(Vue 插件)

Vue 插件接口,等价于 init(options) + 挂载 this.$trace,通过 app.use() / Vue.use() 调用。

5.11 $trace.setCtx(ctx)(高级)

强制设置 currentCtx,用于 SPA 路由切换等需要手动管理上下文的场景。ENABLE=false 时直接 return。


六、使用示例

四条核心规则

  1. startSpan(null, name) → 有 currentCtx 则挂在当前链路,否则自动创建新根链路
  2. 相同 ctx 传给多个 startSpan并行(兄弟节点)
  3. childCtx 作为下一个 startSpan 的父 → 嵌套(父子节点)
  4. HTTP 拦截器自动挂在 currentCtxfinish 后自动恢复,最终保证为 null

6.1 最简用法(HTTP 拦截自动追踪,零侵入)

// init + initAxiosInterceptor 后,所有 axios 请求自动追踪,无需业务侧任何改动
axios.get('/api/user/list');
axios.post('/api/order/create', { items: [] });

6.2 业务 Span + HTTP 请求自动挂载

async function handleLogin(username, password) {
  const [, loginSpan] = $trace.startSpan(null, 'login');
  try {
    await axios.post('/api/auth/login', { username, password });
    loginSpan.finish($trace.SpanStatus.OK);
  } catch (e) {
    loginSpan.recordError(e).finish($trace.SpanStatus.ERROR);
    throw e;
  }
}

链路图:

login
  └── POST:/api/auth/login   ← axios 自动挂在 login 下

6.3 嵌套追踪(父子 Span)

async function handleLogin(username, password) {
  const [ctx1, validateSpan] = $trace.startSpan(null, 'login:validateParams');
  validateInput(username, password); // 同步,微任务自动 finish

  const [ctx2, loginSpan] = $trace.startSpan(ctx1, 'login:request');
  await axios.post('/api/auth/login', { username, password })
    .then(() => loginSpan.finish($trace.SpanStatus.OK))
    .catch(e => { loginSpan.recordError(e).finish($trace.SpanStatus.ERROR); throw e; });

  const [, profileSpan] = $trace.startSpan(ctx2, 'login:fetchProfile');
  await axios.get('/api/user/profile')
    .then(() => profileSpan.finish($trace.SpanStatus.OK))
    .catch(e => { profileSpan.recordError(e).finish($trace.SpanStatus.ERROR); });
}

链路图:

login:validateParams
  └── login:request
        └── login:fetchProfile

6.4 并行追踪(兄弟 Span)

async function loadUserPage(userId) {
  const [rootCtx, rootSpan] = $trace.startSpan(null, 'loadUserPage');
  const [, profileSpan] = $trace.startSpan(rootCtx, 'fetchProfile');
  const [, ordersSpan]  = $trace.startSpan(rootCtx, 'fetchOrders');

  await Promise.allSettled([
    axios.get('/api/user/profile', { params: { userId } })
      .then(() => profileSpan.finish($trace.SpanStatus.OK))
      .catch(e => profileSpan.recordError(e).finish($trace.SpanStatus.ERROR)),
    axios.get('/api/user/orders', { params: { userId } })
      .then(() => ordersSpan.finish($trace.SpanStatus.OK))
      .catch(e => ordersSpan.recordError(e).finish($trace.SpanStatus.ERROR)),
  ]);
  rootSpan.finish($trace.SpanStatus.OK);
}

链路图:

loadUserPage
  ├── fetchProfile
  └── fetchOrders

6.5 Tags / Event / Error / BizStatus(全功能示例)

async function sendRobotTask(robot, task) {
  const [, span] = $trace.startSpan(null, 'sendRobotTask');

  // 设置 Tags(对应 Go StartSpan(tags) → ops[].tgs)
  span.setTags({ robotId: robot.serialNo, taskType: task.type, priority: task.priority });

  // 记录事件(对应 Go AddSpanEvent → ops[].evs)
  span.addEvent('task.prepared', { taskId: task.id });

  try {
    const res = await axios.post('/api/robot/task', task);

    span.addEvent('task.submitted', { serverTaskId: res.data.taskId });

    // 设置业务状态码(对应 Go SetBizResponse → trace.bs + trace.bm)
    // 仅根 Span 生效,写入顶层 trace 对象
    if (res.data.code !== 200) {
      span.setBizStatus(res.data.code, res.data.message);
    }

    span.finish($trace.SpanStatus.OK);
  } catch (e) {
    // 记录错误(对应 Go RecordError → ops[].ers)
    span.recordError(e, { phase: 'http', robotId: robot.serialNo });
    span.finish($trace.SpanStatus.ERROR);
    throw e;
  }
}

6.6 业务身份注入(userId / businessId / deviceId)

重要: userIdbusinessIddeviceId 是 trace-worker 诊断系统按权限检索链路的核心字段。 正确设置这三个 ID 才能在后端实现按用户、楼层、机器人的链路检索和异常分析。 三者关系:userId 登录态持久有效,businessId/deviceId 事件级需及时清空。

// ─── 登录成功后(全局执行一次)────────────────────────────────────
$trace.setUserId(userInfo.openId);   // 持久存在,整个会话有效

// ─── 用户选择业务楼层 + 机器人,开始操作 ─────────────────────────
$trace.setBusinessId(currentBiz.id);
$trace.setDeviceId(selectedRobot.serialNo);

// 此后所有 Span 自动携带 uid/bid/did
const [, span] = $trace.startSpan(null, 'sendTask');
await axios.post('/api/robot/task', payload);
span.finish($trace.SpanStatus.OK);

// ─── 操作完成,清空事件级上下文 ─────────────────────────────────
$trace.clearEventContext();   // businessId + deviceId 清空;userId 保留
// 此后的 Span 只携带 uid,不携带 bid/did

6.7 SPA 路由切换追踪

let _routeSpan = null;

router.beforeEach((to, from, next) => {
  const [routeCtx, routeSpan] = $trace.startSpan(null, `navigate:${to.path}`);
  $trace.setCtx(routeCtx);
  _routeSpan = routeSpan;
  next();
});

router.afterEach(() => {
  if (_routeSpan) {
    _routeSpan.finish($trace.SpanStatus.OK);
    _routeSpan = null;
  }
});

七、与 Go Tracer 的链路对接

7.1 链路头对照

| 前端注入头 | Go 常量 | 后端行为 | |---|---|---| | x-trace-id: {traceId} | liteTraceHeaderTraceID | 后端提取作为本次请求的 TraceID | | x-span-id: {spanId} | liteTraceHeaderSpanID | 后端以此作为 ParentSpanID,前端 Span 成为后端 Span 的父节点 | | traceparent: 00-{tid}-{sid}-01 | W3C traceparent | 兼容 APISIX 网关、OpenTelemetry 生态 | | x-trace-sample: 1 | liteTraceHeaderSample | 告知后端强制采样此请求 |

重要: 这些链路头只注入到出站 HTTP 请求中,不写入上报数据包(hs 字段已移除)。

7.2 前后端链路连接原理

前端 Span(traceId=AAA, spanId=111, parentSpanId='')
  └── HTTP 请求携带 x-trace-id=AAA, x-span-id=111
        │
        ▼
   Go 后端 Middleware
        ├── 提取 x-trace-id=AAA → 本次 TraceID=AAA
        └── 提取 x-span-id=111  → ParentSpanID=111
              │
              ▼
        后端 Span(traceId=AAA, spanId=222, parentSpanId=111)

7.3 业务身份字段传递路径与权限检索

前端 SDK 直接写入 uid/bid/did 字段上报;后端服务通过 HTTP Header(x-openid / x-business-id / x-device-id)传递。trace-worker 的 toSpans 转换时优先取直接字段,为空时从 Header 回落

前端上报:{ uid: "user123", bid: "biz456", did: "robot789" }
                │
                ▼
        trace-worker toSpans
                │  ct.UserID != ""     → 直接使用
                │  ct.UserID == ""     → headers["x-openid"]
                │  ct.BusinessID != "" → 直接使用
                │  ct.BusinessID == "" → headers["x-business-id"]
                │  ct.DeviceID != ""   → 直接使用
                │  ct.DeviceID == ""   → headers["x-device-id"]
                ▼
        diagnosis.Span { UserID: "user123", BusinessID: "biz456", DeviceID: "robot789" }

权限检索说明: trace-worker 诊断系统对 uid / bid / did 字段建立了独立索引,支持以下三种检索模式:

  • 按用户检索user_id 索引):查询指定用户的所有历史链路,用于用户行为回溯和故障排查
  • 按业务/楼层检索business_id 索引):定位特定业务模块的异常分布和性能基线
  • 按设备/机器人检索device_id 索引):追踪特定设备的全量操作记录
  • 组合检索:支持 user_id + device_idbusiness_id + device_id 等多维组合过滤

因此前端 SDK 正确设置 userId / businessId / deviceId 是诊断系统正常运行的前提。 建议在全链路的关键操作节点(登录、选业务、选设备、事件完成)都调用对应 API,确保身份维度不遗漏。

7.4 上报接口(trace-worker)

前端 SDK 上报到 trace-worker 服务:

POST /trace/v1.0/upload
Content-Type: application/json
Body: [childTrace, ...]   ← JSON 数组,批量上报

trace-worker 收到后:

  1. 从请求中提取真实客户端 IP,注入每条 trace 的 cip 字段(自动处理反向代理)
  2. 批量 InsertMany 直接入库ax_raw_spans MongoDB 集合)
  3. 与 Kafka 消息路径完全等价,经 WorkerManager 重放到诊断流程

八、上报数据格式

每次上报的 JSON Body 为 Trace 对象数组,与 Go Tracer Kafka 消息(childTrace)格式完全兼容:

[
  {
    "tid": "ce6e59474608400281a57beb3da5576f",
    "st":  1779263662070000000,
    "et":  1779263662829100000,
    "m":   "POST",
    "p":   "/api/robot/task",
    "sc":  200,
    "pid": "",
    "sid": "e9f9e784a3ab40b380d21a233ea35dcc",
    "sn":  "robot-h5",
    "sip": "frontend",
    "uid": "oHrGS5bNcK0waXofc5WGNm8TJXxY",
    "bid": "biz-floor-3",
    "did": "robot-SN-00123",
    "bs":  4001,
    "bm":  "任务队列已满",
    "ops": [
      {
        "n":   "sendRobotTask",
        "sid": "a1b2c3d4e5f60001",
        "pid": "",
        "st":  1779263662070000000,
        "et":  1779263662100000000,
        "tgs": { "taskType": "navigate", "priority": 1 },
        "evs": [
          { "n": "task.prepared", "t": 1779263662075000000, "d": { "taskId": "T001" } },
          { "n": "task.submitted", "t": 1779263662095000000, "d": { "serverTaskId": "S999" } }
        ]
      }
    ]
  }
]

8.1 顶层 Trace 字段

| 字段 | 类型 | Go childTrace | SDK 填写 | 说明 | |---|---|---|---|---| | tid | string | tid | ✅ | TraceID(32位hex) | | st | int64 | st | ✅ | 开始时间,Unix 纳秒(JS ms × 1,000,000) | | et | int64 | et | ✅ | 结束时间,Unix 纳秒 | | m | string | m | ✅ | HTTP 方法(GET/POST/...)或 FRONTEND(手动 Span)| | p | string | p | ✅ | URL 路径部分(去掉 scheme://host),或操作名 | | sc | int | sc | ✅ | HTTP 状态码(由响应拦截器精确记录)| | pid | string | pid | ✅ | 上游 SpanID(空 = 根链路)| | sid | string | sid | ✅ | 本 Span 的 SpanID | | sn | string | sn | ✅ | 服务名(CONFIG.SERVICE_NAME)| | sip | string | sip | ✅ | 固定值 "frontend",标识来源为前端 | | cip | string | cip | ❌ 服务端注入 | 客户端真实 IP,由 trace-worker 从请求头提取 | | em | string | em | ✅(可选)| 错误信息,recordError() 首条或 finish(ERROR) 时写入 | | bs | int | bs | ✅(可选)| 业务状态码,setBizStatus(code, msg) 写入,仅根 Span 生效 | | bm | string | bm | ✅(可选)| 业务状态消息,配合 bs 使用 | | uid | string | uid | ✅(可选)| 用户 ID,setUserId() 设置后自动附带 | | bid | string | bid | ✅(可选)| 业务 ID,setBusinessId() 设置后自动附带 | | did | string | did | ✅(可选)| 设备/机器人 ID,setDeviceId() 设置后自动附带 | | hs | — | hs | ❌ 不上报 | W3C 链路头只注入出站请求,不写入数据包 | | ops | array | ops | ✅ | 子操作列表 |

8.2 ops[] 子操作字段(对应 Go childOp

| 字段 | 类型 | Go childOp | 说明 | |---|---|---|---| | n | string | n | 子操作名称 | | sid | string | sid | 子 Span ID | | pid | string | pid | 父 Span ID | | st | int64 | st | 开始时间,Unix 纳秒 | | et | int64 | et | 结束时间,Unix 纳秒 | | tgs | object | tgs | Tags,setTag/setTags() 写入(可选)| | evs | array | evs | 事件列表,addEvent() 写入(可选)| | evs[].n | string | n | 事件名称 | | evs[].t | int64 | t | 事件时间,Unix 纳秒 | | evs[].d | object | d | 事件附加数据 | | ers | array | ers | 错误列表,recordError() 写入(可选)| | ers[].er | string | er | 错误描述 | | ers[].t | int64 | t | 错误发生时间,Unix 纳秒 | | ers[].as | object | as | 错误附加属性 |

时间精度: JS Date.now() 精度为毫秒,统一 × 1,000,000 转为纳秒,与 Go time.Now().UnixNano() 一致。


九、配置项说明

| 配置项 | 类型 | 默认值 | 说明 | |---|---|---|---| | ENABLE | boolean | false | 总开关。默认关闭,代码集成后功能完全静默;显式设为 true 才启动 | | SERVICE_NAME | string | 'frontend' | 服务名,写入 sn 字段,建议按项目设置(如 'robot-h5')| | MAX_SPANS | number | 500 | 内存队列最大 Span 数,超限按完整链路丢弃最旧 | | UPLOAD_INTERVAL | number | 3000 | 定时上报间隔(ms)| | UPLOAD_BATCH | number | 50 | 单次上报最大 Trace 条数 | | EXPIRE | number | 604800000(7天) | 持久化数据过期时间(ms)| | UPLOAD_URL | string | string[] | '/trace/v1.0/upload' | 上报接口地址,支持单地址字符串或多地址数组 | | UPLOAD_URL_SELECTION | string | 'random' | 多地址选择策略:'random'(随机)/ 'round-robin'(轮询)/ 'primary'(主节点,仅失败时切换)| | UPLOAD_HEADERS | object | {'Authorization': 'APPCODE c328ac72cc814008a5c664c637cab8ab'} | 上报请求自定义 Header,未填时自动补充默认 Authorization | | WORKER_PATH | string | './trace.worker.js' | Worker 文件路径,仅当 CSP 禁止 blob: 时才需配置 | | SAMPLE_RATE | number | 1 | 采样率(1=全量采集;<1 按比例随机采样,如 0.1=10%)| | SAMPLE_MIN_STATUS_CODE | number | 0 | 最小强制采样状态码(如 400=所有 >=400 的状态码强制采样;0=不启用)| | SAMPLE_STATUS_CODES | number[] | [] | 特定状态码强制采样列表,如 [499, 502, 503] | | SAMPLE_STATUS_CODE_FILTERS | number[] | [] | 从强制采样中排除的状态码,如与 SAMPLE_MIN_STATUS_CODE 配合排除特定码 | | SAMPLE_RES_MIN_DURATION | number | 0 | 最小强制采样耗时(ms),超过该时长的请求强制采样(0=不启用)| | DEBUG | boolean | false | 调试日志开关(true=输出 console.debug/warnfalse=完全静默)|

推荐生产配置:

// 单地址
$trace.init({
  ENABLE:          true,
  SERVICE_NAME:    'your-project-name',
  UPLOAD_URL:      'https://trace.example.com/trace/v1.0/upload',
  UPLOAD_INTERVAL: 5000,
});

// 多地址(随机负载均衡)
$trace.init({
  ENABLE:          true,
  SERVICE_NAME:    'your-project-name',
  UPLOAD_URL:      ['https://trace1.example.com/upload', 'https://trace2.example.com/upload'],
  UPLOAD_URL_SELECTION: 'random',
});

// 多地址(主备模式)
$trace.init({
  ENABLE:          true,
  SERVICE_NAME:    'your-project-name',
  UPLOAD_URL:      ['https://primary.example.com/upload', 'https://backup.example.com/upload'],
  UPLOAD_URL_SELECTION: 'primary',
});

// 采样控制 + 调试日志
$trace.init({
  ENABLE:                    true,
  SERVICE_NAME:              'robot-h5',
  UPLOAD_URL:                'https://trace.example.com/trace/v1.0/upload',
  UPLOAD_INTERVAL:           5000,
  SAMPLE_RATE:               0.5,            // 50% 随机采样
  SAMPLE_MIN_STATUS_CODE:    400,            // 所有 4xx/5xx 强制采样
  SAMPLE_RES_MIN_DURATION:   3000,           // 超过 3 秒的请求强制采样
  DEBUG:                     false,          // 关闭调试日志
});

按环境动态控制:

$trace.init({
  ENABLE:       process.env.NODE_ENV === 'production',
  SERVICE_NAME: 'robot-h5',
  UPLOAD_URL:   process.env.VUE_APP_TRACE_URL,
});

十、性能分析

10.1 主线程影响(Worker 模式)

| 操作 | 执行位置 | 主线程耗时 | |---|---|---| | genId() | 主线程 | ≈ 0.01ms | | startSpan() | 主线程 | ≈ 0.05ms | | 拦截器注入(axios/uni.request) | 主线程 | ≈ 0.1ms,仅追加 4 个 Header | | postMessage() | 主线程→Worker | ≈ 0.2ms | | _shouldSample()(采样判定) | 主线程 | ≈ 0.01ms | | _groupToTraces()(分组排序+字段映射) | Worker | ✅ 零主线程影响 | | IndexedDB 读写 / fetch 上报 | Worker | ✅ 零主线程影响 |

Worker 模式下,主线程除 startSpan()postMessage() 外无任何数据计算开销,对 60FPS(16.7ms/帧)的影响可以忽略。

10.2 降级模式(无 Worker)主线程影响

| 操作 | 主线程耗时 | |---|---| | _groupToTraces()(50条 Span) | ≈ 0.5ms | | _httpPost() / sendBeacon | 异步,不阻塞 | | localStorage 读写 | ≈ 0.1ms |

10.2 内存占用估算

| 项目 | 单条大小 | 500条上限 | |---|---|---| | Span 对象(内存队列) | ≈ 300 字节 | ≈ 150 KB | | Trace 对象(持久化) | ≈ 500 字节 | ≈ 250 KB | | _retryQueue(重试队列) | ≈ 500 字节 | ≈ 250 KB(与持久化共享)|

10.3 数据可靠性

| 场景 | 机制 | 可靠性 | |---|---|---| | 正常关闭页面(浏览器) | sendBeacon | ⭐⭐⭐⭐⭐ | | 移动端切换 App(bfcache) | pagehide + sendBeacon | ⭐⭐⭐⭐⭐ | | 网络断开时关闭页面 | IDB / localStorage 持久化,下次启动重试 | ⭐⭐⭐⭐ | | 上报失败(无持久化环境) | _retryQueue 内存重试队列,下次定时周期重试 | ⭐⭐⭐⭐ | | Worker 崩溃 | 主线程降级自动接管 | ⭐⭐⭐⭐ | | Node.js 正常退出 | process.on('beforeExit') 异步上报 | ⭐⭐⭐⭐ | | Node.js SIGTERM/SIGINT | 同步清理 + process.exit() | ⭐⭐⭐ | | Node.js SIGKILL | ❌ 不可避免丢失 | — |


十一、已知局限与改进建议

| 优先级 | 问题 | 当前状态 / 建议方案 | |---|---|---| | — | 采样率控制 | ✅ v1.0.14 已实现SAMPLE_RATE + SAMPLE_STATUS_CODES + SAMPLE_MIN_STATUS_CODE + SAMPLE_RES_MIN_DURATION | | 高 | Node.js 无持久化 | _retryQueue 内存重试队列已兜底;进程崩溃仍不可避免丢失;可进一步定期序列化写文件 | | 中 | 并行 HTTP 请求 currentCtx 无法精确分配 | 并行场景需显式传 ctx,见2.3 节 | | 中 | 不支持原生 fetch / XMLHttpRequest 拦截 | 增加 initFetchInterceptor() / initXHRInterceptor() | | 低 | $trace.getHeaders(span) 辅助 | Node.js 手动注入链路头较繁琐,可封装辅助方法 |