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

hx-web-tracker

v0.2.3

Published

Lightweight web tracking SDK (SLS client) extracted from growth project

Readme

hx-web-tracker

轻量级前端埋点 SDK,基于阿里云 SLS(Simple Log Service)日志服务,提供自动化的用户行为追踪能力。

核心特性:

  • 🎯 自动采集用户行为:点击、输入、页面浏览、HTTP 请求
  • 📦 批量上报与离线缓存:支持本地持久化,网络恢复后自动重试
  • 🔐 安全的数据脱敏:内置字段掩码策略,保护敏感信息
  • 🎨 丰富的上下文增强:自动附加会话、用户、设备、视图等上下文信息
  • 🔗 请求追踪与动作关联:支持 trace_id 和 action_id 串联前后端日志
  • Session Replay 回放:可按采样录制用户会话,独立队列上报,不影响普通埋点链路

源码结构: 核心代码位于 core/(追踪引擎)、collectors/(行为采集器)、context/(上下文管理)三个目录。

安装

npm i hx-web-tracker

快速集成示例

在你的项目里新增一个本地封装(例如 src/utils/logger.ts),负责初始化、注册采集器、提供 track/setView/refreshUser 等入口。下面是一份可直接拷贝的示例,包含必要的 JSDoc 说明,按注释替换占位符即可开箱使用。

import type { ViewInfo, UserInfo } from 'hx-web-tracker';
import {
  init as trackerInit,
  track as coreTrack,
  setContextView,
  refreshContextUser,
  setTraceId,
  startReplayRecording,
  stopReplayRecording,
  getReplayState,
  registerCollector,
  createClickCollector,
  createInputCollector,
  createRequestCollector,
  createViewCollector,
  createReplayCollector,
} from 'hx-web-tracker';
import router from '@/router';
import axios from '@/utils/request'; // 替换为你的 axios 实例

const ENV = import.meta.env;
const LOG_ENDPOINT = '<你的 SLS endpoint>'; // 例:cn-beijing.log.aliyuncs.com
const LOG_PROJECT = '<你的 project>';
const LOG_LOGSTORE = ENV.MODE === 'development' ? '<test_logstore>' : '<prod_logstore>';
const LOG_TOPIC = '<可选 topic>';

const REPLAY_ENABLED = ENV.VITE_TRACKER_REPLAY_ENABLED === 'true';
const REPLAY_SAMPLE_RATE = Number(ENV.VITE_TRACKER_REPLAY_SAMPLE_RATE ?? 0.01);
const REPLAY_START_MANUALLY = ENV.VITE_TRACKER_REPLAY_START_MANUALLY === 'true';

/**
 * 获取临时凭证(请接入你们的鉴权服务,不要硬编码 AK/SK)。
 */
async function fetchCredential() {
  return {
    accessKeyId: '',
    accessKeySecret: '',
    securityToken: '',
    expireAt: Date.now() + 3600_000,
  };
}

/**
 * 兼容 Vue Router 3(currentRoute 为对象)与 4(currentRoute 为 ref)。
 * 将路由对象转换为视图信息,用于 view_start/view_end。
 */
function unwrapRoute(route: any) {
  if (!route || typeof route !== 'object') return route;
  return 'value' in route ? (route as any).value : route;
}

function toRouteInfo(route: any): ViewInfo {
  const r = unwrapRoute(route);
  if (!r || typeof r !== 'object') {
    return {
      id: typeof window !== 'undefined' ? window.location.pathname : 'unknown',
    };
  }
  const fullPath = r.fullPath ?? r.path ?? 'unknown';
  const metaTitle = r.meta?.title_name ?? r.meta?.title;
  return {
    id: String(fullPath),
    name: metaTitle ? String(metaTitle) : undefined,
    title: metaTitle ? String(metaTitle) : undefined,
    path: r.path ? String(r.path) : undefined,
  };
}

/**
 * 追加自定义字段:页面 URL/标题与视口大小。
 * page_title 优先取 document.title,page_url 优先用视图 id/path 构造。
 */
function createCustomFieldResolvers() {
  return [
    (context: { view?: ViewInfo }) => {
      const hasWindow = typeof window !== 'undefined';
      const hasDocument = typeof document !== 'undefined';
      const viewportWidth = hasWindow && typeof window.innerWidth === 'number' ? window.innerWidth : -1;
      const viewportHeight = hasWindow && typeof window.innerHeight === 'number' ? window.innerHeight : -1;
      const view = context.view ?? {};
      const viewTitle = (view.title ?? view.name) as string | undefined;
      const docTitle = hasDocument && typeof document.title === 'string' ? document.title : '';
      const pageTitle =
        docTitle.trim().length > 0
          ? docTitle
          : typeof viewTitle === 'string' && viewTitle.trim().length > 0
            ? viewTitle
            : '';

      const viewId = typeof view.id === 'string' ? view.id : '';
      const viewPath = typeof view.path === 'string' ? view.path : '';
      const isAbsoluteUrl = /^https?:\/\//i.test(viewId);
      const pageUrl =
        isAbsoluteUrl && viewId
          ? viewId
          : hasWindow && viewPath
            ? `${window.location.origin}${viewPath}`
            : hasWindow && viewId
              ? `${window.location.origin}${viewId}`
              : hasWindow
                ? window.location.href
                : viewId || viewPath;

      return {
        page_url: pageUrl,
        page_title: pageTitle,
        viewport_width: viewportWidth,
        viewport_height: viewportHeight,
      };
    },
  ];
}

let initialized = false;
let initPromise: Promise<void> | null = null;
let manualViewSetter: ((view: ViewInfo) => void) | null = null;

function registerBuiltInCollectors() {
  registerCollector('click', createClickCollector(), { priority: 80, override: true });
  registerCollector('input', createInputCollector({ maskStrategy: 'none' }), { priority: 70, override: true });
  registerCollector(
    'request',
    createRequestCollector({
      instrumentFetch: true,
      instrumentXHR: true,
      axiosInstances: [axios as any],
      ignoredUrls: [/\\.js$/i, /\\.css$/i, /\\.map$/i, /__vite_ping/],
    }),
    { priority: 60, override: true },
  );
  registerCollector(
    'view',
    createViewCollector({
      router,
      getRouteInfo: toRouteInfo, // 兼容 v3/v4 的 currentRoute/afterEach 入参
      registerManualSetter: (setter) => {
        manualViewSetter = setter;
      },
    }),
    { priority: 90, override: true },
  );
  registerCollector(
    'replay',
    createReplayCollector({
      stopOnPageHide: true,
    }),
    { priority: 50, override: true },
  );
}

/**
 * 初始化埋点 SDK(幂等);可在应用入口直接调用,或在首次 track/send 前懒加载。
 */
