为什么这么设计:纯外部观测与 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 桥,事件流 + 扫描元素就能给答案:
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 的维护者文档里,用户站不再展开。
下一步
- DevTools 扩展:扩展的构建、加载、典型调试场景。
- 用 playground + DevTools 联调管线:启用 Debug 桥的具体步骤、「管线」Tab 怎么读。
- DevTools 面板字段参考:事件流 / 管线 / 元素三个 Tab 每个字段的精确含义。
- 排查:没有上报:自上而下分清"没埋上 / 被过滤 / 发出了没到后端"。