Sanity Studio 遥测(Telemetry)架构深度解析:事件定义、批处理传输与用户同意机制

发布时间:2026/9/17 8:29:32
Sanity Studio 遥测(Telemetry)架构深度解析:事件定义、批处理传输与用户同意机制 Sanity Studio 遥测Telemetry架构深度解析事件定义、批处理传输与用户同意机制【免费下载链接】sanitySanity Studio – Rapidly configure content workspaces powered by structured content项目地址: https://gitcode.com/GitHub_Trending/sa/sanitySanity Studio 通过sanity/telemetry包收集匿名化使用数据与性能指标事件经批量聚合、上下文富集后发送至 Sanity 的 intake API全过程受用户同意机制约束且可完全关闭。本文以 docs/TELEMETRY.md 为主线结合packages/sanity/src下真实源码与测试完整讲解遥测的 Provider 架构、事件定义规范、标准属性词表、批量传输细节、调试手段、事件全景分类以及新增埋点的标准流程读者读完可独立为 Sanity Studio 功能添加符合规范的可观测埋点。一、遥测系统全景从 Provider 到 Intake API1.1 架构分层Sanity Studio 的遥测基础设施以 React Provider 嵌套的形式组织职责层层递进StudioProvider └── StudioTelemetryProvider # Creates batched store, enriches events with context └── TelemetryProvider # React context from sanity/telemetry/react └── PerformanceTelemetryTracker # Core Web Vitals legacy INP └── [Studio children] # Components use useTelemetry() hookStudioTelemetryProvider整个遥测系统的心脏负责创建批处理 storebatched store、维护上下文context、执行同意检查TelemetryProvider来自sanity/telemetry/react的 React Context 提供者让任意子组件通过useTelemetry()钩子拿到 logger 实例PerformanceTelemetryTracker挂载性能追踪同时运行 Core Web Vitals基于web-vitals/attribution与旧版 INP 追踪。1.2 关键文件地图文件职责packages/sanity/src/core/studio/telemetry/StudioTelemetryProvider.tsx主 Provider创建批处理 store、富集事件上下文packages/sanity/src/core/studio/telemetry/types.tsTelemetryContext接口定义packages/sanity/src/core/studio/telemetry/PerformanceTelemetry.ts挂载 Core Web Vitals 与旧版 INP 追踪packages/sanity/src/core/studio/telemetry/useWebVitalsTelemetry.ts基于web-vitals/attribution的 Core Web Vitals 追踪packages/sanity/src/core/studio/telemetry/useMeasurePerformanceTelemetry.ts旧版 INP v1 追踪基于PerformanceObserverpackages/sanity/src/core/studio/telemetry/telemetryConsent.ts同意状态解析按 projectId 缓存共享 Observablepackages/sanity/src/core/studio/telemetry/utils/debugLoggingStore.ts调试模式的本地日志 store说明文档中提及的MaybeEnableErrorReporting.ts在当前仓库packages/sanity/src目录下未检索到对应文件其职责错误上报的同意检查由 telemetryConsent.ts 中的getTelemetryConsent$机制统一承载——它通过GET /intake/telemetry-status端点、以不同 tag 区分遥测与错误上报的同意状态。1.3 启动时序从 StudioTelemetryProvider.tsx 可以看到两个关键细节会话 ID 在模块顶层通过createSessionId()一次性创建每次页面加载只生成一个会话 ID整个页面生命周期内的所有事件共享该 ID遥测只在客户端运行const isClient typeof window ! undefined服务端渲染SSR环境下不做任何发送。二、事件定义defineEvent()与__telemetry__/目录约定2.1 事件定义的基本形态每个事件用sanity/telemetry导出的defineEvent()定义包含名称、版本、描述以及可选的类型化载荷与采样率。以发布事件为例源码见 documentActions.telemetry.tsimport {defineEvent} from sanity/telemetry interface DocumentPublishedInfo { publishedImmediately: boolean previouslyPublished: boolean } export const DocumentPublished defineEventDocumentPublishedInfo({ name: Document Published, version: 1, description: User clicked the Publish button in the document pane, })2.2 采样率控制maxSampleRate高频指标如性能测量通过maxSampleRate单位毫秒限流——同一个事件在窗口期内最多上报一次。见 performance.telemetry.tsexport const PerformanceINPMeasuredV2 defineEventINPMetricWithAttribution({ name: Performance INP Measured, version: 2, description: Interaction to Next Paint with attribution (web-vitals), maxSampleRate: 30_000, // At most once every 30 seconds })旧版 INP v1 事件的采样窗口更大maxSampleRate: 60_000即最多每分钟一次见同文件第 24-30 行两者并存正是 Core Web Vitals 迁移期的兼容策略。2.3 目录约定事件定义贴近功能代码事件定义统一存放在与功能代码同级的__telemetry__/目录下。当前仓库中已落地大量此类目录例如packages/sanity/src/ core/ comments/__telemetry__/comments.telemetry.ts canvas/__telemetry__/canvas.telemetry.ts releases/__telemetry__/releases.telemetry.ts releases/__telemetry__/navigation.telemetry.ts singleDocRelease/__telemetry__/scheduledDrafts.telemetry.ts tasks/__telemetry__/tasks.telemetry.ts divergence/__telemetry__/divergence.telemetry.ts form/__telemetry__/form.telemetry.ts form/studio/tree-editing/__telemetry__/nestedObjects.telemetry.ts studio/__telemetry__/performance.telemetry.ts studio/__telemetry__/featureAvailability.telemetry.ts studio/__telemetry__/studioLoaded.telemetry.ts store/document/__telemetry__/documentOutOfSyncEvents.telemetry.ts store/document/__telemetry__/listenerLatency.telemetry.ts store/document/__telemetry__/documentPairLoading.telemetry.ts ... structure/ documentActions/__telemetry__/documentActions.telemetry.ts panes/document/__telemetry__/documentPanes.telemetry.ts panes/documentList/__telemetry__/documentListSearch.telemetry.ts panes/document/documentPanel/banners/__telemetry__/DraftLiveEditBanner.telemetry.ts diffView/__telemetry__/diffView.telemetry.ts components/requestPermissionDialog/__telemetry__/RequestPermissionDialog.telemetry.ts ...命名规范为xxx.telemetry.ts部分历史文件为.telemetry.tsx。将事件定义放在功能代码附近既能保证埋点与业务逻辑同步演进也便于 code review 时一眼定位。三、标准属性值集低基数词表location/position/path同一个交互可能发生在多个 UI 表面、由多种触发方式发起。规范要求不要把这些上下文揉进事件名那会把单一动作碎片化成大量高基数的名字而是编码为通用事件上的属性。为了让这些属性可聚合取值必须来自共享的、低基数的词表。以下三组值是**权威canonical**词表。埋新事件时优先复用已有取值只有当现有取值都无法描述新的表面或触发方式时才引入新值且必须与 Data / Analytics 团队协调保证下游 dbt 模型和 Looker 仪表盘同步。归属方Studio App 团队SAPP与 Data / Analytics 协同维护。location交互发生的 UI 表面值描述document_pane主文档编辑面板array_list对象数组列表字段nested_object_dialog嵌套对象编辑对话框或弹层tree editingposition对象在集合中的创建/编辑位置与location: array_list配对值描述new通过数组的主添加控件创建appended插入到已有条目之后prepended插入到已有条目之前nested打开已有嵌套条目进行编辑path导航或打开交互的触发方式值描述breadcrumb通过面包屑控件close_button通过对话框关闭按钮keyboard_shortcut通过键盘快捷键这些词表在代码中的具体消费场景可见于 nestedObjects.telemetry.ts树编辑等埋点文件中。四、事件如何被发送4.1 React HookuseTelemetry()组件通过sanity/telemetry/react的useTelemetry()钩子记录事件import {useTelemetry} from sanity/telemetry/react import {DocumentPublished} from ./__telemetry__/documentActions.telemetry function MyComponent() { const telemetry useTelemetry() const handlePublish () { telemetry.log(DocumentPublished, { publishedImmediately: true, previouslyPublished: false, }) } }4.2 功能级封装 Hook当一个功能包含多个事件时用专用 hook 封装所有遥测逻辑避免组件里散落大量telemetry.log调用// useCommentsTelemetry.ts export function useCommentsTelemetry() { const telemetry useTelemetry() return { linkCopied: () telemetry.log(CommentLinkCopied), viewedFromLink: () telemetry.log(CommentViewedFromLink), listViewChanged: () telemetry.log(CommentListViewChanged), } }这一模式在 singleDocRelease 下的 scheduled drafts 埋点由useScheduleDraftOperations在操作成功后触发等实现中均有体现。4.3 批处理与传输Batching and Transport事件不会即时发送而是汇入批处理 store周期性刷新设置项值刷新间隔生产30 秒刷新间隔调试1 秒会话 ID每次页面加载经createSessionId()创建一次源码佐证见 StudioTelemetryProvider.tsxflushInterval: 30000调试模式则使用debugLoggingStore中的flushInterval: 1000见 debugLoggingStore.ts。两种投递方式HTTP POST主路径通过 Sanity client 以POST /intake/batch提交body 结构为{projectId, batch: enrichedBatch}Beacon API页面卸载页面关闭时用navigator.sendBeacon()向/intake/batch发送保证关闭瞬间的事件也能可靠送达。对应实现分别见 StudioTelemetryProvider.tsx 的sendEvents与sendBeacon回调。4.4 事件富集每个事件都携带TelemetryContext发送前批次中的每个事件都会被附加一个TelemetryContext对象接口定义见 types.ts// Payload sent to /intake/batch { projectId: abc123, batch: [ { // Original event data (name, version, data, timestamp, etc.) ...event, // Enrichment context context: { // Static (captured once) userAgent: Mozilla/5.0..., screen: { density: 2, height: 1080, width: 1920, innerHeight: 900, innerWidth: 1600 }, studioVersion: 5.18.0, reactVersion: 19.2.3, environment: production, connection: { effectiveType: 4g, downlink: 10, rtt: 50, saveData: false }, // Dynamic (updated on navigation) orgId: org_xyz, activeTool: desk, workspaceCount: 2, activeWorkspace: default, activeProjectId: abc123, activeDataset: production, pluginCount: 12, schemaTypeCount: 84, } } ] }实现要点均可在源码中验证上下文存放在useRef中contextRef动态值workspace、tool、org变化时只更新 ref不会重新创建批处理 store——见 StudioTelemetryProvider.tsxpluginCount递归统计所有嵌套插件countPlugins函数第 54-57 行因此包含子插件的数量会被正确累加schemaTypeCount来自workspace.schema.getTypeNames().length第 106 行连接质量使用浏览器 Network Information APInavigator.connection/mozConnection/webkitConnection第 59-73 行不可用时返回null这些字段全部来自本地不会因此增加任何 Sanity API 请求。这些行为在测试 StudioTelemetryProvider.test.tsx 中被逐一验证例如测试断言sendEvents中client.request收到带context的富集批次第 149-204 行、sendBeacon发送的 JSON 中batch[0].context结构完整第 206-251 行、workspace 与 activeTool 变化时上下文随之更新第 253-336 行、Network Information API 可用时connection字段被正确填充第 416-459 行。4.5 特殊事件StudioLoaded与WorkspaceFeaturesObservedProvider 挂载时还通过store.logger直接记录两个特殊事件因为useTelemetry()在 Provider 内部不可用StudioLoaded页面加载完成时上报一次携带 studio 版本、React 版本、环境、userAgent 与屏幕尺寸。源码使用studioLoadedFiredRef做实例级去重确保 React StrictMode 下双次挂载 effect 只上报一次见 StudioTelemetryProvider.tsxWorkspaceFeaturesObserved每个活动 workspace 上报一次切换 workspace 时重发载荷是已解析功能开关的低基数快照。同样用observedFeaturesKeyRefkey 为projectId:name做去重第 213-220 行。WorkspaceFeaturesObserved的载荷投影逻辑在 featureAvailability.telemetry.ts覆盖 releases、tasks、scheduled drafts/publishing、media library、canvas、variants、document group inventory、events API、announcements、drafts、partial indexing、direct uploads、search strategy、advanced version control 等开关。规范要求载荷中只允许布尔、小枚举或短数字绝不携带自由文本、标识符或客户值。测试对缺失字段意味着应用 studio 默认值的语义有明确断言见测试第 496-539 行缺省字段上报undefined而不是合成默认值。五、用户同意机制Consent遥测是受同意约束的。任何事件发送之前studio 都会检查用户的同意状态GET /intake/telemetry-status → { status: granted | denied }返回granted事件正常发送返回denied事件被静默丢弃。该检查发生在StudioTelemetryProvider挂载时通过批处理 store 的resolveConsent选项见 StudioTelemetryProvider.tsxresolveConsent: () client.request({url: /intake/telemetry-status, tag: telemetry-consent.studio}),底层的同意解析在 telemetryConsent.ts 中实现getTelemetryConsent$返回一个按 projectId 缓存的共享 ObservableshareReplay(1)同一会话内对同一项目的重复查询不会发出额外 API 请求非granted状态一律视为denied。错误上报有独立的同意检查文档中记载其由MaybeEnableErrorReporting承载使用同一端点但不同的 tagtelemetry-consent.error-reporting。这保证了遥测与错误上报可以分别授权、互不影响。六、调试模式设置环境变量即可把事件打印到控制台而非发送SANITY_STUDIO_DEBUG_TELEMETRYtrue开启后见 debugLoggingStore.ts同意自动授予resolveConsent直接 resolvegranted刷新间隔降为 1 秒事件以[telemetry]前缀打印到console.log——批次用console.groupCollapsed折叠展示单条事件带颜色标签log 绿色、trace.start 蓝色、trace.error 红色等并按 key 逐行打印载荷便于肉眼核对不发起任何网络请求。调试开关的判断逻辑在 StudioTelemetryProvider.tsximport.meta.env?.SANITY_STUDIO_DEBUG_TELEMETRY true时直接使用debugLoggingStore替换正常 store。测试文件开头也显式清空该环境变量避免测试输出被遥测日志污染见测试第 9 行。七、事件全景分类7.1 性能类Core Web Vitals由web-vitals/attribution库自动追踪挂载点在 useWebVitalsTelemetry.ts事件指标版本Performance LCP MeasuredLargest Contentful Paintv2Performance FCP MeasuredFirst Contentful Paintv2Performance CLS MeasuredCumulative Layout Shiftv2Performance TTFB MeasuredTime to First Bytev2Performance INP MeasuredInteraction to Next Paintv1旧版 v2实现细节有源码依据LCP/FCP/CLS/TTFB/INP v2全部通过web-vitals/attribution的onLCP/onFCP/onCLS/onTTFB/onINP注册直接透传带归因attribution的完整 metric 对象useRef防止 React StrictMode / HMR 下重复初始化web-vitals 要求每页只注册一次CLS 只在页面隐藏visibilitychange时上报INP 是页面会话中最差的一次交互延迟INP v2 显式开启includeProcessedEventEntries: true以保持与 v5 相同的归因覆盖v6 默认关闭见 useWebVitalsTelemetry.ts旧版INP v1由 useMeasurePerformanceTelemetry.ts 实现用原生PerformanceObserver观察event类型条目取最大 duration 的事件上报target元素选择器串、attrsdata-ui/data-testid、interaction事件名与duration。7.2 文档操作Document Actions事件定义见 documentActions.telemetry.ts事件触发时机Document Published发布操作完成Publish Button Clicked发布操作阶段化started/completed/failed载荷含stagePublish Button Becomes Disabled - Started/Completed发布按钮状态切换载荷含isRemoteEvent同文件还定义了Document Deleted删除确认/完成/失败载荷含引用计数referenceCount、internalReferenceCount、crossDatasetReferenceCount等更多文档生命周期事件。7.3 内容发布Releases事件触发时机Version Document Added to Release文档被加入发布Release Created/Deleted/Published发布生命周期Release Scheduled/Unscheduled发布调度Release Archived/Unarchived发布归档Release Reverted/Duplicated发布管理Release Description Set发布描述的使用情况——创建时设置或在 Studio 编辑含动作、字符数、是否含 URL绝不采集内容本身Release Link/ID/Title Copied剪贴板操作Navigated to Releases Overview导航Navigated to Scheduled Drafts导航7.4 定时草稿Scheduled Drafts定时草稿cardinality-one 发布有独立事件以便与内容发布区分使用情况。每个事件在useScheduleDraftOperations对应操作成功后上报定义见 scheduledDrafts.telemetry.ts事件触发时机载荷Scheduled Draft Created草稿被调度发布无Scheduled Draft Rescheduled已调度草稿的发布时间被修改{ fromPaused }从暂停恢复时为 trueScheduled Draft Cancelled已调度草稿被取消{ keptAsDraft }内容保留为草稿时为 true7.5 评论Comments事件触发时机Comment Link Copied评论链接复制到剪贴板Comment Viewed From Link通过共享链接打开评论Comment List View Changed切换视图模式7.6 任务Tasks事件触发时机Task Created/Duplicated/Removed任务生命周期Task Status Changed任务状态变化Task Link Copied/Opened任务分享7.7 搜索Search事件触发时机Recent Search Clicked用户点击最近搜索Document List Load Time Measured搜索性能采样7.8 Canvas事件触发时机Canvas OpenedCanvas 打开Canvas Link CTA Clicked/RedirectedCanvas 链接交互Canvas Unlink CTA Clicked/ApprovedCanvas 取消链接7.9 表单交互Form事件触发时机Portable Text Input Expanded/CollapsedPTE 编辑器状态Portable Text Invalid Value Ignore/ResolvePTE 错误处理Created Draft新建草稿7.10 分歧处理Divergences分歧会话divergence session从文档首次触发Inspected Divergence事件开始持续到文档在未解决分歧的情况下关闭为止。每个 studio 面板、浏览器标签页和设备各自跟踪自己的会话因此同一用户可以对同一文档持有并行会话。Inspected Divergence与Acted On Divergence都携带 session idBigQuery 据此按会话拼接解决漏斗。事件定义见 divergence.telemetry.ts事件触发时机Inspected Divergence用户查看单个节点中的分歧载荷含sessionId、divergenceCountActed On Divergence用户解决分歧载荷含action: take-upstream-value \| mark-resolvedWorkspace Features Observed每个活动 workspace 上报一次切换时重发载荷为已解析功能开关的低基数快照releases、tasks、scheduled drafts/publishing、media library、canvas、variants、document group inventory、events API、announcements、drafts、partial indexing、direct uploads、search strategy、advanced version control。值直接从解析后的 workspace 读取缺失字段表示应用 studio 默认值7.11 其他事件Copy/Paste—— 文档 ID 与 URL 复制Upsell dialogs—— 免费试用与功能升级交互见studio/upsell/__telemetry__/upsell.telemetry.tsStudio announcements—— 公告查看与交互Request permission dialogs—— 权限请求流程Document out-of-sync—— 分歧与冲突事件Document pair loading—— 加载性能指标Listener latency—— 实时监听性能Nested object editing—— 树编辑交互Draft live edit banner—— Banner 交互Focus events—— 文档面板焦点追踪八、新增一个埋点事件四步标准流程步骤 1在功能旁创建事件定义在功能的__telemetry__/目录下新建文件用defineEvent()定义事件// src/core/myFeature/__telemetry__/myFeature.telemetry.ts import {defineEvent} from sanity/telemetry interface MyEventData { actionType: string } export const MyFeatureUsed defineEventMyEventData({ name: My Feature Used, version: 1, description: User interacted with my feature, })步骤 2在组件中记录事件import {useTelemetry} from sanity/telemetry/react import {MyFeatureUsed} from ./__telemetry__/myFeature.telemetry function MyFeature() { const telemetry useTelemetry() const handleAction (type: string) { telemetry.log(MyFeatureUsed, {actionType: type}) } }步骤 3多事件场景用专用 hook 封装// useMyFeatureTelemetry.ts export function useMyFeatureTelemetry() { const telemetry useTelemetry() return { used: (actionType: string) telemetry.log(MyFeatureUsed, {actionType}), // ... 其他事件 } }步骤 4遵守规范优先复用 标准属性值集 中的location/position/path取值高频事件设置maxSampleRate限流载荷只放布尔、小枚举或短数字不放自由文本与标识符引入新词表取值前与 Data / Analytics 团队协调保持下游 dbt 模型与 Looker 仪表盘同步。九、测试与验证遥测 Provider 通过 mock 测试覆盖测试文件位于 StudioTelemetryProvider.test.tsx。测试策略要点Mock 化sanity/telemetrycreateBatchedStore、createSessionId与sanity/telemetry/reactTelemetryProvider退化为透传 children依赖的 hooksuseClient、useWorkspace、useWorkspaces、useProjectOrganizationId、sanity/router以及PerformanceTelemetryTracker均被 mock测试聚焦 Provider 自身的契约捕获 store 选项createBatchedStore的 mock 会捕获传入的sendEvents/sendBeacon回调测试直接调用这些回调并断言client.request/navigator.sendBeacon收到的富集批次结构场景覆盖上下文富集静态动态字段、sendBeacon 的 JSON 结构与 context、workspace 切换时动态字段更新、activeTool 切换、orgId为 null 的容错、屏幕尺寸、Network Information API 连接质量、WorkspaceFeaturesObserved对已解析功能开关的透传与缺省语义、StudioLoaded在 StrictMode 下只上报一次等单元测试中useTelemetry()返回的是 no-op logger默认不产生副作用。十、隐私边界与关键设计准则最后总结本仓库遥测实现中值得借鉴的设计准则同意优先所有事件发送前经GET /intake/telemetry-status门控denied时静默丢弃不打扰用户零额外 API 成本上下文字段全部取自本地navigator、window、已解析的 workspace 配置富集过程不新增任何 Sanity API 请求低基数优先通用事件 标准属性词表避免高基数事件名碎片化保证数据可聚合绝不采集内容如Release Description Set只上报动作、字符数与是否含 URL从不携带描述内容本身可调试SANITY_STUDIO_DEBUG_TELEMETRYtrue一键切换本地日志模式全程零网络请求防重复上报StudioLoaded与WorkspaceFeaturesObserved均通过 ref 在 StrictMode 双挂载下只上报一次页面卸载不丢事件HTTP POST 为主、Beacon API 兜底兼顾吞吐与可靠性。对于正在为 Sanity Studio 开发插件或贡献核心代码的开发者遵循 docs/TELEMETRY.md 与本文梳理的规范即可让新功能无缝融入现有遥测体系获得可聚合、可审计、尊重用户隐私的使用数据。【免费下载链接】sanitySanity Studio – Rapidly configure content workspaces powered by structured content项目地址: https://gitcode.com/GitHub_Trending/sa/sanity创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询