export async function initTracker(): Promise<void> {
  if (initialized) return;
  if (initPromise) return initPromise;
  registerBuiltInCollectors();
  initPromise = trackerInit({
    endpoint: LOG_ENDPOINT,
    project: LOG_PROJECT,
    logstore: LOG_LOGSTORE,
    topic: LOG_TOPIC,
    debug: !!ENV.DEV,
    fetchCredential,
    context: {
      getUserInfo: async (): Promise<UserInfo> => ({ id: '', name: '', role: '' }), // TODO: 返回真实用户
      resolveView: () => toRouteInfo(router.currentRoute),
      customFieldResolvers: createCustomFieldResolvers(),
    },
    replay: {
      enabled: REPLAY_ENABLED,
      sampleRate: REPLAY_SAMPLE_RATE,
      startManually: REPLAY_START_MANUALLY,
      maxDurationMs: 10 * 60 * 1000,
      maxReplayRawBytes: 8 * 1024 * 1024,
      flushIntervalMs: 5000,
      maxChunkBytes: 128 * 1024,
      maxSingleEventBytes: 1024 * 1024,
      maskAllInputs: true,
      privacyLevel: 'strict',
      uploadBatchSize: 5,
      uploadFlushIntervalMs: 2000,
      uploadMaxRetries: 5,
      uploadStorageKey: 'tracker-replay-queue',
      uploadMaxQueueBytes: 4 * 1024 * 1024,
      uploadMaxPersistedBytes: 4 * 1024 * 1024,
    },
  })
    .then(() => {
      initialized = true;
    })
    .catch((error) => {
      initPromise = null;
      throw error;
    });
  return initPromise;
}

/**
 * 统一导出的 tracker 对象,便于业务侧调用。
 */
export const tracker = {
  /**
   * 手动初始化,可选(track 时会自动懒加载)。
   */
  init: initTracker,

  /**
   * 上报业务事件。
   */
  track(eventType: string, payload?: Record<string, unknown>) {
    return initTracker().then(() => {
      coreTrack({ event_type: eventType, payload });
    });
  },

  /**
   * 更新当前视图(若需手动埋点页面切换)。
   */
  setView(view: ViewInfo) {
    return initTracker().then(() => {
      if (manualViewSetter) {
        manualViewSetter(view);
      } else {
        setContextView(view);
      }
    });
  },

  /**
   * 刷新用户上下文(登录/切换用户后调用)。
   */
  refreshUser(user?: Partial<UserInfo>) {
    return initTracker().then(() => refreshContextUser(user as UserInfo));
  },

  /**
   * 手动开始回放录制;仅在 replay.enabled=true 后生效。
   */
  startReplay(options?: { force?: boolean; reason?: string }) {
    return initTracker().then(() => startReplayRecording(options));
  },

  /**
   * 手动停止回放录制,并触发最终分片收尾。
   */
  stopReplay(reason = 'manual_stop') {
    stopReplayRecording({ reason });
  },

  /**
   * 获取当前回放状态,可用于调试或展示 replay_id。
   */
  getReplayState,

  /**
   * 设置 traceId 以串联前后端日志。
   */
  setTraceId,
};

使用方式:

  • 在应用入口(如 main.ts)调用 void tracker.init(),或在首次 tracker.track() 前懒加载。
  • 路由已自动注册视图埋点,如需手动控制页面视图可调用 tracker.setView({ id, title, path })
  • 登录/切换用户后调用 tracker.refreshUser(user) 刷新上下文。
  • 需要链路追踪时调用 tracker.setTraceId(traceId)
  • 需要开启回放时,将 VITE_TRACKER_REPLAY_ENABLED 设为 true,再按灰度比例设置 VITE_TRACKER_REPLAY_SAMPLE_RATE
  • 若设置 VITE_TRACKER_REPLAY_START_MANUALLY=true,SDK 不会自动录制,需要业务在合适时机调用 tracker.startReplay({ force: true, reason: 'debug' }),结束时调用 tracker.stopReplay()

Session Replay 接入指南

Replay 默认建议按“注册采集器、配置开关、小流量灰度、后端确认可查”四步接入:

  1. 注册 createReplayCollector(),并在 trackerInit() 中配置 replay
  2. 默认保持 replay.enabled=false,先在开发环境开启验证。
  3. 生产环境先用低采样率灰度,例如 sampleRate=0.01,确认普通埋点成功率、页面性能、replay 上传成功率无明显异常后再扩大。
  4. 后端确认能按 replay_id 查回 replay_session_startreplay_chunkreplay_session_end 后,前端播放器即可重建回放。

常用配置建议

| 场景 | 配置建议 | 说明 | |---|---|---| | 默认关闭 | enabled: false | 不产生 replay 事件,不影响普通埋点 | | 小流量灰度 | enabled: true, sampleRate: 0.01 | 自动录制命中采样的会话 | | 指定用户排查 | enabled: true, startManually: true | 登录后按用户、路由或调试按钮调用 startReplay({ force: true }) | | 隐私优先 | maskAllInputs: true, privacyLevel: 'strict' | 默认遮罩输入内容,避免敏感信息明文进入回放数据 | | 控制单次录制 | maxDurationMs: 10 * 60 * 1000, maxReplayRawBytes: 8 * 1024 * 1024 | 到时或达到累计体积上限后自动停止并续录 | | 控制分片大小 | maxChunkBytes: 128 * 1024, flushIntervalMs: 5000 | 按大小或时间切片,减少单条日志过大 | | 观测超大事件 | maxSingleEventBytes: 1024 * 1024 | 单个 rrweb event 过大时上报诊断,不直接丢弃 |

回放数据如何使用

  • 开始录制后可通过 tracker.getReplayState().replayId 拿到当前 replay_id
  • 前端不会上传 MP4,而是上传 rrweb 事件流分片。
  • 后端只需要按 replay_id 返回完整事件流,播放器按 segment_id + chunk_seq 排序、按 idempotency_key 去重、解码 payload_data 后即可播放。
  • 后端配合要求见 docs/replay-backend-collaboration.md

埋点字段说明

通用字段

每条日志都会包含以下通用字段:

| 字段 | 描述 | 示例值 | 备注 | |------|------|--------|------| | timestamp | 格式化时间字符串,格式为 YYYY-MM-DD HH:mm:ss | 2025-09-22 10:54:22 | 用于页面展示,24 小时制,无毫秒 | | timestamp_ms | 事件产生时的 Unix 毫秒时间戳 | 1758509662127 | 供后端做时间排序/计算 | | topic | 日志主题标签,SDK 初始化时配置 | web-tracker | 用于给上报数据分类 | | event_type | 事件类型标识 | ui_click / http_request / view_start 等 | 详见下方事件类型说明 |

