首页
/ Strapi 服务端遥测(Telemetry)全解析:事件上报链路、命名规范与限流机制

Strapi 服务端遥测(Telemetry)全解析:事件上报链路、命名规范与限流机制

2026-09-06 11:52:44作者:冯梦姬Eddie

本文以 Strapi monorepo 中的核心文档 docs/docs/docs/01-core/strapi/telemetry.md 为主线,完整讲解 Strapi 服务端遥测的实现机制:strapi.telemetry.send 的调用链路、事件命名与载荷(Payload)规范、各包中事件的发射位置,以及防止埋点“打爆”分析后端的五类流量控制策略。读完后你可以理解每个埋点事件从哪里产生、如何被组装与限流、何时会静默失效,并掌握为 Strapi 新增一个符合规范的 server-side 埋点所需的完整清单。

架构总览:从事件发射到 analytics 后端

Strapi 通过收集匿名使用数据来理解功能采纳情况并改进产品。服务端遥测的完整链路如下:

┌─────────────────────────────────────────────────────────────────┐
│  Feature code (controller, service, bootstrap, CLI, cron)       │
│       strapi.telemetry.send('didSomething', { ...payload })     │
└────────────────────────────┬────────────────────────────────────┘
                             │
┌────────────────────────────▼────────────────────────────────────┐
│  packages/core/core/src/services/metrics/                       │
│    index.ts        → createTelemetryInstance, LIMITED_EVENTS    │
│    sender.ts       → POST body assembly + strapi.fetch()        │
│    rate-limiter.ts → once-per-24h dedup for selected events    │
│    middleware.ts   → didReceiveRequest (REST, capped at 1000/d) │
└────────────────────────────┬────────────────────────────────────┘
                             │
                             ▼
              https://analytics.strapi.io/api/v2/track
              (override: STRAPI_ANALYTICS_URL)

四个关键文件各司其职:

  • index.ts — 工厂函数,暴露 send()register()isDisabled,并定义限流事件白名单 LIMITED_EVENTS
  • sender.ts — 组装 POST 请求体,通过 strapi.fetch() 发送;
  • rate-limiter.ts — 对选定事件做“每 24 小时最多一次”的去重;
  • middleware.ts — 为 REST 请求发射 didReceiveRequest,单进程上限 1000 次/天。

需要注意边界:Admin 面板(浏览器端)事件走的是另一条通道(React useTracking() 钩子),本文只覆盖 server-side 模式;MCP 及其他仅存在于服务端的功能也应使用下文的 server-side 模式。

注册:telemetry provider 如何接入容器

遥测通过 telemetry provider 挂载到 Strapi 容器,注册链路见 telemetry.ts

export default defineProvider({
  init(strapi) {
    strapi.add('telemetry', () => createTelemetry(strapi));
  },
  async register(strapi) {
    strapi.get('telemetry').register();
  },
  // bootstrap / destroy 同理透传
});

register() 阶段(前提是遥测未被禁用)会做两件事,对应 index.ts 中的源码:

  1. 每日 cron 心跳:注册 sendPingEvent 定时任务,cron 表达式为 0 0 12 * * *(每天中午 12 点发送 ping 事件);
  2. REST 请求中间件strapi.server.use(createMiddleware({ sendEvent, strapi })),用于发射 didReceiveRequest(限流细节见下文“流量控制”一节)。

发送一个事件

业务代码中的标准调用方式是:

strapi.telemetry.send('didCreateContentType', {
  eventProperties: { kind: 'collectionType' },
  userProperties: {
    /* optional, per-user */
  },
  groupProperties: {
    /* optional, project-level */
  },
});

实现位于 sender.ts。每个事件就是一次 HTTP POST(无批处理 API),请求体字段如下:

