通过Proxy拦截setter实现零侵入日志采集,精准代理目标对象、识别有效变更、附加用户/时间/页面等上下文,并异步批量上报。

通过 Setter 属性实现用户操作日志的零侵入式自动化搜集,核心在于**利用属性赋值的拦截机制,在不修改业务代码逻辑的前提下,自动捕获关键字段变更行为**。关键不是重写所有 setter,而是精准代理目标对象、识别有效变更、附加上下文(如操作人、时间、页面路径),并异步上报。
一、用 Proxy 封装目标对象,替代原始 setter
原生 setter 无法批量拦截,也不支持动态添加;而 Proxy 可以统一拦截 set 操作,且对调用方完全透明——业务层仍用 obj.field = value,无需改造。
- 只代理需要记录的模型类(如表单数据对象、配置项对象),避免全局污染
- 在 handler.set 中判断:新旧值是否真正变化(注意深比较基础类型,浅比较引用类型)
- 提取变更字段名、旧值、新值,并组合成日志条目
- 示例:
const loggedForm = new Proxy(form, { set(target, key, value) { logChange(key, target[key], value); return Reflect.set(target, key, value); } });
二、注入上下文信息,让日志具备可追溯性
单纯记录“谁改了什么”不够,需绑定当前用户、操作场景和触发源。这些信息不应硬编码在 setter 里,而应通过闭包或模块级状态注入。
- 在创建代理时传入 context:{ userId, pageId, actionSource: 'form-input' }
- 利用
Performance.now()或Date.now()记录毫秒级时间戳 - 若在 Vue/React 等框架中,可结合响应式系统钩子(如 Vue 的 watchEffect)补全组件路径或事件类型
- 避免在 set 拦截中同步调用复杂逻辑(如发请求),一律交由队列+防抖处理
三、区分变更类型,过滤无效日志
用户快速输入、初始化赋值、重复点击等会产生大量噪声。需在拦截阶段做轻量过滤,提升日志有效性。
- 忽略初始化时的首次赋值(可通过标记
isInitializing或比对 undefined 判断) - 跳过空字符串 → 空字符串、0 → 0、null → undefined 等无意义变更
- 对高频字段(如搜索框内容)启用节流,仅记录最终稳定值
- 支持白名单字段配置:只监控 name、status、priority 等关键业务字段,非关键字段不触发日志
四、异步批报 + 本地缓存,保障日志不丢失
前端网络不可靠,直接 fetch 上报易丢日志。应采用“内存暂存 + 定时刷盘 + 页面卸载兜底”策略。
- 变更日志先推入内存队列,每 3 秒或积满 10 条批量上报
- 使用
localStorage持久化未上报日志(注意大小限制,建议压缩字段名) - 监听
beforeunload和visibilitychange,尝试同步发送剩余日志 - 服务端接收后做幂等校验(如用 clientTimestamp + sessionId 去重)
不复杂但容易忽略的是:日志结构要兼顾可查性与扩展性。建议固定字段为 { eventId, userId, pageId, field, oldValue, newValue, timestamp, source },其余上下文作为 ext 字段 JSON 序列化存储。这样既保证查询效率,又保留灵活埋点空间。


