event_type 详细说明

| 事件类型 | 说明 | 触发场景 | |---------|------|----------| | session_start | 会话启动 | SDK 初始化时自动落一次,标记新会话启动 | | view_start / view_end | 页面浏览 | 路由切换或手动调用 setView 时记录页面进入/离开及停留时长 | | ui_click | 点击行为 | 内置采集器捕捉左键/右键/双击/长按等行为 | | ui_input | 表单输入 | 输入框/文本域/下拉框失焦时,读取当前值(默认忽略 password/hidden/file),做截断或脱敏后上报 | | http_request | 网络请求 | Axios/fetch/XHR 请求结束时落一条,包含耗时、状态码、错误信息等 | | replay_session_start | 回放会话开始 | 启用 Replay 且命中采样或手动启动后,开始录制时上报 | | replay_chunk | 回放分片 | 录制过程中按时间/大小切片上报 rrweb 事件流 | | replay_session_end | 回放会话结束 | 停止录制、超时、页面隐藏/卸载等结束时上报 | | replay_recording_error | 回放错误 | 回放初始化/录制/编码阶段出现异常时上报 | | adjust_column_width | 表格列宽调整 | 通用表格列宽调整成功时手动上报,记录表名/字段/新宽度 | | filter_change | 筛选条件变更 | 筛选项变动时手动上报,覆盖静态筛选面板和动态筛选面板 | | download_table | 表格导出 | 表格导出操作时手动上报,附带导出表名与查询条件 | | no_permission | 无权限访问 | 无权限页面被访问时手动上报,用于排查权限配置 | | custom_event | 自定义事件 | tracker.send 若未传 event_name 会退回到此默认值(保留能力,目前未实际使用) |

自动附加的上下文字段

每个事件都会自动附加以下上下文信息:

会话上下文

| 字段名 | 描述 | 示例值 | 备注 | |-------|------|--------|------| | ctx_session_id | 当前浏览器会话 ID | "6501c4-6b6c" | 基于 sessionStorage,30 分钟无操作会刷新 | | ctx_session_start | 本次会话开始的毫秒时间戳 | 1762805400000 | 与 ctx_elapsed_sec 配合判断会话持续时长 | | ctx_window_id | 当前标签页 ID | "w-98f2..." | 同一账号多标签页同时操作时用于区分 | | ctx_timestamp | 事件捕获时间(毫秒) | 1762809111574 | 若网络阻塞,可与 timestamp_ms 对照 | | ctx_elapsed_sec | 会话开始到事件触发的秒数 | 3200 | 用于分析用户停留时长 | | ctx_event_id | 埋点事件唯一 ID | "a1b2c3...-1762809111574-42" | 由会话 ID + 时间戳 + 计数器拼接 |

页面上下文

路由存在时自动携带的页面视图信息:

| 字段名 | 描述 | 示例值 | 备注 | |-------|------|--------|------| | ctx_view_id | 当前视图唯一标识(通常为路由 fullPath) | "/settings/profile?tab=basic" | 必带,包含查询参数 | | ctx_view_name | 路由 meta.title 或手动设置的页面名称 | "用户设置" | 若未配置标题则为空 | | ctx_view_path | 路由 path(不含查询参数) | "/settings/profile" | 便于做权限/菜单映射 |

用户上下文

| 字段名 | 描述 | 示例值 | 备注 | |-------|------|--------|------| | ctx_user_id | 当前登录用户 ID | "u_123" | 用户唯一标识 | | ctx_user_name | 用户姓名 | "张三" | 空字符串表示尚未拿到用户信息 | | ctx_user_role | 用户角色名称 | "admin" | 按业务侧返回 | | ctx_user_department | 用户所属部门 | "研发部门" | 后端若无该字段则为空 |

设备上下文

| 字段名 | 描述 | 示例值 | 备注 | |-------|------|--------|------| | ctx_device_user_agent | 完整 UA 字符串 | "Mozilla/5.0 (...) Chrome/128.0.0.0 Safari/537.36" | 用于兼容性诊断。UA 是 "User-Agent",浏览器在请求网页时发送给服务器的一段标识字符串,里面包含浏览器类型、版本、操作系统、设备等信息 | | ctx_device_browser_name | 浏览器名称 | "Chrome" | 基于 UA 解析 | | ctx_device_browser_version | 浏览器版本 | "128.0.0.0" | 基于 UA 解析 | | ctx_device_browser_engine | 浏览器渲染内核 | "Blink" | 区分 WebKit/Gecko 等 | | ctx_device_os_name | 操作系统名称 | "macOS" | 移动端会返回 Android、iOS 等 | | ctx_device_os_version | 操作系统版本 | "14.5.1" | 若 UA 无法解析则为 unknown | | ctx_device_device_type | 终端类型 | "desktop" | 支持 mobile/tablet/tv | | ctx_device_screen_width | 屏幕宽度(px) | 1440 | 取自 window.screen.width,无法获取时为 -1 | | ctx_device_screen_height | 屏幕高度(px) | 900 | 取自 window.screen.height,无法获取时为 -1 | | ctx_device_language | 浏览器首选语言 | "zh-CN" | 可用于语言偏好分析 |

追踪上下文

| 字段名 | 描述 | 示例值 | 备注 | |-------|------|--------|------| | trace_id | 全局追踪 ID | "f4b2c8a1-9d3e-4f12-b7a6-1e8c9d0f5a2b" | 关联前后端日志,由 setTraceId() 设置 | | action_id | 当前用户操作的唯一编号 | "d3a9c1f4-7b2c-4c5e-9a10-2f3e8c5d6b7a" | 点击触发后,在行动存活期内的事件/请求都会带上,用于把"同一波操作"串起来 | | action_type | 行动类型 | "click" / "form_submit" | 示例:click(由点击自动生成),业务手动 startAction('submit') 时会出现对应类型 | | action_meta | 行动元信息(JSON 结构) | "{\"track_id\":\"search_btn\",\"track_name\":\"查询\",\"tag\":\"BUTTON\"}" | 便于知道是哪个控件触发了这次行动 |

扩展上下文

自定义字段解析器(customFieldResolvers)可以添加额外的上下文信息,字段前缀为 ctx_extra_*

| 字段名 | 描述 | 示例值 | 备注 | |-------|------|--------|------| | ctx_extra_page_url | 事件触发时页面的完整 URL | "https://example.com/path?query=1#hash" | 包含协议、域名、路径、查询参数和 hash,方便后端还原用户的实际访问地址 | | ctx_extra_page_title | 当前页面设置的浏览器显示标题 | "用户管理 - 后台系统" | 依赖浏览器 document.title | | ctx_extra_viewport_width | 浏览器可视区域宽度 | 1440 | 记录事件发生时的视口宽度(像素) | | ctx_extra_viewport_height | 浏览器可视区域高度 | 900 | 记录事件发生时的视口高度(像素) |

