Frontend Insight:沿一条错误理解架构与代码边界
从 SDK、API、Kafka 到查询页面,解释进程分工、共享契约,以及 Python 开发者阅读 TypeScript 工程时需要补齐的运行时知识。
在实验室触发一条 JS 错误,网络面板已经显示 HTTP 202,管理台却还是空的。这时该查 SDK、consumer,还是页面筛选?202 只是这条路径上的一个确认点。沿同一个事件继续往后找,才能分清它尚未入库,还是已经被查询范围排除。
本文沿 Frontend Insight 的基础错误链路解释运行时分工,再把实现对应到代码目录。面向有编程经验、尚不熟悉 TypeScript/Vue/NestJS 工程的读者;版本与系列阅读顺序见开发者阅读入口。
一次错误的最小输入与输出
在已部署的隔离 demo 中,点击“模拟 JS 异常”,SDK 生成 event="error"、env="dev"、appId="fi_public_m1demo001",并附带本次 eventId 和当前 timestamp。这些是识别链路的字段摘录,不是可直接提交的完整批次;SDK 会补齐 v3 批次、页面与会话字段。
| 观察点 | 预期结果 |
|---|---|
POST /v1/events | HTTP 202:接收端完成本阶段接收,不等于已经能查询 |
| consumer 持久化后,查询匹配项目、dev 环境与时间窗 | occurrence 列表出现该事件,身份为 sha256(eventId) |
| 质量页点击“刷新” | 相对时间窗推进,出现对应“打开诊断上下文”入口 |
保持旧的自定义 to,早于事件时间 | 即便 202 且已入库,页面仍不出现该事件;这是时间筛选的预期结果 |
这个输入、响应和失败分支来自固定基线的 demo-quality 测试。运行需要有源码权限、Node ≥24、pnpm 11.15.1,以及独立的 Docker/Compose 服务栈;本站只提供可阅读的说明。复用维护文章的环境准备与命令,不把 202 或一次模拟 UI 响应当作完整链路复现。
先理解系统在做什么
先看这条错误所在的系统。Frontend Insight 接收业务网站的访问、操作和错误,再分别用于运营、质量和治理:
- 运营:页面是否被有效使用、功能是否采用、工作流是否完成、操作是否高效。
- 质量:错误发生在哪里、性能与稳定性事实是否满足定义、是否具备评分条件。
- 治理:指标版本、评分配置、组织目录、可信数据源和 SDK 策略怎样影响上述结果。
例如,用户点击提交,只能证明操作被触发;业务是否成功,还要等待结果。同样,错误详情没有保存,并不能说明质量合格,观察到一些数据也未必足以评分。维护时保留这些区别,页面上的数才有可以解释的含义。
运行时与代码组织
Nginx/Compose 负责入口与进程部署,图中省略了这层,避免把网络代理误认为业务计算层。部署拓扑见 生产 Compose。
| 位置 | 真实职责 | 为什么这样分 |
|---|---|---|
| apps/web | Vue 页面、路由、筛选、API 调用、状态呈现 | 浏览器呈现服务端语义,不拥有权威评分算法 |
| apps/demo-app | 产生可控制的业务、工作流与错误场景 | 提供真实 SDK 接入示例和端到端验证入口 |
| web-tracker | 路由/可见时间/操作/质量采集、内存队列、发送 | 嵌入业务站点,故障不应扩散为宿主业务异常 |
| event-contract | Schema、规范名称、生成类型、运行时校验、共享范围算法 | SDK、接收端、消费端共享同一传输语言 |
| apps/api | HTTP 路由、输入校验、认证、组件装配 | 类似 Python 项目中路由与依赖注入层 |
| server-core | 业务规则、事实读取、归约、指标绑定、评分、存储服务 | 可脱离 HTTP 与 Vue 测试业务不变量 |
| apps/consumer | Kafka 校验、上下文复核、批量写事实、offset 与 DLQ | 隔离接收延迟与存储延迟,承担重试语义 |
| database | 两种数据库的版本迁移与校验 | 让新建与保留数据升级有可核对的结果 |
| shared-config | 环境配置边界 | 配置错误在服务启动时尽早暴露 |
| test-fixtures | 共享合法/非法事件等测试输入 | 避免测试各自发明不同契约 |
管理数据在 MySQL,分析事实在 ClickHouse,Kafka 连接接收与消费。每种依赖都承担明确职责;新增存储前需要说明它接管哪类数据、怎样迁移及恢复。
Python 开发者需要补齐的最少知识
| 本项目写法 | 可借用的 Python 理解 | 维护时真正要注意的事 |
|---|---|---|
interface、type、import type | 类型提示、结构描述 | TypeScript 类型编译后消失;外部 JSON 必须继续经 Zod/Ajv 校验 |
{ ok: true, value } | { ok: false, errors } | 有标签的成功/失败结果 | 先检查 ok 再使用 value,不能用类型断言跳过失败路径 |
Promise<T>、async/await | 协程返回结果 | 创建 Promise 即可能开始工作;是否等待写入影响 202、事务和 offset 语义 |
Map / Set | dict / set | 常用于按事件 ID 去重、实例状态;内存集合不会跨进程共享 |
undefined 与 null | 缺字段与明确空值 | 查询省略条件和“这个值不可得”不是同一语义 |
@Controller、@Get、@Inject | 路由装饰器、依赖注入 | Nest 装配关系由 AppModule 和 CoreService 明确建立 |
.vue 的 <script setup lang="ts"> | 页面逻辑与模板同文件 | ref 在脚本用 .value,模板自动解包;computed 是派生状态,watch 触发副作用 |
workspace:* | 同仓库本地包依赖 | 一个工作区包含多个包;共享包可供多个应用使用,应用不能互相依赖 |
根 package.json 固定 pnpm 版本并要求 Node >=24。这里的 type: module 表示 ESM;源码中服务端使用的 .js 导入后缀对应编译输出,不应看到 .ts 文件就机械替换为 .ts 后缀。部分包运行时导出 dist,改源码后运行集成链路需要构建;只通过类型检查不能证明运行时加载了新代码。
工作区边界检查 校验依赖环、应用到应用、共享包到应用的非法依赖。需要共享逻辑时,将它放入合理的 packages/*,不要让 API 直接导入 Vue 页面的实现。
用一条 JS 错误走完系统
错误明细穿过采集、接收、存储、查询和页面,是理解系统边界的一条具体路径。下面先追基础错误事实,独立诊断详情的采集、存储和权限在运行时诊断中展开。
- Demo App 的场景触发错误;SDK 的 observability.ts 捕获并规范化。
- BrowserTracker 为事件关联
appId/env/release、页面、身份和eventId,进入队列;flush发送 v3 批次。 - IngestionController.ingest 接收
POST /v1/events,调用 IngestionManager.accept。 accept校验契约、项目、Origin 和 SDK 策略,HMAC 处理用户引用、补充可信组织信息,等待 Kafka 发布成功后返回 202。- EventConsumerRuntime.eachBatch 再校验 envelope 与业务上下文,
rows转换为 ClickHouse 行,observabilityGroupId计算错误组;写raw_events后推进消费。 - PageQualityView.vue 以 URL 的项目、环境、日期、页面、类别等条件请求
/api/projects/:projectId/observability/occurrences。 - ObservabilityController.occurrences 授权并按项目时区解析范围;PageQualityStore.read 读取、去重、分页;Vue 显示返回的 occurrence。
走到第 7 步,用户才真正看到这条错误。开头的 202 出现在第 4 步,此时消息已经交给 Kafka,后面的消费、持久化和查询仍未结束。接收诊断状态的写入也只是尽力而为,所以它同样不能代替同步入库证明。
质量页刷新为什么曾漏掉新错误
质量页刷新修复前,质量页打开时的 from/to 固定在 URL;点击刷新只获得新查询快照,未推进事件时间 to。后来产生的错误虽然正常入库,仍被 timestamp < to 排除。
现在 PageQualityView.refresh 对预设范围重新调用 resolveProjectCalendar,更新 URL 并清理旧游标;自定义范围保留日期。切换环境不会把 dev 的事实复制到 staging/prod。Demo 实际发送 dev,这也是排查时必须核对的条件。修复过程见 质量页刷新记录。
在这个故障里,SDK 和写库都已完成工作,修复点却在页面查询范围。排查空表时,把事件实际所在的位置与查询条件放在一起核对,就能避免改错层。
按任务定位,而不是从第一行读到最后一行
| 想理解或修改 | 先读 | 下一层与测试入口 |
|---|---|---|
| 上报字段或拒绝码 | Schema、validator | tracker → pipeline → consumer;pnpm test:r0 |
| 功能类型、成功语义 | analysis-objects、tracker | business-facts / business-analysis;pnpm test:r4a |
| 工作流版本和实例 | workflow、workflow-definitions | workflow-reducer / workflow-facts;pnpm test:r4b |
| 组织、效率、重复操作 | organization-directory、efficiency-facts | repeated-projection / organization-facts;pnpm test:r4c |
| 性能与稳定性口径 | quality-facts、quality-binding | quality.ts / quality-definition;pnpm test:r5a |
| 错误明细和分页 | page-quality、PageQualityView | observability controller;pnpm test:r5b |
| 页面运营 | page-usage、page-usage-binding | PageOperationsView / controller;pnpm test:r6 |
| 指标/分数改动 | metric-library、score-evaluation | score-management / score-storage;pnpm test:r1c |
| 探针、导出接口、设置 | settings、settings.controller | SettingsView;pnpm test:r7 |
这些是对应行为的验证入口。纯函数测试检查输入与结果,真实服务检查持久化和授权,浏览器测试检查操作链路;执行条件和命令由该版本的 package.json 及工作流定义。
两个会影响维护范围的边界
读代码时会在生产、接收和消费入口都看到校验。它们共享一部分规则,但各自还掌握着不同信息。validateForProducer、validateForIngestion 与 validateForConsumer 共享 validateTransportBatch;接收端还知道当前时间、项目和 Origin,消费端复核治理后的身份、目录与业务上下文。合法 JSON 只能说明传输形状成立,不能证明业务证据可信。
以增加错误类别筛选为例,改动会经过 Vue 的 URL 状态、controller 的枚举校验、读模型过滤及 cursor 的 scope。只加下拉选项,可能得到接口拒绝,或把上一种筛选的游标用于下一种筛选。若只是修改标签文案,则不需要改事件契约;是否跨越边界取决于语义变化。
同样,公共 useRemoteData 只保存状态,不能据此断言所有页面都没有并发保护。PageQualityView.load 自己实现了 AbortController 与 generation;行为必须沿实际入口检查。下一篇继续展开事件校验、重试与消费。