字段 用途
event 事件名(同时作为 X-Strapi-Event 请求头发送)
userId 可用时的哈希化管理员用户(generateAdminUserHash
installId 由项目 uuid + package.json 生成的稳定安装标识
eventProperties 该次行为特有的上下文
userProperties 用户/环境特征(OS、Node 版本等)
groupProperties 项目特征(Strapi 版本、DB client、EE plan、各类计数等)

sender 的源码级细节

阅读 sender.ts 可以看到若干文档层面没有展开但直接影响行为的关键实现:

  • 默认元数据自动合并anonymousGroupProperties 自动附带 dockerprocess.env.DOCKERis-docker 检测结果)、isCIci-info)、versionuseTypescriptOnServer / useTypescriptOnAdmin(懒加载 @strapi/typescript-utils 判断)、projectIdisHostedOnStrapiCloudSTRAPI_HOSTING === 'strapi.cloud');并额外注入 projectType: strapi.EE ? 'Enterprise' : 'Community'package.jsonstrapi 键下的字段也会经 _.defaults 合并进 groupProperties
  • EE 字段条件注入:仅当 strapi.ee.subscriptionId / strapi.ee.planPriceId 非空时才写入 groupProperties
  • installId 生成:调用 install-id.ts 中的 generateInstallId(uuid, installIdFromPackageJson) —— 若 package.json 已有 strapi.installId 则直接复用;否则以 node-machine-id 取机器标识,与 uuid 拼接后做 SHA-256;任一环节失败则回退为 crypto.randomUUID()
  • userProperties 只在存在 userId 时才发送userId ? { ...anonymousUserProperties, ...payload.userProperties } : {}),保证匿名事件不带用户属性;
  • 网络与容错:默认 timeout: 1000(fetch 超时 1 秒);目标地址为 env('STRAPI_ANALYTICS_URL', 'https://analytics.strapi.io') + '/api/v2/track',支持用环境变量覆盖端点(本地调试或自建收集器时可用);整个发送逻辑包在 try/catch 中,失败仅返回 false——遥测绝不允许破坏应用行为,调用方通常还会追加 .catch(() => {}) 或干脆不 await。

何时遥测处于禁用状态

index.ts 判定,满足任意一条即视为禁用:

const isDisabled =
  !uuid || isTruthy(process.env.STRAPI_TELEMETRY_DISABLED) || isTruthy(telemetryDisabled);
  • 配置中没有项目 uuid(典型场景:fresh clone、首次运行之前);
  • 环境变量 STRAPI_TELEMETRY_DISABLED 为真值(1true 等);
  • package.jsonstrapi.telemetryDisabled: true

禁用时的行为特征:

  • strapi.telemetry.isDisabled 返回该状态;暴露遥测数据的 admin 路由会走 admin::isTelemetryEnabled 策略;
  • sender 对象根本不会被创建(isDisabled ? null : createSender(strapi)),send() 直接返回 true 空转——这是源码注释中明确的设计意图:关闭时连 sender 及其 @strapi/typescript-utils 消费方都跳过;
  • 因此业务代码无需在发送前额外判断禁用状态(除非你在此之前要做昂贵的指标计算)。

另外,仓库提供了 strapi telemetry:disable CLI 命令来落盘禁用配置,执行时会发出 didOptOutTelemetry 事件(见下文 CLI 事件表)。

事件命名规范

服务端事件遵循统一词汇表,事件名是扁平的 camelCase 字符串,没有命名空间前缀(例如不存在 mcp.didCallTool 这种形式):

模式 含义 示例
did* 已完成的动作 didCreateContentTypedidInviteUser
didNot* 失败或中断的动作 didNotCreateContentTypedidNotOpenTab
didCreateFirst* 项目生命周期内首次发生 didCreateFirstAdmindidCreateFirstContentType
didInitialize* 插件/功能 bootstrap 快照 didInitializeI18ndidInitializePluginUpload
did*ProcessStart/Finish/Fail 长任务生命周期 didDEITSProcessStart(数据导出/导入/传输)
didSend*OnceAWeek 每周聚合快照 didSendUploadPropertiesOnceAWeek
ping 心跳 每日 cron
will* 意图表达(主要出现在 admin 前端,服务端少见)

Payload 规范