自动采集事件详情

SDK 会自动采集以下用户行为事件,无需手动调用:

会话事件

| 事件类型 | 触发时机 | 主要字段 | |---------|---------|---------| | session_start | SDK 初始化完成 | 会话开始标记 |

页面浏览事件

| 事件类型 | 触发时机 | 主要字段 | |---------|---------|---------| | view_start | 进入新页面/路由 | view_id, view_name, view_path, render_duration_ms(渲染耗时) | | view_end | 离开当前页面 | view_id, duration_ms(停留时长) |

页面浏览事件字段说明:

| 字段名 | 描述 | 示例值 | 备注 | |-------|------|--------|------| | view_id | 页面唯一标识 | "/home" | 通常为路由 fullPath;includeQuery=false 时会去掉 query | | view_name | 页面名称 | "首页" | 优先取路由 meta title 或自定义 view 信息 | | view_path | 页面路径 | "/home" | 路由 path | | render_duration_ms | 页面渲染耗时(毫秒) | 128.4 | 主要出现在 view_start。首次加载优先取 FCP;不可用时降级到 DOMContentLoaded/load;SPA 路由切换取进入路由到下一帧渲染的耗时 | | duration_ms | 页面停留时长(毫秒) | 15320 | 仅 view_end 有意义,表示从进入页面到离开页面的停留时间 |

用户交互事件

| 事件类型 | 触发时机 | 主要字段 | |---------|---------|---------| | ui_click | 点击/长按/右键/双击 | click_type, track_id, track_name, element_tag, page_x, page_y | | ui_input | 输入框失焦 | field_name, field_type, value_masked, value_length, form_name |

ui_click 事件详细字段说明:

| 字段名 | 描述 | 示例值 | 备注 | |-------|------|--------|------| | click_type | 点击类型 | "left" / "right" / "double" / "longpress" | 区分不同的点击行为 | | element_tag | 触发元素的标签名 | "BUTTON" / "DIV" / "A" | 大写形式 | | element_id | 元素的 id 属性 | "submitBtn" / "" | 无 id 时为空字符串 | | element_classes | 元素 class 列表 | "el-button primary" | 以空格拼接的完整 class 字符串 | | page_x | 点击点相对页面左上角的 X 坐标 | 842 | 单位为像素 | | page_y | 点击点相对页面顶端的 Y 坐标 | 412 | 单位为像素 | | text_mask | 目标元素文本内容的脱敏结果 | "保存修改" / "操作按钮..." | 最多 120 字符,超出截断并追加 ... | | track_id | 元素上 data-track-id 的值 | "btn-save" / "" | 用于区分被点击元素,业务侧可自定义设置 | | track_name | 元素展示文案或 data-track-name | "保存修改" | 供业务阅读,优先取 data-track-name,否则取元素文本 |

ui_input 事件详细字段说明:

| 字段名 | 描述 | 示例值 | 备注 | |-------|------|--------|------| | field_name | 失焦输入控件的名称/定位信息 | "account_name" / "email" / "search_keyword" | 优先取 data-track-field,其次 name/id/placeholder | | field_type | 表单控件类型 | "text" / "select" / "textarea" / "checkbox" | 区分不同类型的输入控件 | | form_name | 输入控件所在表单的 name 或 id | "login_form" / "user_profile_form" / "" | 用于关联同一表单内的多个字段,无表单时为空 | | value_masked | 当前值的脱敏结果 | "***" / "[length:8]" / "option1,option2" | 根据 mask 策略脱敏。多选值用逗号连接 | | previous_masked | 上次上报时脱敏后的旧值 | "***" / "[length:5]" / "" | 用于对比值是否发生变化,首次上报时为空 | | value_length | 当前值长度 | 8 / 15 / 2 | 字符串长度或数组项数,帮助判断输入内容的规模 |

网络请求事件

| 事件类型 | 触发时机 | 主要字段 | |---------|---------|---------| | http_request | 发起 HTTP 请求(fetch/XHR/axios) | method, url, status, duration_ms, success, response_size |

http_request 事件详细字段说明:

| 字段名 | 描述 | 示例值 | 备注 | |-------|------|--------|------| | request_id | 每次请求埋点的唯一标识 | "e7f3b9dc-2a16-4d1d-9f2d-5de8a2e1c42b" | 用于串联同一条日志,类型为 string | | method | HTTP 请求方法,表示这条 http_request 埋点对应的接口调用类型 | "GET" / "POST" / "PUT" / "DELETE" | 值来源于实际请求,已自动转成大写;常见包括 GET、POST、PUT、DELETE、PATCH、OPTIONS,若业务里发起其他方法(如 HEAD、TRACE)也会按原样上报 | | url | 请求的完整地址 | "https://api.example.com/report?date=2025-09-10" | 包含协议、域名、路径和查询参数 | | status | HTTP 状态码 | 200 / 401 / 500 | 请求返回的 HTTP 状态码,请求失败时为 0 | | duration_ms | 请求耗时(毫秒) | 352 | 自发出请求到收到响应的总耗时,等价于 Network 面板里显示的 Time | | success | 请求是否成功 | true / false | 请求是否视为成功(状态码在 200–399 且未抛错),类型为 boolean | | request_body_digest | 对本次请求 body 的简化摘要 | "{\"name\":\"test\",\"status\"..." | 默认取原始内容或其 JSON 字符串,并在 200 个字符后截断,超出部分以 ... 结尾。帮助排查接口调用时传了哪些核心参数,同时通过截断/掩码降低敏感数据泄露风险 | | response_digest | 响应体的摘要 | "{\"result\":\"ok\",\"list\":[...]}" / "" / "[unserializable]" | 默认截取前 200 个字符,超长追加 ...,类型为 string 或 null | | response_size | 响应体大小估算 | 1250 / undefined | 单位是字节(字符数)。优先级:encoded > decoded > transfer > content-length > 估算。类型为 number 或 undefined(无法计算时) | | response_size_transfer | 传输总大小(含头,压缩后) | 1856 | 单位是字节。来源 Performance API 的 transferSize。跨域无 TAO 或缓存命中时可能为 0/undefined | | response_size_encoded | 压缩后的响应体大小 | 1720 | 单位是字节。Performance 的 encodedBodySize。压缩请求时与 decoded 不同;未压缩时它们相等 | | response_size_decoded | 解压后的响应体大小 | 4096 | 单位是字节。Performance 的 decodedBodySize。可视为原始体积;缓存命中仍可能有值 | | response_size_content_length | 响应头中的 Content-Length 值 | 1720 | 单位是字节。chunked/流式或未返回该头时为空;通常代表压缩后体积 | | response_size_source | 大小取值来源 | "performance" / "header" | 可取 "performance" 或 "header";缺失表示无法确定 | | response_compressed | 是否启用压缩 | true / false | 根据 Content-Encoding 判断。无法读头时为空 | | response_content_encoding | Content-Encoding 值 | "gzip" / "br" / "deflate" | 无法读头或未压缩则为空 | | response_cache_hit | 是否疑似缓存命中 | true / false | 启发式标记(transferSize=0 且有 body size),非严格判定 | | error_message | 请求失败时的错误信息 | "Network Error" / "timeout of 5000ms exceeded" | 仅在请求失败时出现。请求失败(网络错误、状态码非 2xx/3xx、Axios reject 等)时,会尝试提取 error.message、响应体中的报错内容等写入,帮助后端排查接口异常。请求成功时该字段为空或不存在 | | trace_id | 全局追踪 ID | "f4b2c8a1-9d3e-4f12-b7a6-1e8c9d0f5a2b" | 通过请求头 x-trace-id 发送,与 payload 中的 trace_id 对应,便于后端链路关联 | | action_id | 当前操作 ID | "d3a9c1f4-7b2c-4c5e-9a10-2f3e8c5d6b7a" | 通过请求头 x-action-id 发送,与 payload 中的 action_id 对应。同一行动内的请求头值一致 |

