简历回答逐字稿:监控 SDK 架构与插件化设计

关联:简历追问:监控 SDK 架构与插件化设计15分钟:前端埋点与监控 SDK

30 秒开场

我做这个监控 SDK 时,重点不是做一个大而全的数据平台,而是做一套业务前端愿意接、接了以后不容易出问题的采集基础设施。

所以架构上我拆成 core 包和适配包。core 负责上下文、队列、上报、插件生命周期、错误隔离;React、Vue、Vanilla 适配包只负责框架接入。业务事件尽量声明式采集,比如通过 data-track-event 表达业务语义,SDK 负责事件发现、上下文补齐、队列和降级上报。

如果面试官问:为什么不直接提供 track()

我会说 track() 一定需要,但不能只靠它。

纯手动 track() 很灵活,但长期容易污染业务代码,也容易漏埋、误删、重复上报。尤其是低代码页面、动态列表、弹窗和微前端场景,业务函数里到处插埋点很难维护。

所以我采用“声明式为主,编程式补充”。常规点击、曝光用声明式事件,让业务声明“这是 submit_form”这类稳定语义;复杂业务成功、异步结果、后端返回后的转化事件,可以手动 track()

如果面试官追:core 包和适配包怎么拆?

我会这样说:

core 不依赖 React/Vue,它只提供:

  • SDK 初始化和配置。
  • 公共上下文管理,比如 user、tenant、session、route、app version。
  • 事件队列和上报通道。
  • 插件注册、执行和 teardown。
  • 错误隔离和远程开关。

React 适配包可以提供 hook 或 Provider,自动接路由上下文;Vue 适配包可以提供 plugin install;Vanilla 只提供直接初始化。这样 core 体积更可控,也避免为了某个框架引入额外依赖。

如果面试官追:插件接口怎么设计?

我会说插件不是随便执行一段代码,应该有生命周期:

type MonitorPlugin = {
  name: string
  setup(ctx): void
  teardown?(): void
  onEvent?(event): event | void
  beforeSend?(batch): batch
  onError?(error): void
}

比如点击采集、曝光采集、Web Vitals、Fetch/XHR 监控都可以是插件。每个插件内部出错不能影响主业务,也不能影响其他插件。SDK 会在插件调用外层做隔离,插件失败最多丢监控,不拖垮页面。

如果面试官追:声明式点击怎么不漏?

我会说我不会给每个元素单独绑监听器,而是在 document 或应用 root 上做事件委托。

点击事件冒泡后,用 event.composedPath() 找到最近的 data-track-event 节点。这样 React/Vue 动态渲染、节点销毁重建都不需要重新绑定。对于 Shadow DOM、iframe、微前端,要根据容器边界单独接入,不能假设一个 document 能覆盖所有上下文。

事件 payload 里会补公共上下文,比如 route、tenant、session、app version。业务方只需要声明事件语义和少量扩展字段。

如果面试官追:微前端重复初始化怎么办?

我会说这是实际接入里必须处理的问题。

主应用和子应用都可能初始化 SDK,所以要有 instance id、app id 和 namespace。可以让主应用提供公共上下文,子应用只注册自己的 app context;上报时用幂等 id 或 event id 做去重。更重要的是约定谁负责全局事件监听,避免主子应用各绑一套导致重复采集。

如果 iframe 跨域,就不能直接读上下文,要通过 postMessage 传递有限的上下文字段,并做来源校验。

收尾句

所以这个 SDK 的核心不是一个 track() 方法,而是一套低侵入、可插件化、可隔离、可降级的采集运行时。它让业务表达事件语义,SDK 负责稳定采集和可靠上报。