跳转到内容

前端埋点规范

埋点事件命名、上报规范


  • Requirement:事件名使用小写字母 + 下划线(snake_case),格式为 模块_动作_对象。
  • Requirement:事件名必须见名知意,禁止使用 event1、click 等无意义名称。
模块_动作_对象
│ │ │
│ │ └── 对象:被操作的元素,如 button、modal、list
│ └─────── 动作:用户行为,如 click、view、submit、scroll
└──────────── 模块:业务模块,如 user、order、login
事件名说明
user_click_login_button用户点击登录按钮
user_view_login_page用户查看登录页
order_submit_order_form用户提交订单表单
order_view_order_list用户查看订单列表
product_click_add_cart用户点击加入购物车
  • Standard:动作词统一使用以下词汇,禁止混用同义词:
动作英文说明
点击click用户点击元素
查看view页面或元素曝光
提交submit提交表单
滚动scroll滚动到指定位置
搜索search执行搜索
筛选filter设置筛选条件
切换switch切换 Tab/状态
悬停hover鼠标悬停
反模式问题建议
事件名用驼峰 userClickLogin与后端约定不一致使用 snake_case
事件名 click1、event_xxx无法理解含义使用 模块_动作_对象
动作词混用 tap/press/click数据分析困难统一用 click

  • Requirement:埋点上报必须统一封装,禁止在业务代码中直接调用第三方 SDK。
  • Standard:封装 track() 方法,统一处理公共参数、批量上报、错误兜底。
// src/utils/track.ts
interface TrackEvent {
  event: string;
  properties?: Record<string, any>;
}
 
const queue: TrackEvent[] = [];
const FLUSH_INTERVAL = 5000; // 5 秒批量上报
const MAX_QUEUE_SIZE = 20;   // 满 20 条立即上报
 
function flush() {
  if (queue.length === 0) return;
  const events = queue.splice(0, queue.length);
  navigator.sendBeacon('/api/track', JSON.stringify({ events }));
}
 
// 定时批量上报
setInterval(flush, FLUSH_INTERVAL);
 
export function track(event: string, properties: Record<string, any> = {}) {
  const commonProperties = {
    user_id: getUserStore().id,
    session_id: getSessionId(),
    timestamp: Date.now(),
    page_url: window.location.href,
    ...getCommonProperties(),
  };
 
  queue.push({
    event,
    properties: { ...commonProperties, ...properties },
  });
 
  // 队列满立即上报
  if (queue.length >= MAX_QUEUE_SIZE) {
    flush();
  }
}
  • Standard:根据事件类型选择上报时机:
事件类型上报时机
页面浏览(view)页面加载完成后
点击(click)点击事件触发时
表单提交(submit)提交成功后(失败不上报)
曝光(view)元素进入视口时(用 IntersectionObserver)
离开页面visibilitychange 或 pagehide 时 flush
  • Standard:元素曝光使用 IntersectionObserver 判断,避免用 scroll 事件监听。
  • Standard:同一元素只上报一次曝光,用 Set 记录已曝光的元素。
import { useEffect, useRef } from 'react';
import { track } from '@/utils/track';
 
function useExposureTrack(event: string, properties?: Record<string, any>) {
  const ref = useRef<HTMLDivElement>(null);
  const tracked = useRef(false);
 
  useEffect(() => {
    if (!ref.current || tracked.current) return;
 
    const observer = new IntersectionObserver(
      (entries) => {
        entries.forEach((entry) => {
          if (entry.isIntersecting && !tracked.current) {
            track(event, properties);
            tracked.current = true;
            observer.disconnect();
          }
        });
      },
      { threshold: 0.5 }
    );
 
    observer.observe(ref.current);
    return () => observer.disconnect();
  }, [event, properties]);
 
  return ref;
}
反模式问题建议
业务代码直接调用 SDK耦合、难以更换统一封装 track()
每条事件立即上报请求过多批量上报
曝光用 scroll 事件判断性能差用 IntersectionObserver
上报失败无重试数据丢失失败重试或存入队列

  • Requirement:所有事件必须携带以下公共参数,由封装层自动注入,业务代码无需传入。
参数类型说明
user_idstring用户 ID,未登录为匿名 ID
session_idstring会话 ID
timestampnumber事件时间戳(毫秒)
page_urlstring当前页面 URL
referrerstring来源页面
platformstring平台:web / h5 / mini
app_versionstring应用版本
  • Standard:业务参数使用 snake_case,类型为基本类型(string/number/boolean),禁止传对象或数组。
  • Standard:参数值必须是枚举或有限值,禁止传自由文本(如用户输入的搜索词需脱敏后上报)。