回放事件(Session Replay,可选)

只有在注册 createReplayCollector 且配置 replay.enabled=true 后,才会产生以下事件。

| 事件类型 | 触发时机 | 主要字段 | |---------|---------|---------| | replay_session_start | 开始录制时 | replay_id, segment_id, recording_mode | | replay_chunk | 录制中按时间/大小切片时 | replay_id, segment_id, chunk_seq, chunk_start_ms, chunk_end_ms, payload_data, is_final_chunk | | replay_session_end | 停止录制/超时/页面隐藏或离开时 | replay_id, segment_id, total_chunks, duration_ms, end_reason | | replay_recording_error | 回放初始化、录制、编码等异常时 | replay_id, stage, error_code, error_message, fatal | | replay_stats | 回放性能周期上报 / 配置变更 / 降级变更时 | replay_id, stats_reason + 窗口字段(window_*)+ 计数字段(record_* / chunk_* / playable_*)+ 比率字段(*_rate)+ 性能字段(avg_chunk_size, replay_overhead_*)及队列/降级现有字段 | | replay_degrade_change | 回放降级级别变化时 | replay_id, previous_level, next_level, reason |

replay_session_start 事件详细字段说明:

| 字段名 | 描述 | 示例值 | 备注 | |-------|------|--------|------| | replay_id | 回放会话 ID | "c34c8f54-5f0a-4bc8-8a8a-5c08d6930f11" | 同一回放会话内保持不变 | | segment_id | 回放段 ID | "seg-1" | 当前实现固定为 seg-1 | | recording_mode | 启动模式 | "sampled" / "forced" | 采样命中为 sampled,强制启动为 forced | | idempotency_key | 回放上传幂等键 | "c34c8f54-...:seg-1:replay_session_start" | 由 SDK replay 上传层补充,用于服务端去重 |

replay_chunk 事件详细字段说明:

| 字段名 | 描述 | 示例值 | 备注 | |-------|------|--------|------| | replay_id | 回放会话 ID | "c34c8f54-5f0a-4bc8-8a8a-5c08d6930f11" | 用于关联同一会话 | | segment_id | 回放段 ID | "seg-1" | 当前实现固定为 seg-1 | | chunk_id | 分片 ID | "seg-1-3" | 由 segment_idchunk_seq 拼接 | | chunk_seq | 分片序号 | 3 | 段内递增,1 开始 | | chunk_start_ms | 分片开始时间戳 | 1758509662127 | 毫秒时间戳 | | chunk_end_ms | 分片结束时间戳 | 1758509667358 | 毫秒时间戳 | | payload_data | 分片数据内容 | "[{...rrweb event...}]" | rrweb 事件数组的 JSON 字符串 | | is_final_chunk | 是否最后一个分片 | false / true | 会话结束前最后一次 flush 为 true | | event_count | 分片内事件数 | 42 | 当前分片包含的 rrweb 事件数量 | | flush_reason | 切片触发原因 | "interval" / "size" / "buffer_limit" / "pagehide" / "beforeunload" / "timeout" / "manual" | 根据触发来源写入 | | payload_bytes_raw | 原始 payload 字节数(近似) | 48321 | 基于字符串长度估算 | | payload_bytes_encoded | 编码后字节数 | 48321 | 当前编码为 json,与 raw 相同 | | payload_schema | 数据协议版本 | "rrweb-v1" | 当前固定值 | | encoding | 编码方式 | "json" | 当前固定值 | | encode_ms | 当前 chunk 编码耗时(毫秒) | 12 | 用于 replay 性能治理和降级判断 | | idempotency_key | 回放上传幂等键 | "c34c8f54-...:seg-1:3" | 对 replay_chunk 通常使用 replay_id:segment_id:chunk_seq |

replay_session_end 事件详细字段说明:

| 字段名 | 描述 | 示例值 | 备注 | |-------|------|--------|------| | replay_id | 回放会话 ID | "c34c8f54-5f0a-4bc8-8a8a-5c08d6930f11" | 与 start/chunk 对齐 | | segment_id | 回放段 ID | "seg-1" | 当前实现固定为 seg-1 | | total_chunks | 总分片数 | 6 | 本会话实际成功上报的 chunk 数量 | | duration_ms | 回放持续时长 | 31240 | 录制开始到结束的毫秒差 | | end_reason | 结束原因 | "manual_stop" / "timeout" / "size_limit" / "normal" / "error" | size_limit 表示达到 maxReplayRawBytes 后自动切段 | | idempotency_key | 回放上传幂等键 | "c34c8f54-...:seg-1:replay_session_end" | 由 SDK replay 上传层补充,用于服务端去重 |

replay_recording_error 事件详细字段说明:

