资讯动态

Windmill 功能使用遥测(feature_usage)接入指南:从埋点设计到落库验证

发布时间:2026/9/13 14:55:56 来源:尧图企业网站定制
Windmill 功能使用遥测feature_usage接入指南从埋点设计到落库验证【免费下载链接】windmillOpen-source developer platform to power your entire infra and turn scripts into webhooks, workflows and UIs. Fastest workflow engine (13x vs Airflow). Open-source alternative to Retool and Temporal.项目地址: https://gitcode.com/GitHub_Trending/wi/windmillfeature_usage是 Windmill 的产品遥测累加器以天为桶粒度的计数器最终汇总进匿名使用统计载荷。它回答的是这个功能有没有人用、大家选的是哪个变体同时保证没有任何可识别身份的数据离开实例。本文以 docs/feature-telemetry.md 为骨架结合开源仓库中的前端缓冲实现、后端注册与落库逻辑、迁移脚本与测试用例完整讲解何时该埋点、如何设计词汇表、前端与后端两条接入路径以及如何验证一条记录真的落进了数据库。一、feature_usage 是什么匿名、分桶、可聚合在 Windmill 中feature_usage是一张以天为分桶键的计数器表day-bucketed counters所有计数最终滚入匿名的 usage-stats 载荷。它的设计目标很克制只回答有没有人用、选了哪种变体不回答谁在用、怎么用的。目前注册了49 个动作action分布在18 个功能feature上ai_session、ai_chat、ai_fix、ai_agent、ai_agent_eval、app_sandbox、datatable、flow_editor、flow_run、flow_step、home、run_form、debugger、trigger、command_script、hub_script、usage_meter、sso_groups_claim。文档直言产品中几乎所有功能都未埋点因此每一个新的用户可见工作都是补上埋点的机会窗口。从表结构可以最直观地理解这五个字段如何组合成可按天、可按实体、可按 key 切分的计数器。迁移脚本 backend/migrations/20260720081307_add_feature_usage.up.sql 定义了CREATE TABLE feature_usage ( feature VARCHAR(50) NOT NULL, kind VARCHAR(50) NOT NULL, key VARCHAR(100) NOT NULL DEFAULT , entity_id VARCHAR(50) NOT NULL DEFAULT , day DATE NOT NULL DEFAULT CURRENT_DATE, value BIGINT NOT NULL DEFAULT 0, updated_at TIMESTAMPTZ NOT NULL DEFAULT now(), PRIMARY KEY (feature, kind, key, entity_id, day) ); -- 周期性的保留期删除只按 day 过滤没有这个索引会全表扫描 -- 主键要经过另外四列才能到达 day。 CREATE INDEX idx_feature_usage_day ON feature_usage (day);注意两点实现细节value是BIGINT且可累加同一(feature, kind, key, entity_id, day)组合在一天内通过 UPSERT 不断累加而idx_feature_usage_day索引专门服务于 60 天保留期的清理任务详见后文 backend/src/monitor.rs 中的删除逻辑。二、什么时候该埋点什么时候该保持沉默埋点不是每个功能都加。文档给出了清晰的两分法应当提议埋点的场景——当一个新用户可见的交互留下了一个真实的问题新的面板、模式、标签页、开关或入口它到底有没有被发现、被使用存在互相竞争的 UX 路径或新的默认值哪一条胜出有 opt-in 或 beta 门槛接受率take rate是多少多步骤流程用户在哪个环节流失应当保持沉默的场景纯 bugfix、重构、内部管道plumbing有用的信号必须依赖逐条标识数据路径、名称、提示词、代码才能表达——这些根本不允许记录见隐私规则一节如果答案不会改变任何决策埋点就是过度设计什么都不用说。还有一个工程协作上的硬性约定埋点要在方案plan阶段就提出来把具体词汇表写成文字让用户用一行话决定保留或删除而不是把它当成一个独立的问答打断流程。三、设计埋点词汇表五个字段与封闭取值集合这是整套机制的核心。五个字段各有含义与硬性限制字段含义限制feature产品领域如ai_chat、flow_editor≤50 字符kind该领域内的动作如message、panel_placement(feature, kind)是唯一被允许注册的组合≤50 字符key动作的某个切面——模式、标签页类型、工具名、provider:model。聚合按(feature, kind, key)分组因此它决定一个计数器被拆成多少个可比较的桶≤100 字符identifier-shaped可选entity_id不透明的随机id如会话 id用于需要按实体分布而非扁平计数时≤50 字符identifier-shaped可选value增量默认 1被钳制在 1…1,000,000Identifier-shaped的含义是只允许 ASCII 字母数字加_ - : . /不允许空格其他任何字符都会被拒绝。entity_id是解锁分布统计的关键只有提供了它载荷才会按(feature, kind, key)报告entity_count、total_value、median_value、p90_value、inactive_3d_entity_count。省略它就是朴素的这件事发生了多少次计数器。key 词汇必须封闭且精简应该在调用点旁边用 TS union 枚举全部取值让整套集合能在同一个地方被审查。仓库里的 frontend/src/lib/components/flows/flowEditorTelemetry.ts 就是教科书式示例——它把 flow 编辑器的面板位置拆成恰好三个事件export type FlowPanelPlacementEvent /** 宽度在 auto 模式下把面板挤进了 modal。 */ | breakpoint_modal /** 用户在面板处于 modal 时把它固定进了窗格。 */ | force_attach /** 用户在面板处于窗格时把它固定弹出了 modal。 */ | force_detach这个文件还展示了什么时候不该计数的判断力forcedPlacementEvent只有在用户钉住的偏好与面板当前所处位置不同时才返回事件——auto不是被强制的放置而钉在面板已处的位置则没有移动任何东西计数它会与真正移动面板的覆盖操作无法区分。同样createBreakpointTracker只在跨过 breakpoint 时计一次breakpoint_modal因为mode在拖拽过程中会持续重新解析按每次求值计数会把一次拖拽读成几百次。四、接入配方四步走第 1 步和第 3 步跳过会安静地失败文档给出了固定的四步配方并特别警告跳过第 1 步或第 3 步会安静地失败fails quietly。第 1 步在FEATURE_USAGE_KINDS注册 (feature, kind) 组合注册表位于backend/windmill-common/src/feature_usage_ee.rs该文件托管在windmill-ee-private私有仓库。未注册的(feature, kind)会被is_recordable_event用一个裸的continue丢弃——没有报错、没有日志浏览器端依然是 204。也就是说纯前端埋点看起来一切正常实际上什么都没记录。第 2 步从前端记录import { logFeatureUsage } from $lib/utils/featureUsage logFeatureUsage(flow_editor, panel_placement, { key: force_detach })这是 fire-and-forget 调用。事件会按(workspace, feature, kind, key, entityId)在本地求和然后每30 秒刷新一次在visibilitychange→ hidden 时刷新在pagehide时刷新每个请求最多50 条事件失败的批次直接丢弃不重试。这些常量的源码依据在 frontend/src/lib/utils/featureUsage.ts 中FLUSH_INTERVAL_MS 30_000、MAX_EVENTS_PER_REQUEST 50。缓冲实现用Mapstring, { workspace, event }按(workspace, feature, kind, key, entityId)聚合重复事件在本地累加value这样即使 UI 很啰嗦每次刷新也只产生一次 UPSERT。值得注意的工程细节发送端特意不用生成的 API 客户端而是裸fetch并带keepalive: true见 featureUsage.ts 中的flush实现因为keepalive允许请求在标签页关闭/导航后完成——这正是最后一次 flush 发生的时机。而且每次 flush 会先同步发起所有分块请求再 await因为 pagehide 刷新只能保护已经发出的请求keepalive无法拯救一个从未开始的 fetch。认证则依靠 token cookiecredentials: include请求目标是POST /w/{workspace}/workspaces/log_feature_usage该路由在 backend/windmill-api/openapi.yaml 中定义为logFeatureUsage操作实际挂在 backend/windmill-api-workspaces/src/workspaces.rs 的 workspaced 路由上。对应的单元测试在 frontend/src/lib/utils/featureUsage.test.ts覆盖了三个关键行为重复事件按(feature, kind, key, entity)求和后只 flush 一批、按 workspace 拆分批次且没有 workspace 的事件被丢弃、pagehide flush 时在任一 send resolve 之前发起所有分块请求否则关页瞬间会丢事件。第 3 步更新披露文案frontend/src/lib/components/InstanceSettings.svelte 列出了非 minimal 载荷包含的内容——这段文案出现了两次。任何新的计数器如果没有在披露里命名实例就会少披露了自己发送了什么。文档特别提醒这件事已经漂移过一次This has already drifted once。从该组件的披露文本可以看到 feature usage 的具体内容counts of which product features are used, including AI provider and model identifiers, the names of public hub scripts used, the languages debug sessions...;此外它还提供了 air-gapped隔离网络实例手动下载遥测数据的方式windmill-telemetry-${date}-${signature}.json。第 4 步验证有行落库因为存在静默丢弃路径没有报错证明不了任何事。验证方法是直接查库SELECT feature, kind, key, entity_id, day, value FROM feature_usage ORDER BY updated_at DESC LIMIT 10;另一个关键前提采集位于privatefeature 之后公共构建CE从 HTTP 路由到 Rust helper 都不会记录任何东西。必须用--features enterprise,private运行后端否则无论埋点多么正确这张查询都会一直为空。公共构建里对应的是惰性实现 backend/windmill-common/src/feature_usage_oss.rsis_recordable_event恒返回falselog_feature_usage是空操作flush_feature_usage直接返回Ok(())——因为 CE 实例从不发送 stats 载荷计数只会写出没人读的行。五、隐私规则什么可以离开实例只有聚合计数能离开实例且仅当遥测开启、minimal 模式关闭时。硬性禁止永远不要把路径、提示词、脚本内容、workspace 名称、邮箱或任何用户标识符放进key或entity_identity_id必须是不透明随机 id绝不能映射回某个用户或资源如果想要的信号只能用标识数据表达那它根本不能被采集——放弃它。数据生命周期计数器聚合最近30 天的数据行在60 天后被清理。清理逻辑并不依赖遥测发送器而是独立跑在 backend/src/monitor.rs 的监控循环里因此即使遥测被禁用、或构建没有 stats 调度器保留期行也会被修剪// 60-day retention for anonymous feature-usage counters. Runs here (not only // in the telemetry sender) so rows are pruned even when telemetry is disabled // or the build has no stats scheduler. if let Err(e) sqlx::query!(DELETE FROM feature_usage WHERE day CURRENT_DATE - 60) .execute(db) .await { tracing::error!(Error deleting old feature_usage rows: {e}); }仓库在隐私上还有更细的实践。例如 featureUsage.ts 中的logHubScriptPick与hubScriptUsageKey公开 hub 的脚本被 slugify 后记录app/summary而来自私有 hub 的脚本是客户自己的内容因此在PRIVATE_HUB_MIN_VERSION及以上只记录用了私有脚本这个事实key 塌缩为private。同理hubProjectUsageKey只有当 hub 域名与公共 hub 一致时才上报项目 slug并且要求hubBaseUrlKnown已确认——因为 store 默认种入公共 hub 地址一个读不到设置的实例否则会把自家项目名上报出去。六、从后端记录无 UI 的功能怎么埋没有 UI 的功能用同样的方式从 Rust 记录windmill_common::feature_usage::log_feature_usage(trigger, fired, kind.as_str());同一个注册表、同样的 key 规则、同样的未注册即静默丢弃feature和kind是static str调用点无法传入计算出的组合——这是编译期对封闭词汇表的强制该调用只递增一个内存计数器然后返回由 monitor 循环统一 flush 累加器因此对热路径足够便宜——但只是单次调用便宜不是免费一个基数无限的 key 会让 map 不断增长直到撞上 per-action 上限并开始丢弃新 key这条路径没有entity_id、没有显式value——它只数发生了多少次。feature_usage_ee持有注册表与写入器公共构建拿到的是惰性的feature_usage_oss见 backend/windmill-common/src/feature_usage_oss.rs因为 CE 实例从不发送 stats 载荷。这也解释了为何 flush 循环在 backend/src/monitor.rs 中不按 server_mode 门控feature-usage 计数器在任何一个被埋点的调用点都会累加一个从不 flush 的 worker 会在关停时丢掉计数所以无论什么模式都要 flush。仓库里现成的后端埋点调用点可以当模板参考例如backend/windmill-api-debug/src/lib.rs 在调试会话创建时记录(debugger, session, lang_key)backend/windmill-api-workspaces/src/datatable_migrations.rs 记录(datatable, migration_run | migration_rollback | migrations_toggled | migration_created, ...)并且特意不在未变更的重新推送上计数以免wmill sync push每次都同步全部迁移而淹没有意义的计数backend/windmill-api/src/ai_evals/run.rs 与 backend/windmill-api/src/ai_evals/datasets.rs 记录(ai_agent_eval, run | dataset_created, ...)其中 run 的 key 表达被测的是哪个状态的 agent——部署版本、编辑中的改动还是旧版本。七、端到端数据流从一次点击到匿名统计载荷把整条链路串起来看一次前端埋点事件的生命周期是调用点logFeatureUsage(flow_editor, panel_placement, { key: force_detach })前端或log_feature_usage(trigger, fired, kind)Rust本地聚合前端按(workspace, feature, kind, key, entityId)在内存 Map 里累加value最多攒 30 秒 / 50 条Rust 端则递增内存计数器等待 monitor 循环 flush传输前端用带keepalive的裸fetchPOST 到/w/{workspace}/workspaces/log_feature_usage批次失败直接丢弃Rust 端由 backend/src/monitor.rs 的循环调用flush_feature_usage写入数据库注册表过滤is_recordable_event校验(feature, kind)是否在FEATURE_USAGE_KINDS白名单内未注册即静默丢弃无日志、前端照样 204落库按(feature, kind, key, entity_id, day)主键 UPSERT 累加汇总与清理聚合最近 30 天数据进匿名 usage-stats 载荷含entity_count/median_value/p90_value等分布统计60 天前的行由 monitor 循环按day索引删除。值得重复一遍的三条实操铁律新埋点必须在 plan 阶段带上完整词汇表key 词汇用 TS union 封闭在调用点旁边每一步都必须用查库有行来验证而不是没有报错——因为整套机制对未注册组合是默认静默的。最后采集只存在于--features enterprise,private构建中公共构建的 feature_usage_oss.rs 是彻底的空操作。【免费下载链接】windmillOpen-source developer platform to power your entire infra and turn scripts into webhooks, workflows and UIs. Fastest workflow engine (13x vs Airflow). Open-source alternative to Retool and Temporal.项目地址: https://gitcode.com/GitHub_Trending/wi/windmill创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价