// 正确
track('order_submit_order_form', {
  order_type: 'normal',       // 枚举值
  total_amount: 199.00,       // 数字
  item_count: 3,              // 数字
  pay_method: 'alipay',       // 枚举值
});
 
// 错误
track('order_submit_order_form', {
  order: { ... },             // 传了对象
  items: [ ... ],             // 传了数组
  remark: '请尽快发货',        // 自由文本
});
  • Requirement:禁止上报手机号、身份证、密码、银行卡号等敏感信息,必须脱敏或不上报。
// 正确:脱敏
track('user_view_profile', {
  phone: maskPhone(user.phone), // 138****1234
});
 
// 错误:明文上报
track('user_view_profile', {
  phone: user.phone, // 13812341234
});
反模式问题建议
参数传对象/数组后端难以解析扁平化基本类型
上报明文手机号隐私合规风险脱敏处理
参数名用驼峰与后端约定不一致统一 snake_case
参数值为自由文本难以统计枚举化或脱敏

  • Requirement:开发环境在控制台输出埋点日志,格式为 [track] 事件名 {参数},便于核对。
  • Standard:开发环境提供可视化埋点调试面板,展示最近上报的事件列表。
export function track(event: string, properties: Record<string, any> = {}) {
  if (import.meta.env.DEV) {
    console.log(`[track] ${event}`, properties);
  }
  // ... 上报逻辑
}
  • Standard:提供全局开关控制埋点上报,开发环境可关闭上报仅打印日志。
  • Caution:生产环境必须开启上报,且日志打印必须关闭。
const TRACK_ENABLED = import.meta.env.PROD ? true : false;
 
export function track(event: string, properties: Record<string, any> = {}) {
  if (import.meta.env.DEV) {
    console.log(`[track] ${event}`, properties);
    if (!TRACK_ENABLED) return;
  }
  // 上报逻辑...
}
  • Suggestion:开发环境在控制台校验事件名和参数是否符合规范,不符合时给出警告。
const VALID_EVENTS = new Set([
  'user_click_login_button',
  'user_view_login_page',
  // ...
]);
 
function validate(event: string) {
  if (!VALID_EVENTS.has(event)) {
    console.warn(`[track] 未注册的事件: ${event}`);
  }
}
反模式问题建议
开发环境无日志难以核对埋点控制台输出日志
生产环境打印日志信息泄露、性能损耗生产关闭日志
无埋点开关测试环境产生脏数据提供开关控制

  • Requirement:所有埋点事件必须在事件字典(track-events.ts)中注册,包含事件名、说明、参数定义。
  • Standard:事件字典作为埋点的单一数据源,开发时从中引入,避免硬编码事件名。
// src/constants/track-events.ts
export const TrackEvents = {
  USER_CLICK_LOGIN_BUTTON: 'user_click_login_button',
  USER_VIEW_LOGIN_PAGE: 'user_view_login_page',
  ORDER_SUBMIT_ORDER_FORM: 'order_submit_order_form',
} as const;
 
export interface TrackEventParams {
  [TrackEvents.USER_CLICK_LOGIN_BUTTON]: {
    login_method: 'password' | 'sms' | 'oauth';
  };
  [TrackEvents.ORDER_SUBMIT_ORDER_FORM]: {
    order_type: 'normal' | 'presale';
    total_amount: number;
    pay_method: 'alipay' | 'wechat';
  };
}
  • Standard:利用 TypeScript 类型系统,在编译时校验事件名和参数是否匹配。
export function track<K extends keyof TrackEventParams>(
  event: K,
  properties: TrackEventParams[K]
) {
  // ...
}
 
// 正确
track(TrackEvents.USER_CLICK_LOGIN_BUTTON, { login_method: 'password' });
 
// 错误:参数类型不匹配
track(TrackEvents.USER_CLICK_LOGIN_BUTTON, { login_method: 'face' });
  • Standard:后端对上报的事件做参数校验,不符合规范的事件丢弃并记录告警。
  • Caution:前端上报失败时静默处理,不影响主流程,避免埋点故障导致业务不可用。
反模式问题建议
事件名硬编码散落各处难以维护、易写错统一在事件字典注册
无类型校验参数遗漏或传错用 TypeScript 约束
埋点报错阻断业务影响用户体验埋点失败静默处理