| 字段名 | 描述 | 示例值 | 备注 | |-------|------|--------|------| | replay_id | 回放会话 ID | "c34c8f54-5f0a-4bc8-8a8a-5c08d6930f11" / undefined | 初始化失败时可能为空 | | stage | 错误阶段 | "init" / "record" / "encode" / "persist" / "upload" | 当前实现主要使用 init/record/encode | | error_code | 错误码 | "REPLAY_INIT_RECORDER_START_FAILED" | 便于聚合分析 | | error_message | 错误信息 | "window/document unavailable" | 会截断(最大约 500 字符) | | fatal | 是否致命 | true / false | 致命错误通常会中断当前录制 | | dropped_count | 丢弃条数 | 12 | 背压丢弃时出现(通常 stage=persist) | | dropped_bytes | 丢弃字节估算 | 32768 | 背压丢弃时出现(近似值) | | drop_reason | 丢弃原因 | "count" / "bytes" / "count_and_bytes" / "forced_drop_inflight" | 当发生过租约 inflight 强制丢弃时为 forced_drop_inflight | | drop_overflow_reason | 队列溢出原因 | "count" / "bytes" / "count_and_bytes" | 用于保留本次背压触发维度 | | drop_candidate_source_primary | 本次丢弃主来源 | "pending_oldest" / "expired_inflight" / "oldest_fallback" | 按优先级选择的首个候选来源 | | dropped_pending_count | 丢弃的 pending 条数 | 10 | 本次背压循环内统计 | | dropped_expired_inflight_count | 丢弃的过租约 inflight 条数 | 2 | 本次背压循环内统计 | | dropped_fallback_oldest_count | 兜底最旧丢弃条数 | 0 | 本次背压循环内统计 | | drop_scope | 丢弃触发位置 | "enqueue" / "persist" | 背压检查触发点 | | drop_prefer | 丢弃优先级策略 | "pending_first" | 当前实现固定为该值 | | upload_max_queue_size | 队列条数上限 | 2000 | 便于定位触发阈值 | | upload_max_queue_bytes | 队列字节上限 | 4194304 | 便于定位触发阈值 | | upload_max_persisted_bytes | 持久化字节上限 | 4194304 | 便于定位触发阈值 | | idempotency_key | 回放上传幂等键 | "c34c8f54-...:seg-1:replay_recording_error:re-..." | 由 SDK replay 上传层补充;错误事件默认带唯一后缀避免误去重 |

replay_stats 事件关键字段(趋势分析):

| 字段名 | 描述 | |-------|------| | replay_id | 当前回放会话 ID(若存在) | | stats_reason | 上报原因(如 init / interval / session_end / config_update / degrade_change) | | window_start_ms / window_end_ms / window_duration_ms | 统计窗口起止与窗口时长(毫秒) | | record_start_count | 窗口内 replay_session_start 数量 | | record_success_count | 窗口内“至少产出 1 个 chunk”的会话数量(按 replay_id) | | chunk_produced_count | 窗口内产出的 chunk 数 | | chunk_upload_attempt_count / chunk_upload_success_count | 窗口内 chunk 上传尝试数 / 成功数 | | chunk_drop_count | 窗口内被丢弃的 chunk 数(背压丢弃 + 重试耗尽) | | playable_session_count | 窗口内满足 start + end + final_chunk 的可回放会话数 | | chunk_upload_success_rate | chunk_upload_success_count / chunk_upload_attempt_count | | chunk_drop_rate | chunk_drop_count / chunk_produced_count | | replay_playable_rate | playable_session_count / record_start_count | | chunk_encoded_bytes_sum / avg_chunk_size | 窗口内 chunk 编码字节总量 / 平均 chunk 体积 | | encode_time_sum_ms / flush_time_sum_ms | 窗口内编码总耗时 / flush 总耗时 | | replay_overhead_cpu | (encode_time_sum_ms + flush_time_sum_ms) / window_duration_ms | | replay_overhead_memory | usedJSHeapSize_end - usedJSHeapSize_start(浏览器不支持时为空) | | replay_queue_bytes | 当前 replay 队列字节估算 | | replay_persisted_bytes | 最近一次持久化字节估算 | | replay_retry_backlog | 当前重试积压条数 | | replay_dropped_count | 当前运行周期累计丢弃条数 | | replay_last_encode_ms / replay_max_encode_ms | 编码耗时指标 | | replay_last_flush_ms / replay_max_flush_ms | flush 耗时指标 | | replay_longtask_count / replay_longtask_window_count | long task 指标 | | replay_degrade_level / replay_degrade_reason | 当前降级级别与原因 |

replay_degrade_change 事件关键字段:

| 字段名 | 描述 | |-------|------| | replay_id | 当前回放会话 ID(若存在) | | previous_level | 变更前降级级别 | | next_level | 变更后降级级别 | | reason | 触发变更原因 |

调试播放器可观测事件(Replay Debug Viewer,可选)

以下事件由 tools/replay-debug-vieweronTrack 回调里产出,便于联调阶段做看板聚合;它们不属于内置 collector 自动采集事件。

replay_player_load

| 字段名 | 描述 | |---|---| | replay_id | 当前加载的回放会话 ID | | load_ms | 加载 + 组装 + 解码 + 渲染耗时(毫秒) | | chunk_count | 组装后的 chunk 总数 | | decoded_count | 成功解码的 chunk 数 | | decode_fail_count | 解码失败 chunk 数 | | playability | 可播放等级:playable / partial_playable / unplayable | | playable | 兼容字段,与 playability 值相同 | | from / to | 加载时使用的时间范围参数(可选) |

replay_player_error

| 字段名 | 描述 | |---|---| | replay_id | 回放会话 ID(若可获取) | | stage | 错误阶段:load / assemble / decode / render | | error_code | 稳定错误码(用于聚合) | | error_message | 错误描述 |

replay_player_error.error_code(当前实现)

| 错误码 | 含义 | |---|---| | REPLAY_FETCH_FAILED | 回放数据拉取失败(DataSource / API) | | REPLAY_FETCH_LIMIT_EXCEEDED | 回放加载在 fetch 阶段触发硬上限(事件数或字节)并中止 | | REPLAY_ASSEMBLE_FAILED | 回放事件组装失败 | | REPLAY_DECODE_FAILED | 回放解码流程抛错 | | REPLAY_PLAYER_RENDER_FAILED | Replayer 渲染失败 | | REPLAY_PLAYER_LOAD_FAILED | load 阶段兜底错误码(非结构化错误) | | REPLAY_PLAYER_ASSEMBLE_FAILED | assemble 阶段兜底错误码(非结构化错误) | | REPLAY_PLAYER_DECODE_FAILED | decode 阶段兜底错误码(非结构化错误) | | REPLAY_PLAYER_NO_EVENTS | 返回无事件,无法播放 | | REPLAY_PLAYER_DECODE_EMPTY | chunk 存在但解码结果为空 | | REPLAY_PLAYER_MISSING_START | 缺少 replay_session_start | | REPLAY_PLAYER_MISSING_END | 缺少 replay_session_end | | REPLAY_PLAYER_DECODE_PARTIAL | 解码不完整(部分可播放) | | REPLAY_PLAYER_CHUNK_COUNT_LIMIT_EXCEEDED | 超过单次加载允许的最大 chunk 数,已中止解码 | | REPLAY_PLAYER_TOTAL_BYTES_LIMIT_EXCEEDED | 超过单次加载允许的最大总字节,已中止后续解码 | | REPLAY_PLAYER_DECODE_FAILURES_TRUNCATED | 解码失败过多,播放器错误事件只上报前 N 条 |

