- 可观测性
【免费下载链接】sentry-javascript
Official Sentry SDKs for JavaScript
本文以dev-packages/e2e-tests/test-applications/sveltekit-2-svelte-5/目录下的 README 为骨架,讲清这个基于 create-svelte 脚手架搭建的 SvelteKit 2 + Svelte 5 项目如何创建、开发与构建,并结合仓库源码深入剖析它在 Sentry JavaScript SDK 仓库中的真实角色——作为@sentry/sveltekit包在最新 Svelte 大版本下的端到端(E2E)回归验证目标。读完本文,你既能复现该应用的运行流程,也能理解其 hooks 初始化、Vite 插件、Playwright 测试与事件代理服务器之间的完整链路。
README 的原貌:一个标准的 create-svelte 脚手架项目
该应用的 README.md 本身是 create-svelte 脚手架生成的标准模板,它声明了这是一个由 create-svelte 驱动的 Svelte 项目,并给出三类基础操作命令。
创建项目
README 中保留的原始创建方式为:
# 在当前目录创建新项目 npm create svelte@latest # 在 my-app 目录中创建新项目 npm create svelte@latest my-app不过在本仓库中,这个应用并不是每次用脚手架重新生成的,而是已经以固定形态提交在test-applications/sveltekit-2-svelte-5/目录下。它的技术栈组合可以从 package.json 中精确读出:
| 依赖 | 版本 | 角色 |
|---|---|---|
@sveltejs/kit | 2.69.1 | SvelteKit 2 框架本体(锁定的具体版本) |
svelte | ^5.0.0-next.115 | Svelte 5(next 系列预发布版) |
@sentry/sveltekit | file:../../packed/sentry-sveltekit-packed.tgz | 待验证的 Sentry SDK(本地打包产物) |
@sveltejs/vite-plugin-svelte | ^3.0.0 | Vite 集成插件 |
vite | ^5.4.11 | 构建工具 |
@spotlightjs/spotlight | 2.0.0-alpha.1 | 开发期 trace 可视化 |
@playwright/test | ~1.63.0 | E2E 测试框架 |
值得注意的是@sentry/sveltekit的依赖写法是file:../../packed/sentry-sveltekit-packed.tgz——它指向 monorepo 中预先打包好的 Sentry SvelteKit SDK tarball。这说明该应用验证的不是 npm 上发布的 SDK,而是当前仓库源码构建出的打包产物,是典型的"打包含待测包、安装、跑真实浏览器"的 E2E 回归策略。
开发模式
README 给出的开发流程是:安装依赖后启动开发服务器:
npm run dev # 或启动服务器并在新浏览器标签页中打开应用 npm run dev -- --open对照 package.json 中的 scripts,这里的dev实际映射为vite dev,并且该仓库统一使用 pnpm 管理依赖(clean脚本会删除pnpm-lock.yaml)。完整的脚本清单还包括:
{ "dev": "vite dev", "build": "vite build", "preview": "vite preview", "proxy": "node start-event-proxy.mjs", "clean": "npx rimraf node_modules pnpm-lock.yaml", "check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json", "check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch", "test:prod": "TEST_ENV=production playwright test", "test:build": "pnpm install && pnpm build", "test:assert": "pnpm test:prod" }其中test:build与test:assert揭示了 CI 的使用方式:先在目标应用里执行pnpm install && pnpm build完成安装与生产构建,再以TEST_ENV=production跑 Playwright 断言。
构建与预览
README 中生产构建与预览的命令同样完整继承自模板:
npm run build # 实际执行 vite build # 构建产物可用 preview 预览: npm run previewREADME 末尾还保留了关于部署 adapter 的提示:若要部署应用,可能需要为具体目标环境安装对应的 SvelteKit adapter。这一点在本应用中确实成立——svelte.config.js 使用的是@sveltejs/adapter-auto:
import adapter from '@sveltejs/adapter-auto'; import { vitePreprocess } from '@sveltejs/vite-plugin-svelte'; /** @type {import('@sveltejs/kit').Config} */ const config = { preprocess: vitePreprocess(), kit: { // adapter-auto 只支持部分环境,如果目标环境不被支持, // 需要切换为具体的 adapter adapter: adapter(), }, }; export default config;这也解释了为什么 Playwright 配置里选择vite preview而非部署到某个平台:本地预览即可完整覆盖 SvelteKit 的服务端渲染与 hydration 行为。
目录的真实身份:Sentry SvelteKit SDK 的 Svelte 5 E2E 测试目标
从目录命名(sveltekit-2-svelte-5)与同级目录下并列存在的sveltekit-2、sveltekit-2-static、sveltekit-3、sveltekit-3-cloudflare-workers等应用可以推断,该仓库为@sentry/sveltekit维护了一组覆盖不同 SvelteKit/Svelte 大版本组合的 E2E 应用,而本文主角专门锁定"SvelteKit 2 + Svelte 5"这一前沿组合,用于尽早暴露 Svelte 5 新运行时(runes 模式)下的兼容性问题。
客户端埋点:hooks.client.ts
Sentry 在 SvelteKit 中的推荐接入点是hooks.client.ts与hooks.server.ts。本应用的 hooks.client.ts 完整展示了 SDK 初始化:
import { env } from '$env/dynamic/public'; import * as Sentry from '@sentry/sveltekit'; import * as Spotlight from '@spotlightjs/spotlight'; Sentry.init({ environment: 'qa', // 通过 dynamic sampling bias 保留事务 dsn: env.PUBLIC_E2E_TEST_DSN, debug: !!env.PUBLIC_DEBUG, tunnel: `http://localhost:3031/`, // 代理服务器 tracesSampleRate: 1.0, }); const myErrorHandler = ({ error, event }: any) => { console.error('An error occurred on the client side:', error, event); }; export const handleError = Sentry.handleErrorWithSentry(myErrorHandler); if (import.meta.env.DEV) { Spotlight.init({ injectImmediately: true, }); }几个关键配置值得注意:
dsn来自$env/dynamic/public:客户端代码只能读取PUBLIC_前缀的公开环境变量,即PUBLIC_E2E_TEST_DSN;tunnel指向http://localhost:3031/:事件并不直接发往真实 Sentry 服务器,而是经本地代理转发(后文详述),这使 CI 环境无需公网 DSN;tracesSampleRate: 1.0:全量采样,保证 E2E 断言总能收到性能事件;Sentry.handleErrorWithSentry:把 SvelteKit 的错误处理钩子包装进 Sentry,路由级错误(如 load 抛错)会被自动捕获并上报;- Spotlight:仅在开发模式(
import.meta.env.DEV)注入,用于人工调试 trace,与测试无关。
服务端埋点:hooks.server.ts
hooks.server.ts 与客户端对称,但多了 SvelteKit 特有的请求处理钩子:
import { E2E_TEST_DSN } from '$env/static/private'; import * as Sentry from '@sentry/sveltekit'; import { setupSidecar } from '@spotlightjs/spotlight/sidecar'; Sentry.init({ environment: 'qa', dsn: E2E_TEST_DSN, debug: !!process.env.DEBUG, tunnel: `http://localhost:3031/`, tracesSampleRate: 1.0, spotlight: import.meta.env.DEV, }); // 不向控制台输出,避免测试日志噪音 export const handleError = Sentry.handleErrorWithSentry(() => {}); export const handle = Sentry.sentryHandle(); if (import.meta.env.DEV) { setupSidecar(); }- 服务端 DSN 通过
$env/static/private静态读取(构建期内联,不会暴露到客户端); export const handle = Sentry.sentryHandle()是 SvelteKit 服务端请求处理钩子,负责创建每个请求的 HTTP 事务,并处理 trace 的跨端串联;- 服务端
handleError被有意配置为不打印日志(源码注释说明是为了避免测试输出噪音),错误是否上报完全由 Playwright 侧断言验证。
Vite 插件:sentrySvelteKit
vite.config.ts 展示了 SDK 的构建期集成:
import { sentrySvelteKit } from '@sentry/sveltekit/vite'; import { sveltekit } from '@sveltejs/kit/vite'; import { defineConfig } from 'vite'; export default defineConfig({ plugins: [ sentrySvelteKit({ autoUploadSourceMaps: false, }), sveltekit(), ], });sentrySvelteKit插件来自@sentry/sveltekit/vite子导出,负责 source map 处理与 release/debugId 注入;这里显式设置autoUploadSourceMaps: false,因为 E2E 场景不需要把 source map 上传到 Sentry 服务器。
E2E 测试基础设施:Playwright + 本地事件代理
事件代理服务器
应用目录下的 start-event-proxy.mjs 只有寥寥几行,却解释了tunnel: http://localhost:3031/的来历:
import { startEventProxyServer } from '@sentry-internal/test-utils'; startEventProxyServer({ port: 3031, proxyServerName: 'sveltekit-2-svelte-5', });@sentry-internal/test-utils(对应仓库中的 dev-packages/test-utils 包)提供了一个事件代理:SDK 上报的 error/transaction 事件先打到这个本地服务器,测试代码再按proxyServerName(即'sveltekit-2-svelte-5')订阅、过滤并断言这些事件。package.json中的proxy脚本(node start-event-proxy.mjs)就是启动它的入口。
Playwright 配置
playwright.config.mjs 同样复用 test-utils 的统一工厂:
import { getPlaywrightConfig } from '@sentry-internal/test-utils'; const config = getPlaywrightConfig({ startCommand: 'pnpm preview --port 3030', port: 3030, }); export default config;startCommand: 'pnpm preview --port 3030'与package.json中test:build的先决条件呼应:E2E 断言跑在生产构建 +vite preview(3030 端口)之上,而事件代理监听 3031 端口,两者互不冲突。
稳定性关键:waitForInitialPageload
tests/utils.ts 提供了一个消除测试抖动的核心辅助函数,其注释直接说明了设计动机:
export async function waitForInitialPageload( page: Page, opts?: { route?: string; parameterizedRoute?: string; debug?: boolean }, ) { const route = opts?.route ?? '/'; const spanName = opts?.parameterizedRoute ?? route; const debug = opts?.debug ?? false; const clientPageloadSpanPromise = waitForStreamedSpan('sveltekit-2-svelte-5', span => { return span.name === spanName && getSpanOp(span) === 'pageload' && span.is_segment; }); await Promise.all([ page.goto(route), // 测试应用在 hydration 完成时会向 body 添加 "hydrated" class page.waitForSelector('body.hydrated'), // 同时等待初始 pageload span,避免后续导航与其竞争 clientPageloadSpanPromise, ]); }这里有两个协作点:
hydration 信号:根布局 src/routes/+layout.svelte 在
onMount时给body添加hydratedclass:<script lang="ts"> import { onMount } from 'svelte'; onMount(() => { // 标记 SvelteKit 应用已完成 hydration document.body.classList.add('hydrated'); }); </script>测试端用
page.waitForSelector('body.hydrated')精确等待客户端接管完成。等待 pageload segment span:同时通过代理等待
op === 'pageload'且is_segment的 span 被发送。注释指出,如果导航发生得太快、pageload 空闲 span 仍处于活跃状态,routing span 可能被挂到 pageload span 之下,从而引入大量 flaky;先等 pageload 事件落地再发起导航即可排除这类竞态。
测试覆盖:错误捕获与性能链路
tests 目录下共有 5 个测试文件(errors.client.test.ts、errors.server.test.ts、performance.client.test.ts、performance.server.test.ts、performance.test.ts),与src/routes/下精心布置的"故障路由"一一对应:client-error、server-load-error、server-route-error、universal-load-error、universal-load-fetch、users/[id]等。
客户端错误:errors.client.test.ts
errors.client.test.ts 的第一个用例验证点击抛错的完整上报链路:
test('captures error thrown on click', async ({ page }) => { await waitForInitialPageload(page, { route: '/client-error' }); const errorEventPromise = waitForError('sveltekit-2-svelte-5', errorEvent => { return errorEvent?.exception?.values?.[0]?.value === 'Click Error'; }); await page.getByText('Throw error').click(); await expect(errorEventPromise).resolves.toBeDefined(); const errorEvent = await errorEventPromise; const errorEventFrames = errorEvent.exception?.values?.[0]?.stacktrace?.frames; expect(errorEventFrames?.[errorEventFrames?.length - 1]).toEqual( expect.objectContaining({ function: expect.stringContaining('HTMLButtonElement'), lineno: 1, in_app: true, }), ); expect(errorEvent.transaction).toEqual('/client-error'); });断言覆盖了三个层面:异常值本身(Click Error)、调用栈末帧特征(按钮事件处理器、in_app: true)、以及事件关联的事务名(/client-error)。第二个用例则验证"universal load"中抛出的浏览器侧 load 错误(Universal Load Error (browser))同样被捕获并正确关联事务。
性能:pageload / navigation 与分布式 trace
performance.test.ts 中的用例展示了 SDK 自动埋点应产生的 span 结构。以分布式 pageload trace 为例:
test('capture a distributed pageload trace', async ({ page }) => { const traceSpansPromise = collectStreamedSpans('sveltekit-2-svelte-5', spansOfTrace => { const hasClientSegment = spansOfTrace.some(span => span.name === '/users/[id]' && span.is_segment); const hasServerSegment = spansOfTrace.some(span => span.name === 'GET /users/[id]' && span.is_segment); return hasClientSegment && hasServerSegment; }); const [_, traceSpans] = await Promise.all([ page.goto('/users/123xyz'), traceSpansPromise, expect(page.getByText('User id: 123xyz')).toBeVisible(), ]); const clientSpan = traceSpans.find(span => span.name === '/users/[id]' && span.is_segment)!; const serverSpan = traceSpans.find(span => span.name === 'GET /users/[id]' && span.is_segment)!; expect(clientSpan.attributes).toMatchObject({ 'sentry.op': { value: 'pageload', type: 'string' }, 'sentry.origin': { value: 'auto.pageload.sveltekit', type: 'string' }, 'sentry.segment.name.source': { value: 'route', type: 'string' }, 'url.path': { value: '/users/123xyz', type: 'string' }, 'url.template': { value: '/users/[id]', type: 'string' }, }); expect(serverSpan.attributes).toMatchObject({ 'sentry.op': { value: 'http.server', type: 'string' }, 'sentry.origin': { value: 'auto.http.sveltekit', type: 'string' }, }); // 客户端与服务端 span 同属一条 trace expect(clientSpan.trace_id).toBe(serverSpan.trace_id); // 服务端 span 是客户端 span 的 parent expect(clientSpan.parent_span_id).toBe(serverSpan.span_id); });这个用例对@sentry/sveltekit的行为提出了精确契约:
- 客户端 pageload segment 的 op 必须是
pageload、origin 为auto.pageload.sveltekit,且 span 名采用路由模板/users/[id](sentry.segment.name.source为route); - 服务端 segment 的 op 为
http.server、origin 为auto.http.sveltekit; - 两端
trace_id相同(分布式 trace 连通),且服务端 span 是客户端 span 的父节点——这是 SvelteKit 服务端渲染场景下 SDK 的 trace 组织约定。
同类用例还覆盖了 SPA 导航(navigationop +auto.navigation.sveltekitorigin,见/users路由)以及 universal load 中 fetch 调用派生的服务端 span(GET /api/users),说明测试同时验证了 src/routes/universal-load-fetch/+page.ts 与 src/routes/api/users/+server.ts 构成的跨层调用链。
小结:一个应用承载的完整验证矩阵
| 层面 | 机制 | 关键文件 |
|---|---|---|
| 脚手架与运行 | create-svelte 标准流程,vite dev/vite build/vite preview | README.md、package.json |
| 适配器 | @sveltejs/adapter-auto,本地vite preview验证 | svelte.config.js |
| 客户端埋点 | Sentry.init+handleErrorWithSentry,tunnel 走本地代理 | src/hooks.client.ts |
| 服务端埋点 | sentryHandle()+handleErrorWithSentry | src/hooks.server.ts |
| 构建期集成 | sentrySvelteKitVite 插件(关闭自动上传) | vite.config.ts |
| 事件采集 | 3031 端口事件代理 +proxyServerName过滤 | start-event-proxy.mjs |
| 测试驱动 | Playwright 启动pnpm preview --port 3030 | playwright.config.mjs |
| 稳定性保障 | 等待body.hydrated+ pageload segment 落地 | tests/utils.ts、+layout.svelte |
| 行为断言 | 错误捕获、pageload/navigation/http.server span 契约、跨端 trace 连通 | tests 目录 |
整体来看,这个应用把 create-svelte 模板中最基础的"创建—开发—构建"流程,扩展为一套针对 SvelteKit 2 + Svelte 5 前沿组合的 SDK 回归验证装置:README 描述的是脚手架通用操作,而真正的工程价值在于它锁定了 Svelte 5 版本、打包安装了本地@sentry/sveltekit产物,并用 Playwright + 事件代理对客户端错误、服务端错误、自动性能埋点与跨端 trace 串联做出了可执行的验收标准。
- 可观测性
【免费下载链接】sentry-javascript
Official Sentry SDKs for JavaScript
相关推荐
sentry-javascript SvelteKit 2 Tracing E2E 测试应用:基于 create-svelte 的搭建、构建与 Tracing 验证指南
sentry javascript SvelteKit 2 Tracing E2E 测试应用:基于 create svelte 的搭建、构建与 Tracing
可观测性sentry-javascript 中的 SolidStart E2E 测试应用:如何用 Playwright、Vinxi 与事件隧道验证 @sentry/solidstart SDK
sentry javascript 中的 SolidStart E2E 测试应用:如何用 Playwright、Vinxi 与事件隧道验证 @sentry/so
可观测性sentry-javascript 中 SolidStart 2 E2E 测试应用:从标准 README 到 Sentry 集成验证的完整实操指南
sentry javascript 中 SolidStart 2 E2E 测试应用:从标准 README 到 Sentry 集成验证的完整实操指南 本篇以 de
可观测性
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考