Storybook 崩溃报告(enableCrashReports)配置指南:遥测事件中的错误上报与隐私清理机制
【免费下载链接】storybookStorybook is the industry standard workshop for building, documenting, and testing UI components in isolation项目地址: https://gitcode.com/GitHub_Trending/st/storybook
导读
Storybook 的遥测(Telemetry)系统会在命令执行、版本检测等场景收集完全匿名的使用数据,而崩溃报告(Crash Reports)则是其中默认关闭的一项增强能力:开启后,Storybook 会把运行过程中抛出的错误对象做脱敏处理(移除所有用户本地路径)后附加到遥测事件中,帮助维护者定位框架与构建链路中的真实故障。本文以仓库文档 docs/configure/telemetry.mdx 的 "Crash reports (disabled by default)" 一节为骨架,完整讲解三种启用方式(main.js|ts配置、CLI 标志、环境变量)、上报事件的字段结构,并结合 withTelemetry.ts 与 sanitize.ts 的源码剖析错误等级决策链与路径脱敏的实现细节。读完本文,你将能准确配置并验证 Storybook 的崩溃上报,同时理解其隐私边界。
一、崩溃报告是什么:在匿名遥测之上的可选增强
Storybook 会收集完全匿名的使用数据用于改善产品体验,包括:命令调用(如init、upgrade、dev、build)、Storybook 版本、Story 数量、渲染层(React/Vue 3/Angular/Svelte)、构建器(Webpack5/Vite)、元框架(Next/Gatsby/CRA)、Addons、包管理器与 Monorepo 信息等,详见 docs/configure/telemetry.mdx。
崩溃报告则更进一步:当 Storybook 运行出错时,将清洗后的错误对象(包含错误堆栈与消息)随遥测事件一并发送。关键点在于:
- 默认关闭:普通用户不启用时,错误事件中不会携带完整错误对象;
- 主动开启:必须显式通过配置、CLI 标志或环境变量打开;
- 强制脱敏:即便开启,错误中的用户本地绝对路径也会被替换为
$SNIP占位符,防止敏感路径泄漏。
官方文档对开启后的行为描述是:"Storybook will then sanitize the error object (removing all user paths) and append it to the telemetry event"——即"清洗错误对象(移除所有用户路径)并附加到遥测事件"。
二、三种启用方式(完整配置示例)
方式一:在main.js|ts中设置core.enableCrashReports
在.storybook/main.js或.storybook/main.ts的core配置段中设置enableCrashReports: true即可。以下代码完整来自 docs/_snippets/storybook-telemetry-main-enable-crash-reports.md,覆盖了 CSF 3 与 CSF Next 两种编写范式。
CSF 3(main.js,通用渲染层):
export default { // Replace your-framework with the framework you are using, e.g. react-vite, nextjs, vue3-vite, etc. framework: '@storybook/your-framework', stories: ['../src/**/*.mdx', '../src/**/*.stories.@(js|jsx|mjs|ts|tsx)'], core: { enableCrashReports: true, // 👈 Appends the crash reports to the telemetry events }, };CSF 3(main.ts,通用渲染层):
// Replace your-framework with the framework you are using, e.g. react-vite, nextjs, vue3-vite, etc. import type { StorybookConfig } from '@storybook/your-framework'; const config: StorybookConfig = { framework: '@storybook/your-framework', stories: ['../src/**/*.mdx', '../src/**/*.stories.@(js|jsx|mjs|ts|tsx)'], core: { enableCrashReports: true, // 👈 Appends the crash reports to the telemetry events }, }; export default config;CSF Next(main.ts,React):借助defineMain获得类型提示与校验:
// Replace your-framework with the framework you are using (e.g., react-vite, nextjs, nextjs-vite) import { defineMain } from '@storybook/your-framework/node'; export default defineMain({ framework: '@storybook/your-framework', stories: ['../src/**/*.mdx', '../src/**/*.stories.@(js|jsx|mjs|ts|tsx)'], core: { enableCrashReports: true, // 👈 Appends the crash reports to the telemetry events }, });CSF Next(main.js,React):
// Replace your-framework with the framework you are using (e.g., react-vite, nextjs, nextjs-vite) import { defineMain } from '@storybook/your-framework/node'; export default defineMain({ framework: '@storybook/your-framework', stories: ['../src/**/*.mdx', '../src/**/*.stories.@(js|jsx|mjs|ts|tsx)'], core: { enableCrashReports: true, // 👈 Appends the crash reports to the telemetry events }, });CSF Next(main.ts,Angular):
import { defineMain } from '@storybook/angular/node'; export default defineMain({ framework: '@storybook/angular', stories: ['../src/**/*.mdx', '../src/**/*.stories.@(js|jsx|mjs|ts|tsx)'], core: { enableCrashReports: true, // 👈 Appends the crash reports to the telemetry events }, });CSF Next(main.ts/main.js,Web Components):
import { defineMain } from '@storybook/web-components-vite/node'; export default defineMain({ framework: '@storybook/web-components-vite', stories: ['../src/**/*.mdx', '../src/**/*.stories.@(js|jsx|mjs|ts|tsx)'], core: { enableCrashReports: true, // 👈 Appends the crash reports to the telemetry events }, });import { defineMain } from '@storybook/web-components-vite/node'; export default defineMain({ framework: '@storybook/web-components-vite', stories: ['../src/**/*.mdx', '../src/**/*.stories.@(js|jsx|mjs|ts|tsx)'], core: { enableCrashReports: true, // 👈 Appends the crash reports to the telemetry events }, });说明:
core.enableCrashReports的类型为boolean,属于core配置段(该段还包含disableTelemetry、builder、disableWebpackDefaults等内部特性开关),完整类型定义与相邻配置可参考 docs/api/main-config/main-config-core.mdx。
方式二:命令行--enable-crash-reports标志
不修改配置文件,直接在 CLI 传入--enable-crash-reports即可,按包管理器区分写法(来源 docs/_snippets/storybook-telemetry-storybook-enable-crash-reports-flag.md):
npm run storybook -- --enable-crash-reportspnpm run storybook --enable-crash-reportsyarn storybook --enable-crash-reports该标志在 CLI 选项层面对应code/lib/cli-storybook/src/bin/run.ts#L56中的定义:
.option('--enable-crash-reports', 'Enable sending crash reports to telemetry data')从 docs/api/cli-options.mdx 的选项清单可以看到,--enable-crash-reports并非dev独有,而是几乎覆盖全部 CLI 命令,包括storybook dev、storybook build、storybook init、storybook remove、storybook upgrade、storybook automigrate、storybook sandbox以及create storybook,文档中给出的典型用法示例为storybook dev --enable-crash-reports。这意味着从项目初始化到日常开发、构建、升级的各个环节都可以开启崩溃上报。
方式三:STORYBOOK_ENABLE_CRASH_REPORTS环境变量
设置环境变量为1也能开启(来源 docs/_snippets/storybook-telemetry-storybook-enable-crash-reports-env.md):
STORYBOOK_ENABLE_CRASH_REPORTS=1 yarn storybook三种方式并非互斥。在 common-preset.ts 的corepreset 中可以看到它们的合并逻辑——CLI 选项与环境变量通过「或」运算合并进最终配置:
export const core = async (existing: CoreConfig, options: Options): Promise<CoreConfig> => ({ ...existing, channelOptions: { ...(existing?.channelOptions ?? {}), ...(options.configType === 'DEVELOPMENT' ? { wsToken: getWsToken() } : {}), }, disableTelemetry: options.disableTelemetry || optionalEnvToBoolean(process.env.STORYBOOK_DISABLE_TELEMETRY), enableCrashReports: options.enableCrashReports || optionalEnvToBoolean(process.env.STORYBOOK_ENABLE_CRASH_REPORTS), });即:enableCrashReports = 配置文件中 core.enableCrashReports || CLI 标志 || 环境变量,只要任一途径为真即生效,这与文档"Enabling any of the options"的描述一致。
三、开启后上报什么:崩溃报告事件的结构
文档明确指出,开启任意一种方式后,遥测事件中会出现如下字段(完整示例见 docs/_snippets/storybook-telemetry-crash-report-event.md):
{ stack: 'Error: Your button is not working\n' + ' at Object.<anonymous> ($SNIP/test.js:39:27)\n' + ' at Module._compile (node:internal/modules/cjs/loader:1103:14)\n' + ' at Object.Module._extensions..js (node:internal/modules/cjs/loader:1157:10)\n' + ' at Module.load (node:internal/modules/cjs/loader:981:32)\n' + ' at Function.Module._load (node:internal/modules/cjs/loader:822:12)\n' + ' at Function.executeUserEntryPoint [as runMain] (node:internal/modules/run_main:77:12)\n' + ' at node:internal/main/run_main_module:17:47', message: 'Your button is not working' }注意示例中堆栈首行出现的是$SNIP/test.js:39:27而非真实的/Users/xxx/storybook-app/test.js:39:27——这正是「移除所有用户路径」脱敏处理的结果。该事件通常作为error类型遥测事件的payload.error字段被附加,同时伴随code、name、category、eventType、errorHash(对错误消息做单向哈希)等诊断信息,详见 withTelemetry.ts 中sendTelemetryError的组装逻辑。
四、隐私边界:$SNIP路径脱敏的源码实现
崩溃报告能放心开启,核心依赖脱敏机制。其实现位于 code/core/src/telemetry/sanitize.ts:
cleanPaths(str, separator):遍历「当前工作目录process.cwd()」与「用户主目录os.homedir()」两个基准路径,并兼容/、\与平台分隔符,把字符串中出现的用户专属文件系统路径全部替换为$SNIP。该文件的注释给出直观示例:/Users/username/storybook-app/src/pages/index.js→$SNIP/src/pages/index.js;removeAnsiEscapeCodes(input):剥离终端 ANSI 颜色转义码,避免日志污染;sanitizeError(error):对错误对象递归清洗message与stack。
配套测试 code/core/src/telemetry/sanitize.test.ts 验证了关键行为:清洗后堆栈不包含当前工作目录字符串、用户主目录片段会被替换、pnpm store 与 yarn berry 缓存这类位于 home 下的路径同样会被清理。测试断言中expect(sanitizedError.stack).toEqual(expect.not.stringContaining(mockCwd))直接印证了"用户路径不出库"的承诺。
在发送链路 code/core/src/telemetry/index.ts#L185-L209 中,错误对象在进入 payload 前统一经过sanitizeError;仅当enableCrashReports为真时,metadataError与完整payload.error才会被保留并发送,否则只发送脱敏后的消息摘要:
} catch (error: any) { payload.metadataErrorMessage = sanitizeError(error).message; if (options?.enableCrashReports) { payload.metadataError = sanitizeError(error); } } finally { const { error } = payload; // make sure to anonymise possible paths from error messages if (error) { payload.error = sanitizeError(error); } if (!payload.error || options?.enableCrashReports) { // ... 发送 telemetryData } }五、底层原理:错误等级决策链(none / error / full)
崩溃报告开关最终作用于错误上报的"完整度等级"。在 withTelemetry.ts 的getErrorLevel中,决策优先级如下:
- CLI 显式禁用遥测(
cliOptions.disableTelemetry)→ 返回'none',完全不上报; - 加载 presets 后读取
core配置:若core.enableCrashReports明确为true→'full'(携带完整脱敏错误);明确为false→'error'(仅上报错误元信息);若core.disableTelemetry为真 →'none'; - 读取缓存(
cache.get('enableCrashReports'),兼容旧版拼写enableCrashreports)→ 有值则按 true/false 返回'full'/'error'; - 交互式询问(
promptCrashReports):在非 CI 且终端为 TTY 时弹出确认框,文案为"Would you like to send anonymous crash reports to improve Storybook and fix bugs faster?",默认值为true,选择结果会写入缓存; - 兜底:其余情况返回
'full'。
sendTelemetryError随后据此决定是否携带错误对象并强制上报(enableCrashReports: errorLevel === 'full'、force: true,后者用于绕过全局遥测禁用态):
await telemetry( 'error', { code, name, category, eventType, blocking, precedingUpgrade, error: errorLevel === 'full' ? error : undefined, errorHash, isErrorInstance: error instanceof Error, ...(parent ? { parent: parent.fullErrorCode } : {}), }, { immediate: true, configDir: options.cliOptions.configDir || options.presetOptions?.configDir, enableCrashReports: errorLevel === 'full', force: true, } );此外,整个命令运行被withTelemetry包装(withTelemetry.ts):开始时先发送不含元数据的boot事件,通过onPayloadError注册全局错误钩子,命令抛出未处理异常时自动进入sendTelemetryError,并区分HandledError/StorybookError等已知错误与意外错误;对SIGINT中断(如 Ctrl+C)则记录canceled事件而非崩溃报告。这些设计保证了错误上报只覆盖真实故障,而非用户主动中断。
六、调试与验证:STORYBOOK_TELEMETRY_DEBUG
若想确认上报内容与脱敏效果,可设置STORYBOOK_TELEMETRY_DEBUG=1,Storybook 会在发送前把完整的遥测 payload 打印到终端(见 code/core/src/telemetry/index.ts#L202-L207 与 docs/configure/telemetry.mdx)。输出的 JSON 中除了anonymousId(安装时生成的一次性哈希)、eventType、context(platform、nodeVersion、storybookVersion 等),还会包含payload与metadata(包管理器、Monorepo、framework、addons 等),方便核对:
{ "anonymousId": "8bcfdfd5f9616a1923dd92adf89714331b2d18693c722e05152a47f8093392bb", "eventType": "dev", "context": { "isTTY": true, "platform": "macOS", "nodeVersion": "24.11.0", "storybookVersion": "10.3.0-alpha.9" }, "payload": { "versionStatus": "cached", "storyIndex": { "storyCount": 0, "componentCount": 0 } }, "metadata": { "packageManager": { "type": "yarn", "version": "3.1.1" }, "framework": { "name": "@storybook/react-vite" } } }启用崩溃报告后再复现一个错误,即可在调试输出中看到形如第三节所示的payload.error(堆栈中所有本地路径均已变为$SNIP)。
七、与disableTelemetry的配合关系
崩溃报告与遥测总开关是两套独立机制,需区分对待:
core.disableTelemetry: true、--disable-telemetry或STORYBOOK_DISABLE_TELEMETRY=1会整体关闭遥测,此时错误事件连元数据都不会发送(getErrorLevel直接返回'none');core.enableCrashReports: true仅在遥测开启的前提下,决定错误事件是否携带完整脱敏错误对象;- 因此两者同时配置时,以禁用为准——先有遥测,才有崩溃报告可言。
关于遥测关闭的完整说明见 docs/configure/telemetry.mdx 与 main-config-core.mdx 中的disableTelemetry一节。另需注意:boot事件在评估main.js|ts之前就已发送,不读取配置文件中的开关,若希望连该事件也不发送,只能使用STORYBOOK_DISABLE_TELEMETRY环境变量(这是文档中的明确提示)。
小结
崩溃报告是 Storybook 遥测体系中默认关闭、按需开启的排障利器:
- 开启三途径:
core.enableCrashReports: true(main 配置)、--enable-crash-reports(CLI 标志)、STORYBOOK_ENABLE_CRASH_REPORTS=1(环境变量),三者取「或」合并,且 CLI 标志覆盖 dev/build/init/upgrade 等全部主要命令; - 上报内容:脱敏后的
message+stack(事件示例),用户本地路径一律替换为$SNIP; - 实现机制:
getErrorLevel按 CLI → main 配置 → 缓存 → 交互提示的优先级裁决none/error/full(withTelemetry.ts),sanitizeError/cleanPaths保证隐私边界(sanitize.ts); - 验证手段:
STORYBOOK_TELEMETRY_DEBUG=1可打印完整 payload 供核对。
如需为团队统一开启崩溃上报,推荐在.storybook/main.js|ts的core段写入enableCrashReports: true并提交到版本库;若只需临时排查单次命令故障,--enable-crash-reports或环境变量则更加轻量、无需改动配置文件。
【免费下载链接】storybookStorybook is the industry standard workshop for building, documenting, and testing UI components in isolation项目地址: https://gitcode.com/GitHub_Trending/st/storybook
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考