Skip to content

为什么这么设计:纯外部观测与 Debug 桥的取舍 ​

DevTools 扩展默认采取纯外部观测——不和 SDK 通信、不要求 SDK 开任何 debug 开关、不发布到 npm、也不需要业务代码 import。只有当外部视角真的看不到你要排查的东西时,才需要叠加打开 SDK 内置的 Debug 桥,在面板的「管线」Tab 看每条事件在 SDK 内部的逐阶段去向。

本页解释两个问题:

  • 为什么默认是纯外部观测?这套设计的取舍是什么?
  • 什么时候该升级到启用 Debug 桥?怎么对接到联调流程?

为什么默认是纯外部观测 ​

「调试 SDK 上报」最直觉的做法,是给 SDK 加一个 debug: true 开关、让它把内部状态实时打印到 DevTools,扩展直接读这份数据就行。我们没这么做,原因是想把默认体验做到三个零:

  • 零侵入接入页:扩展装上即用,业务不必改 init()、不必引新的依赖、也不必加任何打包标志。
  • 零信任 SDK 内部:上报真的有没有发出去,看 Network 才是最终答案——SDK 自报的"我发了"未必等于网关收到了。外部观测的视角天然就是"最终事实"。
  • 零生产风险:不需要 SDK 内部状态默认对外广播,就不存在"上线时忘记关 debug、把采样前的原始事件泄露给同源页面"这类风险。

把这三条钉在默认路径上,扩展和 SDK 就可以独立演进:扩展只依赖网关上报契约(POST 批次的 wire 格式)和页面 DOM 约定(data-trace 元素),两者都是公开稳定的边界,跨版本兼容代价小。SDK 也不需要为"被观测"付出常驻成本。

代价是:扩展看不见 SDK 内部发生过、但没真正发到网络的事——这是这套取舍的另一面,下一节展开。

外部观测能看见什么、看不见什么 ​

把扩展想成"贴在网关入口和页面 DOM 上的两只摄像头",它只拍真的发生在外面的事:

范围
能看见每条实际 POST 出去的事件(行为 / 错误 / 性能走同一接口,按 event_type 通用纳入)、完整信封 { context, payload, event_format, timestamp }、批量壳里的 device_id / session_id / 背压淘汰计数 dropped、页面上所有 data-trace* 元素及其 TracePath 坐标
看不见被采样 / Schema 校验 / killSwitch 在 SDK 内部丢弃的事件(它们从不上网络)、队列实时水位、每个管线阶段(采样 / Schema / PII 清洗 / beforeEmit / 序列化)各自丢了什么、改了什么

「看不见」不是 bug,是物理边界

扩展不拦截 fetch / beacon,也不注入 SDK 运行时——这是它"零侵入"的代价。在网络上没出现过的字节,扩展就是看不到。

绝大多数日常调试场景("埋点是否生效"、"这次点击实际报了什么"),外部观测够用,而且因为视角就在网关入口,结论也最可信:扩展里看到了 = 网关一定收到了。

真正会撞上"看不见"的,是少数深度排查场景——我们专门做了一套可选叠加能力来覆盖它。

什么时候该启用 SDK 内置 Debug 桥 ​

下面这几类问题,外部观测原则上回答不了,需要升级到 Debug 桥 + 面板的「管线」Tab:

  • "埋了但没上报",到底是哪一阶段丢的:扫描元素能看到 data-trace 元素,事件流却始终没出对应事件——可能是被采样丢弃、可能是 Schema 校验失败、可能是 killSwitch 熔断了上报。这三种结果在事件流里长得一模一样(都是"没有"),只有看到管线里"在采样阶段 dropped"、"在 schema 阶段 dropped"才能区分。
  • PII 清洗到底改写了什么:上报的字段不是你写进去的那个值,怀疑被 PII 阶段清洗过——管线 Tab 能直接看到清洗前的原始 raw 与清洗后产物的对比。
  • 队列背压具体怎么发生的:事件流底部条只显示一个累计淘汰计数;要看队列实时 size、按优先级(P0/P1/P2)分布、每次 flush 节奏,需要 Debug 桥广播的队列快照。
  • 想验证某次发布前后的管线行为变化:在本地 / 测试环境对比同一批操作,管线条目级别的逐阶段产物是最细的口径。

反过来,下面这些情况没必要启用 Debug 桥,事件流 + 扫描元素就能给答案:

  • 确认某个元素埋上没——场景 A。
  • 看某次操作实际报了什么字段——场景 B。
  • 怀疑"没上报"但还没分清是没埋上 / 被过滤 / 没发出网络——先按排查指南走一遍,多数情况会在外部观测阶段就定位掉。

Debug 桥从用户视角看是怎么工作的 ​

一句话:SDK 默认不装配 Debug 桥;你在面板上点「开启内部观测」(或在被检页面写一个 localStorage flag),刷新页面后 SDK 才异步加载 Debug 模块,把每条事件流经的管线阶段、队列状态变化通过 window.postMessage 同源广播给监听者;扩展的 content 收到后,沿"content → background → 面板"链路把数据送进「管线」Tab。

用户视角只需要记住三件事:

  • 必须刷新被检页面才生效:SDK 只在装配那一刻读取一次 debug flag,之后就不再重读。开关了 flag 没刷新,看到的"管线"就还是空的。
  • 必须开着面板才接收得到:扩展和 SDK 之间没有持久通道——Debug 桥是被检页面对当前同源广播事件,面板是其中一个监听者。
  • 页面端的 flag 决定一切(一个例外):在面板上点「开启内部观测」按钮,本质是替你在被检页面写下 localStorage['__tripo_lens_debug']='1';想彻底关掉,把这个键删掉再刷新就行。例外是构造时显式传了 debug: false 的实例——它硬关 Debug 桥、拒绝页面级 flag(跑分 / 静默实例防广播洪泛),这类实例只能改代码开启。

具体操作(三种等价启用方式、面板 Tab 各字段含义、收工怎么关)整理在联调指南里,本页不重复。

Debug 桥只在本地 / 测试用,生产禁用

开启 Debug 桥后,SDK 会把采样 / PII 清洗之前的原始事件(含潜在 PII)经 window.postMessage 广播给所有同源监听者。生产页面绝不能开——SDK 默认就是关的,启用是显式动作;收工时请按联调指南关回。

边界与代价一览 ​

视角适用场景代价
纯外部观测(默认)所有日常调试:确认埋点、看实际上报的字段、双向定位事件与元素看不到被 SDK 内部丢弃的事件,无法区分"哪一阶段丢的"
叠加 Debug 桥(按需)区分丢弃阶段、看 PII 清洗改写、看队列实时状态需要刷新页面,需要在被检页面写 flag;仅限本地 / 测试,不能上生产;端到端联调需要联调指南那条路径

两者不是替代关系,是默认 + 叠加:Debug 桥关掉后扩展仍然能完整跑纯外部观测,扫描元素与事件流照样工作。

实现层细节(content / background / 面板的消息通路、Debug 桥消息形态、扩展内部的状态聚合方式)放在仓库 llmdoc/architecture 的维护者文档里,用户站不再展开。

下一步 ​

基于 MIT 协议发布(内部使用)