前端埋点体系 事件模型、上报与去重
FeatherPen
8 min read
前端埋点体系 事件模型、上报与去重
埋点体系的难点不在"发一个请求",而在三件事能自洽:事件怎么建模、上报怎么不丢不堵、重复数据怎么识别。这三块没设计好,下游数据分析会持续受到污染,越积越难修。很多团队前期只图"能发就行",等到数据对不上、指标翻倍、日报忽高忽低,才发现地基是歪的,返工成本远高于一开始就设计好。
事件模型:统一 schema
所有事件共用一份结构,下游才能稳定解析。最基础的字段应包含:事件名、时间戳、匿名用户标识、页面路径、事件唯一 ID 和自定义属性:
{
event: 'button_click',
ts: 1700000000000,
uid: 'a1b2c3',
page: '/checkout',
eid: 'uuid-xxxx',
props: { btn: 'pay', amount: 99 }
}
事件名用"对象_动作"的命名约定(如 cart_add、video_play),避免团队各写各的导致同义不同名。属性键也要收敛,零散的新键会拖垮数据仓库的表结构,最好有字段字典约束。经验上,公共字段(uid、page、ts)由采集层自动补全,业务只填 event 和 props,既减少出错也保证一致性。
采集层:自动 + 手动
自动采集覆盖通用行为,减少漏埋:页面曝光用 IntersectionObserver 判定、路由切换监听、全局点击事件委托捕获带标记的元素。手动埋点负责业务语义,例如"加入购物车"这种自动采集拿不到含义的动作:
function track(event, props = {}) {
const payload = {
event,
ts: Date.now(),
uid: getUid(),
page: location.pathname,
eid: uuid(),
props,
};
buffer.push(payload);
flushIfNeeded();
}
自动和手动都汇入同一个 track,保证 schema 一致,也方便统一加 uid、加会话 ID。边界:自动采集的点击如果过于宽泛,会产生海量低价值事件,反而淹没真正重要的信号,所以自动采集最好限定在带特定属性的元素上,而非监听全部点击。
上报策略:不丢也不堵
逐条实时上报请求太多,批量攒够一定数量或间隔再发,能显著降低请求数。关键难点在"页面关闭时缓冲区还有数据"——同步 XHR 会阻塞卸载、且常被浏览器丢弃,应当用 navigator.sendBeacon:
function flush() {
if (!buffer.length) return;
const data = JSON.stringify(buffer);
buffer = [];
if (navigator.sendBeacon) {
navigator.sendBeacon('/log', data);
} else {
fetch('/log', { method: 'POST', body: data, keepalive: true });
}
}
window.addEventListener('pagehide', flush);
sendBeacon 在卸载时仍能异步送达,不阻塞用户。边界:单条 beacon 体积受浏览器限制(经验上约数十 KB 量级),批量时要控制单包大小,超限拆分,否则整包被丢弃反而丢数据。弱网或后台标签页会被浏览器节流,定时上报要兼容"攒很久才发"的情况。
去重:事件 ID 与幂等
前端网络抖动常触发重试,重试若不做约束,下游会收到重复事件,指标被放大。两层去重:
- 前端给每个事件生成 eid,重试时携带同一 eid,后端按 eid 做幂等落库,重复直接丢弃。
- 后端还要基于"uid + event + ts + 关键属性"做时间窗去重,兜住前端 eid 丢失或重复生成的极端情况。
上报本身必须幂等:重试只重发同一份 payload,绝不能因重试而"多记一次业务动作"。否则越重试数据越膨胀,且这种膨胀在指标上看是"增长",极具迷惑性。经验上,去重逻辑宁可前端后端各做一层,也不要只靠一端,单点失效时数据就脏了。
采样、首屏与隐私
高频事件(如滚动、鼠标移动)全量上报既贵又无用,按固定比例采样即可,下游乘以倒数还原量级。首屏类指标(FCP、LCP)要用 PerformanceObserver 在真实用户端采集,比实验室跑分更接近分布,也更能反映不同地区、机型的差异。隐私是红线:uid 用匿名随机标识,不上报姓名、手机号、精确地址等可识别个人信息;涉及输入的属性要做脱敏,例如只记"是否非空"而非原文。合规要求下还要提供关闭采集的开关,并尊重系统级的隐私偏好设置,否则在部分地区会直接违反法规。
几个边界
- 弱网或后台页会被浏览器节流,定时上报要兼容"攒很久才发"的情况,单包别超限制。
- SPA 路由切换不会刷新页面,曝光类埋点要手动在路由钩子里触发,不能依赖
load 事件,否则只有首屏能采到。
- 调试阶段要能一键开关详细日志,线上把采样和上报合并到同一个开关,避免忘了关导致调试数据污染生产。
- 埋点代码本身出错不应影响业务,
track 内部要吞掉异常,绝不能因为埋点挂了导致页面白屏。
- 字段变更要向后兼容:新增可选字段没问题,删除或改类型要通知下游,否则历史数据和新数据对不上。
端到端的可信校验
埋点上线后要有校验机制,否则脏数据会一直流到报表。一个简单做法是"影子比对":在关键路径同时走新老两套埋点(或采样比对),观察数值是否一致,发现偏差立刻告警。另一层是数据侧的异常检测,比如某事件量突然翻倍,优先怀疑去重失效而非真实增长。还可以定期回溯:用服务端日志交叉验证前端埋点的关键转化漏斗,两者长期偏差过大说明前端埋点有漏采或重复。埋点质量的衡量标准不是"埋了多少",而是"下游敢不敢拿它做决策"——可信度比覆盖率更重要。
组织层面的约定
埋点要可持续,离不开团队约定:事件命名规范、属性字典、新增事件的评审流程,以及一份"哪些指标已被废弃"的清单。没有字典,不同人会给同一含义起不同名字,数据分析时就要写一堆 OR 条件,脆弱且易错。建议把埋点定义写成可机器校验的 schema,提交时自动检查字段合法性,从源头拦住脏结构。这样埋点体系才能随业务增长而维持秩序,而不是越埋越乱。
另外,埋点的生命周期管理也不能忽视:废弃的事件要及时从字典移除并通知下游报表下线,否则没人敢删的历史字段会越积越多,新同学也不敢动。把埋点当成代码一样做版本与下线管理,体系才不会随人员流动而腐化。文档与字典要保持同步,否则过半年没人说得清某个字段的真实含义,数据分析就只能靠猜。
经验上,埋点体系先定 schema 和去重,再补上报与采样,再往上才是花式分析。地基不稳,上面的看板再漂亮也是建立在脏数据上。一个能信的埋点体系,比十个好看的图表更有价值。
1 view
0 likes
0 comments