另外,当发生 chunk 级解码失败时,replay_player_error.error_code 可能直接使用 decode/* 系列编码(如 decode/chunk_too_largedecode/json_parse_failed),并附带 segment_id/chunk_seq/chunk_id/idempotency_key

Replay 独立上传队列行为(当前实现)

  • Replay 事件通过 trackReplay(event) 写入独立队列,不进入普通 state.queue
  • Replay 队列使用独立定时器与独立 flush,不占用普通埋点的 flush 锁。
  • 页面 beforeunload / pagehide / visibilitychange=hidden 时,Replay 队列走独立 lifecycle flush(优先 sendBeacon);同一监听内普通队列也会独立执行自身 beacon 兜底,两条通道互不阻塞。
  • 调用 flush() / forceFlush() 时,会并行处理普通队列与 Replay 队列。
  • replay.enabled=false 时:不会产生 replay 事件,trackReplay 会直接跳过;初始化或运行时关闭 replay 时会清理 replay 队列。
  • Replay 队列对 replay_chunk / replay_session_start / replay_session_end 启用幂等键去重;重复幂等键不会重复入队。
  • 现有普通埋点(如 ui_click / ui_input / http_request / view_*)字段契约保持不变,Replay 走独立队列,不会改写这些事件结构。
  • shutdown() 会先尝试一次 replay quick beacon(reason=shutdownquick=true),随后执行 replay 队列独立收尾:清理 replay timer、将剩余 inflight 回收为 pending 并持久化;下次 init() 可继续补传。
  • 如需彻底丢弃 replay 未发数据,可调用 shutdown({ dropReplayQueue: true })

Replay 上传配置项(独立于录制分片配置)

以下配置位于 TrackerConfig.replay,默认值已内置,不配置也可运行:

| 配置项 | 类型 | 默认值 | 说明 | |---|---|---|---| | uploadBatchSize | number | 5 | 每次 replay flush 最多发送的事件数(按 replay 事件条数切片) | | uploadFlushIntervalMs | number | 2000 | replay 队列自动 flush 间隔(毫秒) | | uploadMaxRetries | number | 5 | 单条 replay 事件最大重试次数 | | uploadRetryBaseDelayMs | number | 1500 | replay 重试基础退避时长(毫秒),指数退避 | | uploadStorageKey | string | "tracker-replay-queue" | replay 队列本地持久化 key | | uploadMaxQueueSize | number | 2000 | replay 队列最大长度,超出会丢弃最早数据 | | uploadMaxQueueBytes | number | 4194304 | replay 队列估算字节上限(默认 4MB),超限触发背压丢弃 | | uploadMaxPersistedBytes | number | 4194304 | replay 队列持久化字节上限(默认 4MB);落盘前按 min(uploadMaxQueueBytes, uploadMaxPersistedBytes) 执行背压裁剪 | | uploadDropPolicy | string | "drop_oldest" | 背压丢弃策略;当前仅支持丢弃最旧数据 | | uploadDropPrefer | string | "pending_first" | 丢弃优先级;当前顺序为“最旧 pending -> 过租约 inflight -> 全队列最旧兜底” | | uploadLifecycleBeaconBatchSize | number | 3 | 生命周期 beacon flush 的最大条数阈值(会再受 uploadBatchSize 约束) | | uploadLifecycleBeaconMaxBytes | number | 49152 | 生命周期 beacon flush 的最大字节阈值(默认 48KB,内部上限 60KB) | | degradeEnabled | boolean | false | 是否启用 replay 自动降级状态机(L0-L3);仅启用后才会挂载 long task 性能观测 | | degradeCooldownMs | number | 30000 | 降级恢复冷却窗口(毫秒) | | maxMainThreadTaskMs | number | 60 | long task 计数阈值(毫秒) | | maxEncodeMs | number | 50 | 单个 replay chunk 编码耗时预算(毫秒) | | maxReplayRawBytes | number | 8388608 | 单个 replay 原始 rrweb payload 累计字节上限,超限以 end_reason=size_limit 结束并自动续录 | | maxSingleEventBytes | number | 1048576 | 单个 rrweb event 体积观测阈值,超限上报 REPLAY_RECORD_OVERSIZED_EVENT,但不丢弃事件 | | maxRecordBufferBytes | number | 524288 | replay collector 内存 buffer 上限,超限触发 flush_reason=buffer_limit | | maxLongTaskPerMinute | number | 30 | 60 秒窗口内 long task 阈值,超限参与降级判定 | | maxRetryBacklog | number | 200 | replay 重试积压阈值,超限参与降级判定 | | statsIntervalMs | number | 60000 | replay_stats 趋势事件上报周期(毫秒) |

Replay 失败与重试语义(当前实现)

  • 可重试状态码:04084295xx;若无法提取状态码,默认按可重试处理。
  • 不可重试或重试超限后,事件会被丢弃,并上报 replay_recording_errorstage=upload)。
  • 重试策略为指数退避:uploadRetryBaseDelayMs * 2^nn 为已重试次数)。
  • 鉴权失败(401/403,或错误信息包含 signature/token/sts/unauthorized)会触发凭证刷新;刷新成功后 replay 队列立即重试。
  • 队列背压(超过 uploadMaxQueueSizeuploadMaxQueueBytes,或持久化阶段超过 uploadMaxPersistedBytes)会按 uploadDropPolicy=drop_oldest 执行丢弃;当前 uploadDropPrefer=pending_first 时按“最旧 pending -> 过租约 inflight -> 全队列最旧兜底”顺序选择候选,并上报 replay_recording_errorstage=persist, error_code=REPLAY_PERSIST_QUEUE_OVERFLOW, drop_reason/dropped_count 等扩展字段)。

Replay 状态可观测(getStateSnapshot()

用于调试和看板聚合的 Replay 相关快照字段:

| 字段名 | 类型 | 说明 | |---|---|---| | replayQueueLength | number | 当前 replay 队列长度(条数) | | replayQueueBytes | number | 当前 replay 队列估算字节数 | | replayPersistedBytes | number | undefined | 当前 replay 队列最近一次持久化字节估算(存储未初始化时为空) | | replayIsFlushing | boolean | replay 队列是否正在 flush | | replayNextRetryAt | number | undefined | 最近一次待重试时间戳(毫秒) | | replayNextRetryInMs | number | undefined | 距离下一次重试的毫秒数 | | replayDroppedCount | number | 当前运行周期内累计丢弃条数 | | replayLastDropReason | string | undefined | 最近一次丢弃原因(当前值:count/bytes/count_and_bytes) | | replayDegradeLevel | number | 当前 replay 降级级别(0~3) | | replayDegradeReason | string | undefined | 最近一次降级级别变化原因 | | replayDegradeUpdatedAt | number | undefined | 最近一次降级级别变化时间戳(毫秒) | | replayConsecutiveFailures | number | replay 传输连续失败次数 | | replayEncodeOverBudgetStreak | number | replay chunk 编码超预算连续次数 | | replayRetryBacklog | number | replay 队列中已进入重试状态的积压条数 | | replayLastEncodeMs | number | undefined | 最近一次 replay chunk 编码耗时(毫秒) | | replayMaxEncodeMs | number | undefined | 当前运行周期内最大 replay chunk 编码耗时(毫秒) | | replayLastFlushMs | number | undefined | 最近一次 replay flush 耗时(毫秒) | | replayMaxFlushMs | number | undefined | 当前运行周期内最大 replay flush 耗时(毫秒) | | replayLongTaskCount | number | 当前运行周期累计 long task 数(按阈值过滤) | | replayLongTaskWindowCount | number | 最近 60 秒 long task 数 | | replayStatsWindowStartMs / replayStatsWindowDurationMs | number | undefined | replay_stats 当前聚合窗口起点与窗口时长 | | replayStatsRecordStartCount / replayStatsRecordSuccessCount | number | replay_stats 窗口内会话开始数 / 成功产出 chunk 的会话数 | | replayStatsChunkProducedCount / replayStatsChunkDropCount | number | replay_stats 窗口内产出 chunk 数 / 丢弃 chunk 数 | | replayStatsChunkUploadAttemptCount / replayStatsChunkUploadSuccessCount | number | replay_stats 窗口内 chunk 上传尝试数 / 成功数 | | replayStatsChunkUploadSuccessRate / replayStatsChunkDropRate | number | replay_stats 窗口内 chunk 上传成功率 / 丢弃率 | | replayStatsPlayableSessionCount / replayStatsPlayableRate | number | replay_stats 窗口内可回放会话数 / 可回放率 | | replayStatsChunkEncodedBytesSum / replayStatsAvgChunkSize | number | replay_stats 窗口内 chunk 编码总字节 / 平均 chunk 体积 | | replayStatsEncodeTimeSumMs / replayStatsFlushTimeSumMs | number | replay_stats 窗口内编码总耗时 / flush 总耗时 | | replayStatsOverheadCpu / replayStatsOverheadMemory | number / number|undefined | replay_stats 计算出的 CPU 代理开销与内存增量 |

手动埋点

除了自动采集,你也可以手动上报自定义事件:

// 简单事件
tracker.track('button_click', {
  button_name: 'signup',
  button_text: '立即注册'
});

// 复杂业务事件
tracker.track('order_submit', {
  order_id: 'ORD_123456',
  amount: 99.99,
  currency: 'CNY',
  item_count: 3
});

表格列宽/拖拽埋点示例(通用写法)

SDK 不强制表格埋点,以下为通用示例,字段请替换为你的业务含义:

VXE Table 列宽拖拽(vxe-table

<!-- template -->
<vxe-table @column-resizable-change="handleResize" ...>
  ...
</vxe-table>
import tracker from '@/utils/logger'; // 你的业务封装

const handleResize = ({ column }: { column: any }) => {
  tracker.track('adjust_column_width', {
    table: 'example_table', // 自定义表名
    field: column?.property || column?.field || '',
    field_label: column?.title || '',
    width: column?.resizeWidth,
  });
};

VXE Table 行拖拽(Sortable)

import Sortable from 'sortablejs';
import tracker from '@/utils/logger';

const rowDrop = () => {
  const $table = tableRef.value;
  Sortable.create($table.$el.querySelector('.vxe-table--body-wrapper tbody'), {
    handle: '.drag-btn',
    onEnd({ oldIndex, newIndex }) {
      tracker.track('table_row_reorder', {
        table: 'example_table',
        from_index: oldIndex,
        to_index: newIndex,
        row_id: rows[oldIndex]?.id,
      });
    },
  });
};

Element Plus el-table 列宽拖拽

<!-- template -->
<el-table :data="rows" border @header-dragend="handleHeaderDragend">
  ...
</el-table>
import tracker from '@/utils/logger';

const handleHeaderDragend = (newWidth: number, oldWidth: number, column: any) => {
  tracker.track('adjust_column_width', {
    table: 'example_table',
    field: column?.property || column?.prop || '',
    field_label: column?.label || '',
    width: newWidth,
    old_width: oldWidth,
  });
};

以上仅为示例,请用你的表名/业务字段替换占位符。

完整事件示例

{
  "event_type": "ui_click",
  "timestamp": "2024-05-20 10:24:11",
  "timestamp_ms": "1716181451000",
  "click_type": "left",
  "track_id": "ad-card-cta",
  "track_name": "立即报名",
  "element_tag": "BUTTON",
  "element_id": "signup-btn",
  "element_classes": "btn btn-primary",
  "text_mask": "立即报名",
  "page_x": 812,
  "page_y": 364,
  "ctx_session_id": "6501c4-6b6c",
  "ctx_session_start": "1716178251000",
  "ctx_window_id": "a7f3e9-2c1d",
  "ctx_timestamp": "1716181451000",
  "ctx_elapsed_sec": "3200",
  "ctx_event_id": "6501c4-6b6c-1716181451000-42",
  "ctx_view_id": "/campaign/list",
  "ctx_view_name": "Campaign List",
  "ctx_view_path": "/campaign/list",
  "ctx_user_id": "u_123",
  "ctx_user_name": "张三",
  "ctx_user_role": "admin",
  "ctx_device_browser_name": "Chrome",
  "ctx_device_browser_version": "124.0.6367.118",
  "ctx_device_os_name": "macOS",
  "action_id": "f4b2c8a1-9d3e-4f12-b7a6-1e8c9d0f5a2b",
  "action_type": "click"
}

💡 提示: 完整的字段定义和数据契约请参考 operation-log-reference.mdx

关键配置说明

  • endpoint/project/logstore/fetchCredential:必填,由使用方提供,勿硬编码敏感值到 SDK 内。
  • router:用于路由级视图埋点;非路由视图可调用 setView
  • axiosInstances / instrumentFetch / instrumentXHR:启用请求埋点并传入实例。
  • ignoredUrls:可过滤静态资源或不需埋点的接口。
  • context.getUserInfo/refreshContextUser:提供当前用户信息,可加掩码处理。

发布提示

  • 发布前检查 .npmignore/files 确保只带源码与类型;无敏感配置。
  • 可在 CI 中加入 git-secrets/敏感信息扫描。
  • 根据需要补充构建脚本(rollup/tsc)与类型打包。