eventProperties:行为上下文

只放“这次动作特有”的信息。代码库中的典型字段:

  • kind — 创建 content type 时的类型种类;
  • model — 首个 entry 创建时的 content type UID;
  • urlsuccessstatusCode — REST 中间件;
  • sourcedestination — 数据传输 provider;
  • error — 失败信息。

隐私取向:优先类别(category)和计数(count),而不是标识符。存量事件中确有包含 UID 的(modelworkflowId),但文档明确要求新特性在“类别足以表达”时避免重复用户级或内容级名称。

userProperties:用户/环境特征

  • 服务端自动附带:environmentososPlatformosArchosReleasenodeVersion(见 sender 中的 anonymousUserProperties);
  • 显式传入示例:languagesInUsedidChangeInterfaceLanguage);
  • 部分事件显式匿名(空 userId),如 didStartServerdidChangeInterfaceLanguage

groupProperties:项目/安装级状态

通常是计数或配置:

  • numberOfAllContentTypesnumberOfComponentsdatabaseplugins — 启动事件;
  • numberOfLocales — i18n;
  • uploadProviderprivateProvider — upload 初始化;
  • numberOfActiveAdminUsers — admin cron;
  • 上传 / 审核工作流的周聚合指标对象。

启动事件 didStartServergroupProperties 可以在 Strapi.ts 中逐项核对:database.connection.clientplugins 键列表、numberOfAllContentTypesnumberOfComponentsnumberOfDynamicZonesnumberOfConditionalFieldsnumberOfCustomControllersenvironment

服务端事件的发射位置

Core / 启动阶段

事件 位置 说明
didStartServer Strapi.tssendStartupTelemetry() 每次进程启动触发一次;groupProperties 内容丰富
didOpenTab / didNotOpenTab Strapi.tsopenAdmin() 仅开发环境自动打开浏览器时
ping metrics register() 的 cron 每天中午
didReceiveRequest middleware.ts 仅 REST API;不覆盖 MCP/GraphQL/admin

其中 didStartServerpostListen() 中被触发(服务器监听成功、打印启动日志之后),且刻意不 await 以避免拖慢启动。didOpenTab/didNotOpenTab 成对出现,用于对比“自动打开浏览器”的成功率。

Admin(packages/core/admin/server

事件 触发点
didCreateFirstAdmin 首个管理员注册
didInviteUser 用户邀请
didUpdateRolePermissions 角色权限保存
didChangeInterfaceLanguage 界面语言偏好变更
didUpdateProjectInformation bootstrap + 每日午夜 cron
didUpdateSSOSettings EE SSO 设置(EE)
didWatchAnAuditLog EE 审计日志查看(EE)

metrics 服务:metrics.ts

Content-type builder

事件 触发点
didCreateFirstContentType / didCreateContentType 创建 content type(_.isEmpty(strapi.apis) 判定为“首次”)
didNotCreateContentType 创建失败
didCreateFirstComponent / didCreateComponent 创建 component(以 registry 大小判定首次)

Content manager

事件 触发点
didCreateFirstContentTypeEntry 集合类型中的首个文档(totalEntries === 0
didConfigureListView 保存列表视图配置

metrics 服务:metrics.ts

Content releases

didCreateContentReleasedidUpdateContentReleasedidDeleteContentReleasedidPublishContentRelease — 位于 release.ts

Review workflows(EE)

  • 单动作事件:didCreateStagedidEditStagedidDeleteStagedidChangeEntryStagedidCreateWorkflowdidEditWorkflowdidEditAssignee
  • 周聚合事件:didSendReviewWorkflowPropertiesOnceAWeek,由 weekly-metrics.ts 中的 cron 发送。

Upload

事件 触发点
didInitializePluginUpload 插件 bootstrap(每日限流)
didSaveMediaWithCaption / didSaveMediaWithAlternativeText 媒体保存(每日限流)
didEnableResponsiveDimensions / didDisableResponsiveDimensions 设置变更(每日限流)
didUploadImage 图片上传
didBulkDeleteMediaLibraryElements / didBulkMoveMediaLibraryElements 批量操作
didSendUploadPropertiesOnceAWeek 每周 cron 聚合

metrics 封装:metrics.tstrackUsage()

i18n

事件 触发点
didInitializeI18n 插件 bootstrap
didUpdateI18nLocales Locale 增删改

metrics 服务:metrics.ts

CLI(内嵌 Strapi 或独立运行)

事件 上下文
didDEITSProcessStart / didDEITSProcessFinish / didDEITSProcessFail 数据导出(Export)、导入(Import)、传输(Transfer)
didOptOutTelemetry strapi telemetry:disable
will* / did* 项目创建事件 packages/cli/create-strapi-app
Cloud CLI 事件 经 Cloud API 代理(packages/cli/cloud

MCP(core)

MCP 是当前仓库中埋点设计最精细的模块,事件全部定义在 metrics.ts

事件 触发点
didStartMcpServer server.mcp.enabled: true 时 MCP 服务器启动(eventProperties.pathgroupProperties.numberOfTools / numberOfPrompts / numberOfResources;每日限流)
didUseMcpServer 认证通过的 MCP HTTP 请求(handlePost.ts;每日限流)
didNotAuthenticateMcpRequest 处理前被拒绝(eventProperties.errorClass: missing_token | invalid_token;每日限流)
didNotHandleMcpRequest 已认证请求在 connect/handle 阶段失败(eventProperties.errorClass: timeout | error;每日限流)
didExecuteMcpCapability 能力执行成功(eventProperties.type: tool | prompt | resource;另含 actionsource
didNotExecuteMcpCapability 能力返回 isError: true(同上结构 + errorClass: execution_error

两个值得注意的实现决策:

  1. 能力名归一化:工具/提示词/资源名被归一化为粗粒度的 action 值(见 normalizeMcpCapability.ts),绝不使用 content type slug 等高基数字面量;
  2. 双层去重:请求级事件走核心 rate limiter(事件名维度);didExecuteMcpCapability / didNotExecuteMcpCapability 则按 type + action + source 组合在 metrics.ts 内本地去重——这验证了后文“按类别先去重、再发送”的流量策略。对应测试见 metrics.test.ts

对比 didStartMcpServerdidUseMcpServer 两个事件,可以识别出“启用了 MCP 但从未使用”的安装实例;prompt/resource 的执行埋点在归一化规则存在时激活。

包级 metrics 服务模式

多个包不把 strapi.telemetry.send 直接写在 controller 里,而是封装在专用的 metrics 服务中(仓库中均已确认存在):

packages/core/admin/server/src/services/metrics.ts
packages/core/content-manager/server/src/services/metrics.ts
packages/core/upload/server/src/services/metrics.ts
packages/core/core/src/services/mcp/metrics/metrics.ts
packages/plugins/i18n/server/src/services/metrics.ts
packages/core/review-workflows/server/src/services/metrics/index.ts
packages/core/admin/ee/server/src/services/metrics.ts

模式:薄服务 + sendDid…() 系列 helper;controller/service 只调用 metrics 服务,不直接触碰 strapi.telemetry。测试相应地 mock strapi.telemetry.send 或 mock 整个 metrics 服务。

流量控制(Volume Controls)

没有批处理 API —— 每个事件都是一次独立 HTTP 请求。Strapi 用五种互补策略控制总量:

1. 每日限流器(按事件名)

rate-limiter.ts 将 sender 包装一层,只对 LIMITED_EVENTS 中列出的事件生效。当前白名单(见 index.ts):

const LIMITED_EVENTS = [
  'didSaveMediaWithAlternativeText',
  'didSaveMediaWithCaption',
  'didDisableResponsiveDimensions',
  'didEnableResponsiveDimensions',
  'didInitializePluginUpload',
  ...Object.values(MCP_LIMITED_TELEMETRY_EVENTS),
];

实现细节:一个 Map 缓存 + 滚动 24 小时窗口(cacheExpiresAt 到期后整体清空再重置)。关键限制:去重维度仅为事件名,不看 eventProperties。如果一个事件名覆盖多个类别(例如 didExecuteMcpCapability 有不同 type/action),必须在调用 strapi.telemetry.send 之前自行按类别去重(参考 MCP metrics.ts),而不是往 LIMITED_EVENTS 里堆一堆名字。

为高频新事件选择限流方案的四种路径:

  • 把其精确事件名加入 LIMITED_EVENTS
  • 每个类别使用独立事件名(各加入 LIMITED_EVENTS);
  • 发送前本地按类别去重(MCP 工具 action 的做法);
  • 使用周聚合(见策略 3)。

2. REST 中间件上限

middleware.tsdidReceiveRequest 每个进程每 24 小时最多 1000 条,滚动窗口到期后计数器归零。匹配条件:

  • URL 必须包含 REST API 前缀(strapi.config.get('api.rest.prefix'));
  • 方法限于 GET / PUT / POST / DELETE
  • URL 含 . 的静态资源被跳过。

计数器在中间件入口处立即自增(防止竞态),实际发送推迟到 ctx.resfinishclose 事件,并按响应状态计算 success(2xx 为成功,无状态码时按 500 记)。发送用 Promise.resolve(...).catch(() => {}) 包裹,静默吞掉一切错误。MCP 请求(/mcp)不经过此中间件

3. 周聚合 cron

对噪声大的领域,本地算好指标、每周只发一条事件

  • Upload:didSendUploadPropertiesOnceAWeek — 文件夹深度统计、资源数量(weekly-metrics.ts + 用 strapi.store 保存调度抖动);
  • Review workflows:didSendReviewWorkflowPropertiesOnceAWeek — workflow/stage 计数。

调度时刻按安装实例随机化并持久化在 strapi.store 中,用于分摊后端压力。

4. “首次”与“后续”分离

不需要持久化 store 的“首用”模式:

  • didCreateFirstContentType:创建前 _.isEmpty(strapi.apis)
  • didCreateFirstComponent:component registry 为空;
  • didCreateFirstAdmin:注册流程中触发;
  • didCreateFirstContentTypeEntrytotalEntries === 0

后续动作统一使用不带 First 的事件名(didCreateContentTypedidCreateComponent 等),两类事件分开统计即可区分首用曲线与使用量曲线。

5. Fire-and-forget / 非阻塞

启动遥测与大量调用点都不 await 发送结果,失败被忽略;fetch 超时默认 1 秒。这条策略保证遥测在任何情况下都不成为业务链路上的阻塞点。

新增服务端事件检查清单

文档给出的 7 步清单可直接作为 PR 自检项:

  1. 命名did* 过去时;该用 didCreateFirst*didInitialize* 时不要偷懒;
  2. 载荷 — 动作上下文进 eventProperties,项目级计数进 groupProperties
  3. 隐私 — 避免 PII、密钥与高基数字符串(content type UID、文档 ID 等),除非确有必要;
  4. 量级 — 可能高频触发的动作,第一天就接入限流或周聚合;
  5. 位置 — 优先放在所属包的 metrics 服务中,在 controller/service 成功后调用;
  6. 禁用守卫strapi.telemetry.send 禁用时自动空转,无需额外判断(除非发送前有昂贵的指标计算,可先查 isDisabled);
  7. 测试 — 单测中 mock strapi.telemetry.send(可参考各包内现成的 __tests__/metrics.test.ts,如 MCP metrics 测试)。

相关文档

  • 采集数据的公开说明(Strapi 官网 Usage information 页面)——说明哪些数据被收集;
  • Admin 前端遥测是独立通道(useTracking()、插件包装器、生命周期事件),新 server-side 功能(如 MCP)应使用 strapi.telemetry.send 而非 React tracking hook;
  • API 参考:Telemetry Service
  • 关键类型定义:sender.ts 中的 PayloadSender
登录后查看全文
热门项目推荐
相关项目推荐