From 36a85a0645b2bb84e068f51876e1298e884fd817 Mon Sep 17 00:00:00 2001 From: Sam Greening <2552620+SG60@users.noreply.github.com> Date: Mon, 9 Dec 2024 13:39:41 +0000 Subject: [PATCH 01/31] fix(sveltekit): Don't use node apis in cloudflare workers Ideally this would use OTEL, but that requires integrating another library, as the first-party OTEL libaries do not support Cloudflare workers. --- packages/sveltekit/package.json | 4 + packages/sveltekit/rollup.npm.config.mjs | 2 +- packages/sveltekit/src/index.types.ts | 4 + packages/sveltekit/src/index.worker.ts | 2 + packages/sveltekit/src/worker/handle.ts | 194 +++++++++++++++++++ packages/sveltekit/src/worker/handleError.ts | 79 ++++++++ packages/sveltekit/src/worker/index.ts | 105 ++++++++++ packages/sveltekit/src/worker/load.ts | 124 ++++++++++++ packages/sveltekit/src/worker/serverRoute.ts | 71 +++++++ packages/sveltekit/src/worker/utils.ts | 71 +++++++ 10 files changed, 655 insertions(+), 1 deletion(-) create mode 100644 packages/sveltekit/src/index.worker.ts create mode 100644 packages/sveltekit/src/worker/handle.ts create mode 100644 packages/sveltekit/src/worker/handleError.ts create mode 100644 packages/sveltekit/src/worker/index.ts create mode 100644 packages/sveltekit/src/worker/load.ts create mode 100644 packages/sveltekit/src/worker/serverRoute.ts create mode 100644 packages/sveltekit/src/worker/utils.ts diff --git a/packages/sveltekit/package.json b/packages/sveltekit/package.json index ba1535d799de..69eb8dea3df6 100644 --- a/packages/sveltekit/package.json +++ b/packages/sveltekit/package.json @@ -20,6 +20,10 @@ "./package.json": "./package.json", ".": { "types": "./build/types/index.types.d.ts", + "worker": { + "import": "./build/esm/index.worker.js", + "require": "./build/cjs/index.worker.js" + }, "browser": { "import": "./build/esm/index.client.js", "require": "./build/cjs/index.client.js" diff --git a/packages/sveltekit/rollup.npm.config.mjs b/packages/sveltekit/rollup.npm.config.mjs index b0a19e091ad8..91a460933251 100644 --- a/packages/sveltekit/rollup.npm.config.mjs +++ b/packages/sveltekit/rollup.npm.config.mjs @@ -2,7 +2,7 @@ import { makeBaseNPMConfig, makeNPMConfigVariants } from '@sentry-internal/rollu export default makeNPMConfigVariants( makeBaseNPMConfig({ - entrypoints: ['src/index.server.ts', 'src/index.client.ts', 'src/client/index.ts', 'src/server/index.ts'], + entrypoints: ['src/index.server.ts', 'src/index.client.ts', 'src/index.worker.ts', 'src/client/index.ts', 'src/server/index.ts', 'src/worker/index.ts'], packageSpecificConfig: { external: ['$app/stores'], output: { diff --git a/packages/sveltekit/src/index.types.ts b/packages/sveltekit/src/index.types.ts index 3ad8b728bb5f..1399ffeede3e 100644 --- a/packages/sveltekit/src/index.types.ts +++ b/packages/sveltekit/src/index.types.ts @@ -4,6 +4,10 @@ export * from './client'; export * from './vite'; export * from './server'; +export * from './worker'; + +// Use the ./server version of some functions that are also exported from ./worker +export { wrapServerLoadWithSentry, wrapServerRouteWithSentry, sentryHandle } from './server'; import type { Client, Integration, Options, StackParser } from '@sentry/core'; import type { HandleClientError, HandleServerError } from '@sveltejs/kit'; diff --git a/packages/sveltekit/src/index.worker.ts b/packages/sveltekit/src/index.worker.ts new file mode 100644 index 000000000000..016e36c8a289 --- /dev/null +++ b/packages/sveltekit/src/index.worker.ts @@ -0,0 +1,2 @@ +export * from './worker'; +// export * from './vite'; diff --git a/packages/sveltekit/src/worker/handle.ts b/packages/sveltekit/src/worker/handle.ts new file mode 100644 index 000000000000..568d421016ee --- /dev/null +++ b/packages/sveltekit/src/worker/handle.ts @@ -0,0 +1,194 @@ +import type { Span } from '@sentry/core'; +import { + SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN, + SEMANTIC_ATTRIBUTE_SENTRY_SOURCE, + getActiveSpan, + getCurrentScope, + getDefaultIsolationScope, + getIsolationScope, + getTraceMetaTags, + logger, + setHttpStatus, + startSpan, + winterCGRequestToRequestData, + withIsolationScope, + continueTrace, +} from '@sentry/core'; +import type { Handle, ResolveOptions } from '@sveltejs/kit'; + +import { DEBUG_BUILD } from '../common/debug-build'; +import { flushIfServerless, getTracePropagationData, sendErrorToSentry } from './utils'; + +export type SentryHandleOptions = { + /** + * Controls whether the SDK should capture errors and traces in requests that don't belong to a + * route defined in your SvelteKit application. + * + * By default, this option is set to `false` to reduce noise (e.g. bots sending random requests to your server). + * + * Set this option to `true` if you want to monitor requests events without a route. This might be useful in certain + * scenarios, for instance if you registered other handlers that handle these requests. + * If you set this option, you might want adjust the the transaction name in the `beforeSendTransaction` + * callback of your server-side `Sentry.init` options. You can also use `beforeSendTransaction` to filter out + * transactions that you still don't want to be sent to Sentry. + * + * @default false + */ + handleUnknownRoutes?: boolean; + + /** + * Controls if `sentryHandle` should inject a script tag into the page that enables instrumentation + * of `fetch` calls in `load` functions. + * + * @default true + */ + injectFetchProxyScript?: boolean; + + /** + * If this option is set, the `sentryHandle` handler will add a nonce attribute to the script + * tag it injects into the page. This script is used to enable instrumentation of `fetch` calls + * in `load` functions. + * + * Use this if your CSP policy blocks the fetch proxy script injected by `sentryHandle`. + */ + fetchProxyScriptNonce?: string; +}; + +/** + * Exported only for testing + */ +export const FETCH_PROXY_SCRIPT = ` + const f = window.fetch; + if(f){ + window._sentryFetchProxy = function(...a){return f(...a)} + window.fetch = function(...a){return window._sentryFetchProxy(...a)} + } +`; + +/** + * Adds Sentry tracing tags to the returned html page. + * Adds Sentry fetch proxy script to the returned html page if enabled in options. + * Also adds a nonce attribute to the script tag if users specified one for CSP. + * + * Exported only for testing + */ +export function addSentryCodeToPage(options: SentryHandleOptions): NonNullable { + const { fetchProxyScriptNonce, injectFetchProxyScript } = options; + // if injectFetchProxyScript is not set, we default to true + const shouldInjectScript = injectFetchProxyScript !== false; + const nonce = fetchProxyScriptNonce ? `nonce="${fetchProxyScriptNonce}"` : ''; + + return ({ html }) => { + const metaTags = getTraceMetaTags(); + const headWithMetaTags = metaTags ? `\n${metaTags}` : ''; + + const headWithFetchScript = shouldInjectScript ? `\n` : ''; + + const modifiedHead = `${headWithMetaTags}${headWithFetchScript}`; + + return html.replace('', modifiedHead); + }; +} + +/** + * A SvelteKit handle function that wraps the request for Sentry error and + * performance monitoring. + * + * This doesn't currently use OTEL, as it isn't available outside of Node + * + * Usage: + * ``` + * // src/hooks.server.ts + * import { sentryHandle } from '@sentry/sveltekit'; + * + * export const handle = sentryHandle(); + * + * // Optionally use the `sequence` function to add additional handlers. + * // export const handle = sequence(sentryHandle(), yourCustomHandler); + * ``` + */ +export function sentryHandle(handlerOptions?: SentryHandleOptions): Handle { + const options = { + handleUnknownRoutes: false, + injectFetchProxyScript: true, + ...handlerOptions, + }; + + const sentryRequestHandler: Handle = input => { + // event.isSubRequest was added in SvelteKit 1.21.0 and we can use it to check + // if we should create a new execution context or not. + // In case of a same-origin `fetch` call within a server`load` function, + // SvelteKit will actually just re-enter the `handle` function and set `isSubRequest` + // to `true` so that no additional network call is made. + // We want the `http.server` span of that nested call to be a child span of the + // currently active span instead of a new root span to correctly reflect this + // behavior. + // As a fallback for Kit < 1.21.0, we check if there is an active span only if there's none, + // we create a new execution context. + const isSubRequest = typeof input.event.isSubRequest === 'boolean' ? input.event.isSubRequest : !!getActiveSpan(); + + if (isSubRequest) { + return instrumentHandle(input, options); + } + + return withIsolationScope(isolationScope => { + // We only call continueTrace in the initial top level request to avoid + // creating a new root span for the sub request. + isolationScope.setSDKProcessingMetadata({ + normalizedRequest: winterCGRequestToRequestData(input.event.request.clone()), + }); + return continueTrace(getTracePropagationData(input.event), () => instrumentHandle(input, options)); + }); + }; + + return sentryRequestHandler; +} + +async function instrumentHandle( + { event, resolve }: Parameters[0], + options: SentryHandleOptions, +): Promise { + if (!event.route?.id && !options.handleUnknownRoutes) { + return resolve(event); + } + + const routeName = `${event.request.method} ${event.route?.id || event.url.pathname}`; + + if (getIsolationScope() !== getDefaultIsolationScope()) { + getIsolationScope().setTransactionName(routeName); + } else { + DEBUG_BUILD && logger.warn('Isolation scope is default isolation scope - skipping setting transactionName'); + } + + try { + const resolveResult = await startSpan( + { + op: 'http.server', + attributes: { + [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.http.sveltekit', + [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: event.route?.id ? 'route' : 'url', + 'http.method': event.request.method, + }, + name: routeName, + }, + async (span?: Span) => { + getCurrentScope().setSDKProcessingMetadata({ + normalizedRequest: winterCGRequestToRequestData(event.request.clone()), + }); + const res = await resolve(event, { + transformPageChunk: addSentryCodeToPage(options), + }); + if (span) { + setHttpStatus(span, res.status); + } + return res; + }, + ); + return resolveResult; + } catch (e: unknown) { + sendErrorToSentry(e, 'handle'); + throw e; + } finally { + await flushIfServerless(); + } +} diff --git a/packages/sveltekit/src/worker/handleError.ts b/packages/sveltekit/src/worker/handleError.ts new file mode 100644 index 000000000000..f668a30088f0 --- /dev/null +++ b/packages/sveltekit/src/worker/handleError.ts @@ -0,0 +1,79 @@ +import { consoleSandbox, captureException } from '@sentry/core'; +import type { HandleServerError } from '@sveltejs/kit'; + +import { flushIfServerless } from './utils'; + +// The SvelteKit default error handler just logs the error's stack trace to the console +// see: https://github.com/sveltejs/kit/blob/369e7d6851f543a40c947e033bfc4a9506fdc0a8/packages/kit/src/runtime/server/index.js#L43 +function defaultErrorHandler({ error }: Parameters[0]): ReturnType { + // @ts-expect-error this conforms to the default implementation (including this ts-expect-error) + // eslint-disable-next-line no-console + consoleSandbox(() => console.error(error && error.stack)); +} + +type HandleServerErrorInput = Parameters[0]; + +/** + * Backwards-compatible HandleServerError Input type for SvelteKit 1.x and 2.x + * `message` and `status` were added in 2.x. + * For backwards-compatibility, we make them optional + * + * @see https://kit.svelte.dev/docs/migrating-to-sveltekit-2#improved-error-handling + */ +type SafeHandleServerErrorInput = Omit & + Partial>; + +/** + * Wrapper for the SvelteKit error handler that sends the error to Sentry. + * + * @param handleError The original SvelteKit error handler. + */ +export function handleErrorWithSentry(handleError: HandleServerError = defaultErrorHandler): HandleServerError { + return async (input: SafeHandleServerErrorInput): Promise => { + if (isNotFoundError(input)) { + // We're extra cautious with SafeHandleServerErrorInput - this type is not compatible with HandleServerErrorInput + // @ts-expect-error - we're still passing the same object, just with a different (backwards-compatible) type + return handleError(input); + } + + captureException(input.error, { + mechanism: { + type: 'sveltekit', + handled: false, + }, + }); + + await flushIfServerless(); + + // We're extra cautious with SafeHandleServerErrorInput - this type is not compatible with HandleServerErrorInput + // @ts-expect-error - we're still passing the same object, just with a different (backwards-compatible) type + return handleError(input); + }; +} + +/** + * When a page request fails because the page is not found, SvelteKit throws a "Not found" error. + */ +function isNotFoundError(input: SafeHandleServerErrorInput): boolean { + const { error, event, status } = input; + + // SvelteKit 2.0 offers a reliable way to check for a Not Found error: + if (status === 404) { + return true; + } + + // SvelteKit 1.x doesn't offer a reliable way to check for a Not Found error. + // So we check the route id (shouldn't exist) and the raw stack trace + // We can delete all of this below whenever we drop Kit 1.x support + const hasNoRouteId = !event.route || !event.route.id; + + const rawStack: string = + (error != null && + typeof error === 'object' && + 'stack' in error && + typeof error.stack === 'string' && + error.stack) || + ''; + + return hasNoRouteId && rawStack.startsWith('Error: Not found:'); +} diff --git a/packages/sveltekit/src/worker/index.ts b/packages/sveltekit/src/worker/index.ts new file mode 100644 index 000000000000..ce6b385c0d44 --- /dev/null +++ b/packages/sveltekit/src/worker/index.ts @@ -0,0 +1,105 @@ +// For use in cloudflare workers and other edge environments +// +// These are essentially the same as the node server exports, but using imports from @sentry/core +// instead of @sentry/node. +// +// This is expected to be used together with something like the @sentry/cloudflare package, to initialize Sentry +// in the worker. +// +// ------------------------- +// SvelteKit SDK exports: +export { handleErrorWithSentry } from './handleError'; +export { wrapLoadWithSentry, wrapServerLoadWithSentry } from './load'; +export { sentryHandle } from './handle'; +export { wrapServerRouteWithSentry } from './serverRoute'; + +// Re-export some functions from core SDK +export { + addBreadcrumb, + addEventProcessor, + addIntegration, + // eslint-disable-next-line deprecation/deprecation + addRequestDataToEvent, + captureCheckIn, + captureConsoleIntegration, + captureEvent, + captureException, + captureFeedback, + captureMessage, + captureSession, + close, + continueTrace, + createTransport, + // eslint-disable-next-line deprecation/deprecation + debugIntegration, + dedupeIntegration, + DEFAULT_USER_INCLUDES, + endSession, + // eslint-disable-next-line deprecation/deprecation + extractRequestData, + extraErrorDataIntegration, + flush, + functionToStringIntegration, + getActiveSpan, + getClient, + // eslint-disable-next-line deprecation/deprecation + getCurrentHub, + getCurrentScope, + getGlobalScope, + getIsolationScope, + getRootSpan, + getSpanDescendants, + getSpanStatusFromHttpCode, + getTraceData, + getTraceMetaTags, + inboundFiltersIntegration, + isInitialized, + lastEventId, + linkedErrorsIntegration, + // eslint-disable-next-line deprecation/deprecation + metrics, + parameterize, + requestDataIntegration, + rewriteFramesIntegration, + Scope, + SDK_VERSION, + SEMANTIC_ATTRIBUTE_SENTRY_OP, + SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN, + SEMANTIC_ATTRIBUTE_SENTRY_SAMPLE_RATE, + SEMANTIC_ATTRIBUTE_SENTRY_SOURCE, + // eslint-disable-next-line deprecation/deprecation + sessionTimingIntegration, + setContext, + setCurrentClient, + setExtra, + setExtras, + setHttpStatus, + setMeasurement, + setTag, + setTags, + setUser, + spanToBaggageHeader, + spanToJSON, + spanToTraceHeader, + startInactiveSpan, + startNewTrace, + suppressTracing, + startSession, + startSpan, + startSpanManual, + trpcMiddleware, + withActiveSpan, + withIsolationScope, + withMonitor, + withScope, + zodErrorsIntegration, +} from '@sentry/core'; + +/** + * Tracks the Svelte component's initialization and mounting operation as well as + * updates and records them as spans. These spans are only recorded on the client-side. + * Sever-side, during SSR, this function will not record any spans. + */ +export function trackComponent(_options?: unknown): void { + // no-op on the server side +} diff --git a/packages/sveltekit/src/worker/load.ts b/packages/sveltekit/src/worker/load.ts new file mode 100644 index 000000000000..1c65dacf1254 --- /dev/null +++ b/packages/sveltekit/src/worker/load.ts @@ -0,0 +1,124 @@ +import { + addNonEnumerableProperty, + SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN, + SEMANTIC_ATTRIBUTE_SENTRY_SOURCE, + startSpan, +} from '@sentry/core'; +import type { LoadEvent, ServerLoadEvent } from '@sveltejs/kit'; + +import type { SentryWrappedFlag } from '../common/utils'; +import { flushIfServerless, sendErrorToSentry } from './utils'; + +type PatchedLoadEvent = LoadEvent & SentryWrappedFlag; +type PatchedServerLoadEvent = ServerLoadEvent & SentryWrappedFlag; + +/** + * @inheritdoc + */ +// The liberal generic typing of `T` is necessary because we cannot let T extend `Load`. +// This function needs to tell TS that it returns exactly the type that it was called with +// because SvelteKit generates the narrowed down `PageLoad` or `LayoutLoad` types +// at build time for every route. +// eslint-disable-next-line @typescript-eslint/no-explicit-any +export function wrapLoadWithSentry any>(origLoad: T): T { + return new Proxy(origLoad, { + apply: async (wrappingTarget, thisArg, args: Parameters) => { + // Type casting here because `T` cannot extend `Load` (see comment above function signature) + // Also, this event possibly already has a sentry wrapped flag attached + const event = args[0] as PatchedLoadEvent; + + if (event.__sentry_wrapped__) { + return wrappingTarget.apply(thisArg, args); + } + + addNonEnumerableProperty(event as unknown as Record, '__sentry_wrapped__', true); + + const routeId = event.route && event.route.id; + + try { + // We need to await before returning, otherwise we won't catch any errors thrown by the load function + return await startSpan( + { + op: 'function.sveltekit.load', + attributes: { + [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.function.sveltekit', + [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: routeId ? 'route' : 'url', + }, + name: routeId ? routeId : event.url.pathname, + }, + () => wrappingTarget.apply(thisArg, args), + ); + } catch (e) { + sendErrorToSentry(e, 'load'); + throw e; + } finally { + await flushIfServerless(); + } + }, + }); +} + +/** + * Wrap a server-only load function (e.g. +page.server.js or +layout.server.js) with Sentry functionality + * + * Usage: + * + * ```js + * // +page.serverjs + * + * import { wrapServerLoadWithSentry } + * + * export const load = wrapServerLoadWithSentry((event) => { + * // your load code + * }); + * ``` + * + * @param origServerLoad SvelteKit user defined server-only load function + */ +// The liberal generic typing of `T` is necessary because we cannot let T extend `ServerLoad`. +// This function needs to tell TS that it returns exactly the type that it was called with +// because SvelteKit generates the narrowed down `PageServerLoad` or `LayoutServerLoad` types +// at build time for every route. +// eslint-disable-next-line @typescript-eslint/no-explicit-any +export function wrapServerLoadWithSentry any>(origServerLoad: T): T { + return new Proxy(origServerLoad, { + apply: async (wrappingTarget, thisArg, args: Parameters) => { + // Type casting here because `T` cannot extend `ServerLoad` (see comment above function signature) + // Also, this event possibly already has a sentry wrapped flag attached + const event = args[0] as PatchedServerLoadEvent; + + if (event.__sentry_wrapped__) { + return wrappingTarget.apply(thisArg, args); + } + + addNonEnumerableProperty(event as unknown as Record, '__sentry_wrapped__', true); + + // Accessing any member of `event.route` causes SvelteKit to invalidate the + // server `load` function's data on every route change. + // To work around this, we use `Object.getOwnPropertyDescriptor` which doesn't invoke the proxy. + // https://github.com/sveltejs/kit/blob/e133aba479fa9ba0e7f9e71512f5f937f0247e2c/packages/kit/src/runtime/server/page/load_data.js#L111C3-L124 + const routeId = event.route && (Object.getOwnPropertyDescriptor(event.route, 'id')?.value as string | undefined); + + try { + // We need to await before returning, otherwise we won't catch any errors thrown by the load function + return await startSpan( + { + op: 'function.sveltekit.server.load', + attributes: { + [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.function.sveltekit', + [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: routeId ? 'route' : 'url', + 'http.method': event.request.method, + }, + name: routeId ? routeId : event.url.pathname, + }, + () => wrappingTarget.apply(thisArg, args), + ); + } catch (e: unknown) { + sendErrorToSentry(e, 'load'); + throw e; + } finally { + await flushIfServerless(); + } + }, + }); +} diff --git a/packages/sveltekit/src/worker/serverRoute.ts b/packages/sveltekit/src/worker/serverRoute.ts new file mode 100644 index 000000000000..ec0ebda529e6 --- /dev/null +++ b/packages/sveltekit/src/worker/serverRoute.ts @@ -0,0 +1,71 @@ +import { + addNonEnumerableProperty, + SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN, + SEMANTIC_ATTRIBUTE_SENTRY_SOURCE, + startSpan, +} from '@sentry/core'; +import type { RequestEvent } from '@sveltejs/kit'; +import { flushIfServerless, sendErrorToSentry } from './utils'; + +type PatchedServerRouteEvent = RequestEvent & { __sentry_wrapped__?: boolean }; + +/** + * Wraps a server route handler for API or server routes registered in `+server.(js|js)` files. + * + * This function will automatically capture any errors that occur during the execution of the route handler + * and it will start a span for the duration of your route handler. + * + * @example + * ```js + * import { wrapServerRouteWithSentry } from '@sentry/sveltekit'; + * + * const get = async event => { + * return new Response(JSON.stringify({ message: 'hello world' })); + * } + * + * export const GET = wrapServerRouteWithSentry(get); + * ``` + * + * @param originalRouteHandler your server route handler + * @param httpMethod the HTTP method of your route handler + * + * @returns a wrapped version of your server route handler + */ +export function wrapServerRouteWithSentry( + originalRouteHandler: (request: T) => Promise, +): (requestEvent: T) => Promise { + return new Proxy(originalRouteHandler, { + apply: async (wrappingTarget, thisArg, args) => { + const event = args[0] as PatchedServerRouteEvent; + + if (event.__sentry_wrapped__) { + return wrappingTarget.apply(thisArg, args); + } + + const routeId = event.route && event.route.id; + const httpMethod = event.request.method; + + addNonEnumerableProperty(event as unknown as Record, '__sentry_wrapped__', true); + + try { + return await startSpan( + { + name: `${httpMethod} ${routeId || 'Server Route'}`, + op: `function.sveltekit.server.${httpMethod.toLowerCase()}`, + attributes: { + [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.function.sveltekit', + [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'route', + }, + onlyIfParent: true, + }, + () => wrappingTarget.apply(thisArg, args), + ); + } catch (e) { + sendErrorToSentry(e, 'serverRoute'); + throw e; + } finally { + await flushIfServerless(); + } + }, + }); +} diff --git a/packages/sveltekit/src/worker/utils.ts b/packages/sveltekit/src/worker/utils.ts new file mode 100644 index 000000000000..151e194574b3 --- /dev/null +++ b/packages/sveltekit/src/worker/utils.ts @@ -0,0 +1,71 @@ +import { logger, objectify, captureException, flush } from '@sentry/core'; +import type { RequestEvent } from '@sveltejs/kit'; + +import { DEBUG_BUILD } from '../common/debug-build'; +import { isHttpError, isRedirect } from '../common/utils'; + +/** + * Takes a request event and extracts traceparent and DSC data + * from the `sentry-trace` and `baggage` DSC headers. + * + * Sets propagation context as a side effect. + */ +export function getTracePropagationData(event: RequestEvent): { sentryTrace: string; baggage: string | null } { + const sentryTrace = event.request.headers.get('sentry-trace') || ''; + const baggage = event.request.headers.get('baggage'); + + return { sentryTrace, baggage }; +} + +/** Flush the event queue to ensure that events get sent to Sentry before the response is finished and the lambda ends */ +export async function flushIfServerless(): Promise { + const platformSupportsStreaming = !process.env.LAMBDA_TASK_ROOT && !process.env.VERCEL; + + if (!platformSupportsStreaming) { + try { + DEBUG_BUILD && logger.log('Flushing events...'); + await flush(2000); + DEBUG_BUILD && logger.log('Done flushing events'); + } catch (e) { + DEBUG_BUILD && logger.log('Error while flushing events:\n', e); + } + } +} + +/** + * Extracts a server-side sveltekit error, filters a couple of known errors we don't want to capture + * and captures the error via `captureException`. + * + * @param e error + * + * @returns an objectified version of @param e + */ +export function sendErrorToSentry(e: unknown, handlerFn: 'handle' | 'load' | 'serverRoute'): object { + // In case we have a primitive, wrap it in the equivalent wrapper class (string -> String, etc.) so that we can + // store a seen flag on it. + const objectifiedErr = objectify(e); + + // The error() helper is commonly used to throw errors in load functions: https://kit.svelte.dev/docs/modules#sveltejs-kit-error + // If we detect a thrown error that is an instance of HttpError, we don't want to capture 4xx errors as they + // could be noisy. + // Also the `redirect(...)` helper is used to redirect users from one page to another. We don't want to capture thrown + // `Redirect`s as they're not errors but expected behaviour + if ( + isRedirect(objectifiedErr) || + (isHttpError(objectifiedErr) && objectifiedErr.status < 500 && objectifiedErr.status >= 400) + ) { + return objectifiedErr; + } + + captureException(objectifiedErr, { + mechanism: { + type: 'sveltekit', + handled: false, + data: { + function: handlerFn, + }, + }, + }); + + return objectifiedErr; +} From ce9382485356458aefabb2a08e4586d6f88411e2 Mon Sep 17 00:00:00 2001 From: Sam Greening <2552620+SG60@users.noreply.github.com> Date: Wed, 11 Dec 2024 20:02:27 +0000 Subject: [PATCH 02/31] fix(sveltekit): provide a handle function to init cloudflare workers sdk in sveltekit Also reexports the Cloudflare SDK. --- packages/sveltekit/package.json | 5 ++-- packages/sveltekit/src/worker/handle.ts | 26 +++++++++++++++++++- packages/sveltekit/src/worker/handleError.ts | 3 ++- packages/sveltekit/src/worker/index.ts | 20 +++------------ packages/sveltekit/src/worker/load.ts | 8 ++---- packages/sveltekit/src/worker/serverRoute.ts | 8 ++---- packages/sveltekit/src/worker/utils.ts | 3 ++- 7 files changed, 39 insertions(+), 34 deletions(-) diff --git a/packages/sveltekit/package.json b/packages/sveltekit/package.json index 69eb8dea3df6..3c39cb3093f0 100644 --- a/packages/sveltekit/package.json +++ b/packages/sveltekit/package.json @@ -9,9 +9,7 @@ "engines": { "node": ">=18" }, - "files": [ - "/build" - ], + "files": ["/build"], "main": "build/cjs/index.server.js", "module": "build/esm/index.server.js", "browser": "build/esm/index.client.js", @@ -44,6 +42,7 @@ } }, "dependencies": { + "@sentry/cloudflare": "9.1.0", "@sentry/core": "9.1.0", "@sentry/node": "9.1.0", "@sentry/opentelemetry": "9.1.0", diff --git a/packages/sveltekit/src/worker/handle.ts b/packages/sveltekit/src/worker/handle.ts index 568d421016ee..744b7ae008b4 100644 --- a/packages/sveltekit/src/worker/handle.ts +++ b/packages/sveltekit/src/worker/handle.ts @@ -12,8 +12,8 @@ import { startSpan, winterCGRequestToRequestData, withIsolationScope, - continueTrace, } from '@sentry/core'; +import { CloudflareOptions, continueTrace, wrapRequestHandler } from '@sentry/cloudflare'; import type { Handle, ResolveOptions } from '@sveltejs/kit'; import { DEBUG_BUILD } from '../common/debug-build'; @@ -192,3 +192,27 @@ async function instrumentHandle( await flushIfServerless(); } } + +/** Initializes Sentry SvelteKit Cloudflare SDK + * This should be before the sentryHandle() call. + * */ +export function initCloudflareSentryHandle(handlerOptions: CloudflareOptions): Handle { + const options = { ...handlerOptions }; + + const handleInitSentry: Handle = ({ event, resolve }) => { + // if event.platform exists (should be there in a cloudflare worker), then do the cloudflare sentry init + return event.platform + ? wrapRequestHandler( + { + options, + request: event.request, + // @ts-expect-error This will exist in Cloudflare + context: event.platform.context, + }, + () => resolve(event), + ) + : resolve(event); + }; + + return handleInitSentry; +} diff --git a/packages/sveltekit/src/worker/handleError.ts b/packages/sveltekit/src/worker/handleError.ts index f668a30088f0..da57ea09afad 100644 --- a/packages/sveltekit/src/worker/handleError.ts +++ b/packages/sveltekit/src/worker/handleError.ts @@ -1,4 +1,5 @@ -import { consoleSandbox, captureException } from '@sentry/core'; +import { consoleSandbox } from '@sentry/core'; +import { captureException } from '@sentry/cloudflare'; import type { HandleServerError } from '@sveltejs/kit'; import { flushIfServerless } from './utils'; diff --git a/packages/sveltekit/src/worker/index.ts b/packages/sveltekit/src/worker/index.ts index ce6b385c0d44..0159dda0dd1f 100644 --- a/packages/sveltekit/src/worker/index.ts +++ b/packages/sveltekit/src/worker/index.ts @@ -10,41 +10,33 @@ // SvelteKit SDK exports: export { handleErrorWithSentry } from './handleError'; export { wrapLoadWithSentry, wrapServerLoadWithSentry } from './load'; -export { sentryHandle } from './handle'; +export { sentryHandle, initCloudflareSentryHandle } from './handle'; export { wrapServerRouteWithSentry } from './serverRoute'; -// Re-export some functions from core SDK +// Re-export some functions from Cloudflare SDK export { addBreadcrumb, addEventProcessor, addIntegration, - // eslint-disable-next-line deprecation/deprecation - addRequestDataToEvent, captureCheckIn, captureConsoleIntegration, captureEvent, captureException, captureFeedback, captureMessage, - captureSession, close, continueTrace, createTransport, // eslint-disable-next-line deprecation/deprecation debugIntegration, dedupeIntegration, - DEFAULT_USER_INCLUDES, - endSession, - // eslint-disable-next-line deprecation/deprecation - extractRequestData, extraErrorDataIntegration, flush, functionToStringIntegration, getActiveSpan, getClient, - // eslint-disable-next-line deprecation/deprecation - getCurrentHub, getCurrentScope, + getDefaultIntegrations, getGlobalScope, getIsolationScope, getRootSpan, @@ -58,7 +50,6 @@ export { linkedErrorsIntegration, // eslint-disable-next-line deprecation/deprecation metrics, - parameterize, requestDataIntegration, rewriteFramesIntegration, Scope, @@ -67,8 +58,6 @@ export { SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN, SEMANTIC_ATTRIBUTE_SENTRY_SAMPLE_RATE, SEMANTIC_ATTRIBUTE_SENTRY_SOURCE, - // eslint-disable-next-line deprecation/deprecation - sessionTimingIntegration, setContext, setCurrentClient, setExtra, @@ -84,7 +73,6 @@ export { startInactiveSpan, startNewTrace, suppressTracing, - startSession, startSpan, startSpanManual, trpcMiddleware, @@ -93,7 +81,7 @@ export { withMonitor, withScope, zodErrorsIntegration, -} from '@sentry/core'; +} from '@sentry/cloudflare'; /** * Tracks the Svelte component's initialization and mounting operation as well as diff --git a/packages/sveltekit/src/worker/load.ts b/packages/sveltekit/src/worker/load.ts index 1c65dacf1254..2fda5db920f7 100644 --- a/packages/sveltekit/src/worker/load.ts +++ b/packages/sveltekit/src/worker/load.ts @@ -1,9 +1,5 @@ -import { - addNonEnumerableProperty, - SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN, - SEMANTIC_ATTRIBUTE_SENTRY_SOURCE, - startSpan, -} from '@sentry/core'; +import { addNonEnumerableProperty } from '@sentry/core'; +import { SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN, SEMANTIC_ATTRIBUTE_SENTRY_SOURCE, startSpan } from '@sentry/cloudflare'; import type { LoadEvent, ServerLoadEvent } from '@sveltejs/kit'; import type { SentryWrappedFlag } from '../common/utils'; diff --git a/packages/sveltekit/src/worker/serverRoute.ts b/packages/sveltekit/src/worker/serverRoute.ts index ec0ebda529e6..803eecf74e56 100644 --- a/packages/sveltekit/src/worker/serverRoute.ts +++ b/packages/sveltekit/src/worker/serverRoute.ts @@ -1,9 +1,5 @@ -import { - addNonEnumerableProperty, - SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN, - SEMANTIC_ATTRIBUTE_SENTRY_SOURCE, - startSpan, -} from '@sentry/core'; +import { addNonEnumerableProperty } from '@sentry/core'; +import { SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN, SEMANTIC_ATTRIBUTE_SENTRY_SOURCE, startSpan } from '@sentry/cloudflare'; import type { RequestEvent } from '@sveltejs/kit'; import { flushIfServerless, sendErrorToSentry } from './utils'; diff --git a/packages/sveltekit/src/worker/utils.ts b/packages/sveltekit/src/worker/utils.ts index 151e194574b3..397f58f6b7ae 100644 --- a/packages/sveltekit/src/worker/utils.ts +++ b/packages/sveltekit/src/worker/utils.ts @@ -1,4 +1,5 @@ -import { logger, objectify, captureException, flush } from '@sentry/core'; +import { logger, objectify } from '@sentry/core'; +import { captureException, flush } from '@sentry/cloudflare'; import type { RequestEvent } from '@sveltejs/kit'; import { DEBUG_BUILD } from '../common/debug-build'; From 9eec3cf701d671a03425cdd7f9c3d932d3cd857a Mon Sep 17 00:00:00 2001 From: Sam Greening <2552620+SG60@users.noreply.github.com> Date: Thu, 12 Dec 2024 11:36:20 +0000 Subject: [PATCH 03/31] chore(sveltekit): refactor some common server-side code Handle functions and utils.ts There is more left to refactor as well --- .../sveltekit/src/server-common/handle.ts | 197 ++++++++++++++++++ .../src/{server => server-common}/utils.ts | 3 +- packages/sveltekit/src/server/handle.ts | 62 +----- packages/sveltekit/src/server/handleError.ts | 2 +- packages/sveltekit/src/server/load.ts | 2 +- packages/sveltekit/src/server/serverRoute.ts | 2 +- packages/sveltekit/src/worker/handle.ts | 173 +-------------- packages/sveltekit/src/worker/handleError.ts | 2 +- packages/sveltekit/src/worker/load.ts | 2 +- packages/sveltekit/src/worker/serverRoute.ts | 2 +- packages/sveltekit/src/worker/utils.ts | 72 ------- 11 files changed, 209 insertions(+), 310 deletions(-) create mode 100644 packages/sveltekit/src/server-common/handle.ts rename packages/sveltekit/src/{server => server-common}/utils.ts (95%) delete mode 100644 packages/sveltekit/src/worker/utils.ts diff --git a/packages/sveltekit/src/server-common/handle.ts b/packages/sveltekit/src/server-common/handle.ts new file mode 100644 index 000000000000..1811faf3c66b --- /dev/null +++ b/packages/sveltekit/src/server-common/handle.ts @@ -0,0 +1,197 @@ +import type { continueTrace, Span } from '@sentry/core'; +import { + SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN, + SEMANTIC_ATTRIBUTE_SENTRY_SOURCE, + getActiveSpan, + getCurrentScope, + getDefaultIsolationScope, + getIsolationScope, + getTraceMetaTags, + logger, + setHttpStatus, + startSpan, + winterCGRequestToRequestData, + withIsolationScope, +} from '@sentry/core'; +import type { Handle, ResolveOptions } from '@sveltejs/kit'; + +import { DEBUG_BUILD } from '../common/debug-build'; +import { flushIfServerless, getTracePropagationData, sendErrorToSentry } from './utils'; + +export type SentryHandleOptions = { + /** + * Controls whether the SDK should capture errors and traces in requests that don't belong to a + * route defined in your SvelteKit application. + * + * By default, this option is set to `false` to reduce noise (e.g. bots sending random requests to your server). + * + * Set this option to `true` if you want to monitor requests events without a route. This might be useful in certain + * scenarios, for instance if you registered other handlers that handle these requests. + * If you set this option, you might want adjust the the transaction name in the `beforeSendTransaction` + * callback of your server-side `Sentry.init` options. You can also use `beforeSendTransaction` to filter out + * transactions that you still don't want to be sent to Sentry. + * + * @default false + */ + handleUnknownRoutes?: boolean; + + /** + * Controls if `sentryHandle` should inject a script tag into the page that enables instrumentation + * of `fetch` calls in `load` functions. + * + * @default true + */ + injectFetchProxyScript?: boolean; +}; + +export const FETCH_PROXY_SCRIPT = ` + const f = window.fetch; + if(f){ + window._sentryFetchProxy = function(...a){return f(...a)} + window.fetch = function(...a){return window._sentryFetchProxy(...a)} + } +`; +/** + * Adds Sentry tracing tags to the returned html page. + * Adds Sentry fetch proxy script to the returned html page if enabled in options. + * + * Exported only for testing + */ +export function addSentryCodeToPage(options: { injectFetchProxyScript: boolean }): NonNullable< + ResolveOptions['transformPageChunk'] +> { + return ({ html }) => { + const metaTags = getTraceMetaTags(); + const headWithMetaTags = metaTags ? `\n${metaTags}` : ''; + + const headWithFetchScript = options.injectFetchProxyScript ? `\n` : ''; + + const modifiedHead = `${headWithMetaTags}${headWithFetchScript}`; + + return html.replace('', modifiedHead); + }; +} + +async function instrumentHandle( + { event, resolve }: Parameters[0], + options: SentryHandleOptions, +): Promise { + if (!event.route?.id && !options.handleUnknownRoutes) { + return resolve(event); + } + + // caching the result of the version check in `options.injectFetchProxyScript` + // to avoid doing the dynamic import on every request + if (options.injectFetchProxyScript == null) { + try { + // @ts-expect-error - the dynamic import is fine here + const { VERSION } = await import('@sveltejs/kit'); + options.injectFetchProxyScript = isFetchProxyRequired(VERSION); + } catch { + options.injectFetchProxyScript = true; + } + } + + const routeName = `${event.request.method} ${event.route?.id || event.url.pathname}`; + + if (getIsolationScope() !== getDefaultIsolationScope()) { + getIsolationScope().setTransactionName(routeName); + } else { + DEBUG_BUILD && logger.warn('Isolation scope is default isolation scope - skipping setting transactionName'); + } + + try { + const resolveResult = await startSpan( + { + op: 'http.server', + attributes: { + [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.http.sveltekit', + [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: event.route?.id ? 'route' : 'url', + 'http.method': event.request.method, + }, + name: routeName, + }, + async (span?: Span) => { + getCurrentScope().setSDKProcessingMetadata({ + normalizedRequest: winterCGRequestToRequestData(event.request.clone()), + }); + const res = await resolve(event, { + transformPageChunk: addSentryCodeToPage({ injectFetchProxyScript: options.injectFetchProxyScript ?? true }), + }); + if (span) { + setHttpStatus(span, res.status); + } + return res; + }, + ); + return resolveResult; + } catch (e: unknown) { + sendErrorToSentry(e, 'handle'); + throw e; + } finally { + await flushIfServerless(); + } +} + +/** + * We only need to inject the fetch proxy script for SvelteKit versions < 2.16.0. + * Exported only for testing. + */ +export function isFetchProxyRequired(version: string): boolean { + try { + const [major, minor] = version.trim().replace(/-.*/, '').split('.').map(Number); + if (major != null && minor != null && (major > 2 || (major === 2 && minor >= 16))) { + return false; + } + } catch { + // ignore + } + return true; +} + +/** + * A SvelteKit handle function that wraps the request for Sentry error and + * performance monitoring. + * + * Some environments require a different continueTrace function. E.g. Node can use + * the Opentelemetry SDK, whereas Cloudflare cannot. + */ +export function sentryHandleGeneric( + continueTraceFunction: typeof continueTrace, + handlerOptions?: SentryHandleOptions, +): Handle { + const options = { + handleUnknownRoutes: false, + injectFetchProxyScript: true, + ...handlerOptions, + }; + + const sentryRequestHandler: Handle = input => { + // event.isSubRequest was added in SvelteKit 1.21.0 and we can use it to check + // if we should create a new execution context or not. + // In case of a same-origin `fetch` call within a server`load` function, + // SvelteKit will actually just re-enter the `handle` function and set `isSubRequest` + // to `true` so that no additional network call is made. + // We want the `http.server` span of that nested call to be a child span of the + // currently active span instead of a new root span to correctly reflect this + // behavior. + // As a fallback for Kit < 1.21.0, we check if there is an active span only if there's none, + // we create a new execution context. + const isSubRequest = typeof input.event.isSubRequest === 'boolean' ? input.event.isSubRequest : !!getActiveSpan(); + + if (isSubRequest) { + return instrumentHandle(input, options); + } + + return withIsolationScope(isolationScope => { + // We only call continueTrace in the initial top level request to avoid + // creating a new root span for the sub request. + isolationScope.setSDKProcessingMetadata({ + normalizedRequest: winterCGRequestToRequestData(input.event.request.clone()), + }); + return continueTraceFunction(getTracePropagationData(input.event), () => instrumentHandle(input, options)); + }); + }; + + return sentryRequestHandler; +} diff --git a/packages/sveltekit/src/server/utils.ts b/packages/sveltekit/src/server-common/utils.ts similarity index 95% rename from packages/sveltekit/src/server/utils.ts rename to packages/sveltekit/src/server-common/utils.ts index 8eae93d531ab..d6f09093b74d 100644 --- a/packages/sveltekit/src/server/utils.ts +++ b/packages/sveltekit/src/server-common/utils.ts @@ -1,5 +1,4 @@ -import { logger, objectify } from '@sentry/core'; -import { captureException, flush } from '@sentry/node'; +import { captureException, flush, logger, objectify } from '@sentry/core'; import type { RequestEvent } from '@sveltejs/kit'; import { DEBUG_BUILD } from '../common/debug-build'; diff --git a/packages/sveltekit/src/server/handle.ts b/packages/sveltekit/src/server/handle.ts index 84f29a2c70c5..359e24c0ee5f 100644 --- a/packages/sveltekit/src/server/handle.ts +++ b/packages/sveltekit/src/server/handle.ts @@ -15,66 +15,8 @@ import { } from '@sentry/core'; import type { Handle, ResolveOptions } from '@sveltejs/kit'; -import { DEBUG_BUILD } from '../common/debug-build'; -import { flushIfServerless, getTracePropagationData, sendErrorToSentry } from './utils'; - -export type SentryHandleOptions = { - /** - * Controls whether the SDK should capture errors and traces in requests that don't belong to a - * route defined in your SvelteKit application. - * - * By default, this option is set to `false` to reduce noise (e.g. bots sending random requests to your server). - * - * Set this option to `true` if you want to monitor requests events without a route. This might be useful in certain - * scenarios, for instance if you registered other handlers that handle these requests. - * If you set this option, you might want adjust the the transaction name in the `beforeSendTransaction` - * callback of your server-side `Sentry.init` options. You can also use `beforeSendTransaction` to filter out - * transactions that you still don't want to be sent to Sentry. - * - * @default false - */ - handleUnknownRoutes?: boolean; - - /** - * Controls if `sentryHandle` should inject a script tag into the page that enables instrumentation - * of `fetch` calls in `load` functions. - * - * @default true - */ - injectFetchProxyScript?: boolean; -}; - -/** - * Exported only for testing - */ -export const FETCH_PROXY_SCRIPT = ` - const f = window.fetch; - if(f){ - window._sentryFetchProxy = function(...a){return f(...a)} - window.fetch = function(...a){return window._sentryFetchProxy(...a)} - } -`; - -/** - * Adds Sentry tracing tags to the returned html page. - * Adds Sentry fetch proxy script to the returned html page if enabled in options. - * - * Exported only for testing - */ -export function addSentryCodeToPage(options: { injectFetchProxyScript: boolean }): NonNullable< - ResolveOptions['transformPageChunk'] -> { - return ({ html }) => { - const metaTags = getTraceMetaTags(); - const headWithMetaTags = metaTags ? `\n${metaTags}` : ''; - - const headWithFetchScript = options.injectFetchProxyScript ? `\n` : ''; - - const modifiedHead = `${headWithMetaTags}${headWithFetchScript}`; - - return html.replace('', modifiedHead); - }; -} +import type { SentryHandleOptions } from '../server-common/handle'; +import { sentryHandleGeneric } from '../server-common/handle'; /** * A SvelteKit handle function that wraps the request for Sentry error and diff --git a/packages/sveltekit/src/server/handleError.ts b/packages/sveltekit/src/server/handleError.ts index 30ca4e28de1a..761c098189a1 100644 --- a/packages/sveltekit/src/server/handleError.ts +++ b/packages/sveltekit/src/server/handleError.ts @@ -2,7 +2,7 @@ import { consoleSandbox } from '@sentry/core'; import { captureException } from '@sentry/node'; import type { HandleServerError } from '@sveltejs/kit'; -import { flushIfServerless } from './utils'; +import { flushIfServerless } from '../server-common/utils'; // The SvelteKit default error handler just logs the error's stack trace to the console // see: https://github.com/sveltejs/kit/blob/369e7d6851f543a40c947e033bfc4a9506fdc0a8/packages/kit/src/runtime/server/index.js#L43 diff --git a/packages/sveltekit/src/server/load.ts b/packages/sveltekit/src/server/load.ts index 30fab345e05b..e48fa070bf38 100644 --- a/packages/sveltekit/src/server/load.ts +++ b/packages/sveltekit/src/server/load.ts @@ -3,7 +3,7 @@ import { SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN, SEMANTIC_ATTRIBUTE_SENTRY_SOURCE, sta import type { LoadEvent, ServerLoadEvent } from '@sveltejs/kit'; import type { SentryWrappedFlag } from '../common/utils'; -import { flushIfServerless, sendErrorToSentry } from './utils'; +import { flushIfServerless, sendErrorToSentry } from '../server-common/utils'; type PatchedLoadEvent = LoadEvent & SentryWrappedFlag; type PatchedServerLoadEvent = ServerLoadEvent & SentryWrappedFlag; diff --git a/packages/sveltekit/src/server/serverRoute.ts b/packages/sveltekit/src/server/serverRoute.ts index 9d2cba3dbcdc..5f0edd38b8ff 100644 --- a/packages/sveltekit/src/server/serverRoute.ts +++ b/packages/sveltekit/src/server/serverRoute.ts @@ -1,7 +1,7 @@ import { addNonEnumerableProperty } from '@sentry/core'; import { SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN, SEMANTIC_ATTRIBUTE_SENTRY_SOURCE, startSpan } from '@sentry/node'; import type { RequestEvent } from '@sveltejs/kit'; -import { flushIfServerless, sendErrorToSentry } from './utils'; +import { flushIfServerless, sendErrorToSentry } from '../server-common/utils'; type PatchedServerRouteEvent = RequestEvent & { __sentry_wrapped__?: boolean }; diff --git a/packages/sveltekit/src/worker/handle.ts b/packages/sveltekit/src/worker/handle.ts index 744b7ae008b4..363a07adc3bd 100644 --- a/packages/sveltekit/src/worker/handle.ts +++ b/packages/sveltekit/src/worker/handle.ts @@ -1,94 +1,7 @@ -import type { Span } from '@sentry/core'; -import { - SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN, - SEMANTIC_ATTRIBUTE_SENTRY_SOURCE, - getActiveSpan, - getCurrentScope, - getDefaultIsolationScope, - getIsolationScope, - getTraceMetaTags, - logger, - setHttpStatus, - startSpan, - winterCGRequestToRequestData, - withIsolationScope, -} from '@sentry/core'; import { CloudflareOptions, continueTrace, wrapRequestHandler } from '@sentry/cloudflare'; -import type { Handle, ResolveOptions } from '@sveltejs/kit'; +import type { Handle } from '@sveltejs/kit'; -import { DEBUG_BUILD } from '../common/debug-build'; -import { flushIfServerless, getTracePropagationData, sendErrorToSentry } from './utils'; - -export type SentryHandleOptions = { - /** - * Controls whether the SDK should capture errors and traces in requests that don't belong to a - * route defined in your SvelteKit application. - * - * By default, this option is set to `false` to reduce noise (e.g. bots sending random requests to your server). - * - * Set this option to `true` if you want to monitor requests events without a route. This might be useful in certain - * scenarios, for instance if you registered other handlers that handle these requests. - * If you set this option, you might want adjust the the transaction name in the `beforeSendTransaction` - * callback of your server-side `Sentry.init` options. You can also use `beforeSendTransaction` to filter out - * transactions that you still don't want to be sent to Sentry. - * - * @default false - */ - handleUnknownRoutes?: boolean; - - /** - * Controls if `sentryHandle` should inject a script tag into the page that enables instrumentation - * of `fetch` calls in `load` functions. - * - * @default true - */ - injectFetchProxyScript?: boolean; - - /** - * If this option is set, the `sentryHandle` handler will add a nonce attribute to the script - * tag it injects into the page. This script is used to enable instrumentation of `fetch` calls - * in `load` functions. - * - * Use this if your CSP policy blocks the fetch proxy script injected by `sentryHandle`. - */ - fetchProxyScriptNonce?: string; -}; - -/** - * Exported only for testing - */ -export const FETCH_PROXY_SCRIPT = ` - const f = window.fetch; - if(f){ - window._sentryFetchProxy = function(...a){return f(...a)} - window.fetch = function(...a){return window._sentryFetchProxy(...a)} - } -`; - -/** - * Adds Sentry tracing tags to the returned html page. - * Adds Sentry fetch proxy script to the returned html page if enabled in options. - * Also adds a nonce attribute to the script tag if users specified one for CSP. - * - * Exported only for testing - */ -export function addSentryCodeToPage(options: SentryHandleOptions): NonNullable { - const { fetchProxyScriptNonce, injectFetchProxyScript } = options; - // if injectFetchProxyScript is not set, we default to true - const shouldInjectScript = injectFetchProxyScript !== false; - const nonce = fetchProxyScriptNonce ? `nonce="${fetchProxyScriptNonce}"` : ''; - - return ({ html }) => { - const metaTags = getTraceMetaTags(); - const headWithMetaTags = metaTags ? `\n${metaTags}` : ''; - - const headWithFetchScript = shouldInjectScript ? `\n` : ''; - - const modifiedHead = `${headWithMetaTags}${headWithFetchScript}`; - - return html.replace('', modifiedHead); - }; -} +import { sentryHandleGeneric, SentryHandleOptions } from '../server-common/handle'; /** * A SvelteKit handle function that wraps the request for Sentry error and @@ -108,91 +21,11 @@ export function addSentryCodeToPage(options: SentryHandleOptions): NonNullable { - // event.isSubRequest was added in SvelteKit 1.21.0 and we can use it to check - // if we should create a new execution context or not. - // In case of a same-origin `fetch` call within a server`load` function, - // SvelteKit will actually just re-enter the `handle` function and set `isSubRequest` - // to `true` so that no additional network call is made. - // We want the `http.server` span of that nested call to be a child span of the - // currently active span instead of a new root span to correctly reflect this - // behavior. - // As a fallback for Kit < 1.21.0, we check if there is an active span only if there's none, - // we create a new execution context. - const isSubRequest = typeof input.event.isSubRequest === 'boolean' ? input.event.isSubRequest : !!getActiveSpan(); - - if (isSubRequest) { - return instrumentHandle(input, options); - } - - return withIsolationScope(isolationScope => { - // We only call continueTrace in the initial top level request to avoid - // creating a new root span for the sub request. - isolationScope.setSDKProcessingMetadata({ - normalizedRequest: winterCGRequestToRequestData(input.event.request.clone()), - }); - return continueTrace(getTracePropagationData(input.event), () => instrumentHandle(input, options)); - }); - }; + const sentryRequestHandler = sentryHandleGeneric(continueTrace, handlerOptions); return sentryRequestHandler; } -async function instrumentHandle( - { event, resolve }: Parameters[0], - options: SentryHandleOptions, -): Promise { - if (!event.route?.id && !options.handleUnknownRoutes) { - return resolve(event); - } - - const routeName = `${event.request.method} ${event.route?.id || event.url.pathname}`; - - if (getIsolationScope() !== getDefaultIsolationScope()) { - getIsolationScope().setTransactionName(routeName); - } else { - DEBUG_BUILD && logger.warn('Isolation scope is default isolation scope - skipping setting transactionName'); - } - - try { - const resolveResult = await startSpan( - { - op: 'http.server', - attributes: { - [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.http.sveltekit', - [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: event.route?.id ? 'route' : 'url', - 'http.method': event.request.method, - }, - name: routeName, - }, - async (span?: Span) => { - getCurrentScope().setSDKProcessingMetadata({ - normalizedRequest: winterCGRequestToRequestData(event.request.clone()), - }); - const res = await resolve(event, { - transformPageChunk: addSentryCodeToPage(options), - }); - if (span) { - setHttpStatus(span, res.status); - } - return res; - }, - ); - return resolveResult; - } catch (e: unknown) { - sendErrorToSentry(e, 'handle'); - throw e; - } finally { - await flushIfServerless(); - } -} - /** Initializes Sentry SvelteKit Cloudflare SDK * This should be before the sentryHandle() call. * */ diff --git a/packages/sveltekit/src/worker/handleError.ts b/packages/sveltekit/src/worker/handleError.ts index da57ea09afad..e05ac76ec627 100644 --- a/packages/sveltekit/src/worker/handleError.ts +++ b/packages/sveltekit/src/worker/handleError.ts @@ -2,7 +2,7 @@ import { consoleSandbox } from '@sentry/core'; import { captureException } from '@sentry/cloudflare'; import type { HandleServerError } from '@sveltejs/kit'; -import { flushIfServerless } from './utils'; +import { flushIfServerless } from '../server-common/utils'; // The SvelteKit default error handler just logs the error's stack trace to the console // see: https://github.com/sveltejs/kit/blob/369e7d6851f543a40c947e033bfc4a9506fdc0a8/packages/kit/src/runtime/server/index.js#L43 diff --git a/packages/sveltekit/src/worker/load.ts b/packages/sveltekit/src/worker/load.ts index 2fda5db920f7..863315607816 100644 --- a/packages/sveltekit/src/worker/load.ts +++ b/packages/sveltekit/src/worker/load.ts @@ -3,7 +3,7 @@ import { SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN, SEMANTIC_ATTRIBUTE_SENTRY_SOURCE, sta import type { LoadEvent, ServerLoadEvent } from '@sveltejs/kit'; import type { SentryWrappedFlag } from '../common/utils'; -import { flushIfServerless, sendErrorToSentry } from './utils'; +import { flushIfServerless, sendErrorToSentry } from '../server-common/utils'; type PatchedLoadEvent = LoadEvent & SentryWrappedFlag; type PatchedServerLoadEvent = ServerLoadEvent & SentryWrappedFlag; diff --git a/packages/sveltekit/src/worker/serverRoute.ts b/packages/sveltekit/src/worker/serverRoute.ts index 803eecf74e56..b736272da2e1 100644 --- a/packages/sveltekit/src/worker/serverRoute.ts +++ b/packages/sveltekit/src/worker/serverRoute.ts @@ -1,7 +1,7 @@ import { addNonEnumerableProperty } from '@sentry/core'; import { SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN, SEMANTIC_ATTRIBUTE_SENTRY_SOURCE, startSpan } from '@sentry/cloudflare'; import type { RequestEvent } from '@sveltejs/kit'; -import { flushIfServerless, sendErrorToSentry } from './utils'; +import { flushIfServerless, sendErrorToSentry } from '../server-common/utils'; type PatchedServerRouteEvent = RequestEvent & { __sentry_wrapped__?: boolean }; diff --git a/packages/sveltekit/src/worker/utils.ts b/packages/sveltekit/src/worker/utils.ts deleted file mode 100644 index 397f58f6b7ae..000000000000 --- a/packages/sveltekit/src/worker/utils.ts +++ /dev/null @@ -1,72 +0,0 @@ -import { logger, objectify } from '@sentry/core'; -import { captureException, flush } from '@sentry/cloudflare'; -import type { RequestEvent } from '@sveltejs/kit'; - -import { DEBUG_BUILD } from '../common/debug-build'; -import { isHttpError, isRedirect } from '../common/utils'; - -/** - * Takes a request event and extracts traceparent and DSC data - * from the `sentry-trace` and `baggage` DSC headers. - * - * Sets propagation context as a side effect. - */ -export function getTracePropagationData(event: RequestEvent): { sentryTrace: string; baggage: string | null } { - const sentryTrace = event.request.headers.get('sentry-trace') || ''; - const baggage = event.request.headers.get('baggage'); - - return { sentryTrace, baggage }; -} - -/** Flush the event queue to ensure that events get sent to Sentry before the response is finished and the lambda ends */ -export async function flushIfServerless(): Promise { - const platformSupportsStreaming = !process.env.LAMBDA_TASK_ROOT && !process.env.VERCEL; - - if (!platformSupportsStreaming) { - try { - DEBUG_BUILD && logger.log('Flushing events...'); - await flush(2000); - DEBUG_BUILD && logger.log('Done flushing events'); - } catch (e) { - DEBUG_BUILD && logger.log('Error while flushing events:\n', e); - } - } -} - -/** - * Extracts a server-side sveltekit error, filters a couple of known errors we don't want to capture - * and captures the error via `captureException`. - * - * @param e error - * - * @returns an objectified version of @param e - */ -export function sendErrorToSentry(e: unknown, handlerFn: 'handle' | 'load' | 'serverRoute'): object { - // In case we have a primitive, wrap it in the equivalent wrapper class (string -> String, etc.) so that we can - // store a seen flag on it. - const objectifiedErr = objectify(e); - - // The error() helper is commonly used to throw errors in load functions: https://kit.svelte.dev/docs/modules#sveltejs-kit-error - // If we detect a thrown error that is an instance of HttpError, we don't want to capture 4xx errors as they - // could be noisy. - // Also the `redirect(...)` helper is used to redirect users from one page to another. We don't want to capture thrown - // `Redirect`s as they're not errors but expected behaviour - if ( - isRedirect(objectifiedErr) || - (isHttpError(objectifiedErr) && objectifiedErr.status < 500 && objectifiedErr.status >= 400) - ) { - return objectifiedErr; - } - - captureException(objectifiedErr, { - mechanism: { - type: 'sveltekit', - handled: false, - data: { - function: handlerFn, - }, - }, - }); - - return objectifiedErr; -} From d68d80cc43b0cedffd323a69c0362501a5d60397 Mon Sep 17 00:00:00 2001 From: Sam Greening <2552620+SG60@users.noreply.github.com> Date: Thu, 12 Dec 2024 11:44:16 +0000 Subject: [PATCH 04/31] chore(sveltekit): refactor the rest of the common server-side code --- .../{server => server-common}/handleError.ts | 3 +- .../src/{server => server-common}/load.ts | 10 +- .../rewriteFramesIntegration.ts | 4 +- .../{server => server-common}/serverRoute.ts | 10 +- packages/sveltekit/src/server/index.ts | 6 +- packages/sveltekit/src/server/sdk.ts | 2 +- packages/sveltekit/src/worker/handle.ts | 11 +- packages/sveltekit/src/worker/handleError.ts | 80 ------------ packages/sveltekit/src/worker/index.ts | 6 +- packages/sveltekit/src/worker/load.ts | 120 ------------------ packages/sveltekit/src/worker/serverRoute.ts | 67 ---------- 11 files changed, 32 insertions(+), 287 deletions(-) rename packages/sveltekit/src/{server => server-common}/handleError.ts (96%) rename packages/sveltekit/src/{server => server-common}/load.ts (94%) rename packages/sveltekit/src/{server => server-common}/rewriteFramesIntegration.ts (95%) rename packages/sveltekit/src/{server => server-common}/serverRoute.ts (89%) delete mode 100644 packages/sveltekit/src/worker/handleError.ts delete mode 100644 packages/sveltekit/src/worker/load.ts delete mode 100644 packages/sveltekit/src/worker/serverRoute.ts diff --git a/packages/sveltekit/src/server/handleError.ts b/packages/sveltekit/src/server-common/handleError.ts similarity index 96% rename from packages/sveltekit/src/server/handleError.ts rename to packages/sveltekit/src/server-common/handleError.ts index 761c098189a1..0f9782282e48 100644 --- a/packages/sveltekit/src/server/handleError.ts +++ b/packages/sveltekit/src/server-common/handleError.ts @@ -1,5 +1,4 @@ -import { consoleSandbox } from '@sentry/core'; -import { captureException } from '@sentry/node'; +import { captureException, consoleSandbox } from '@sentry/core'; import type { HandleServerError } from '@sveltejs/kit'; import { flushIfServerless } from '../server-common/utils'; diff --git a/packages/sveltekit/src/server/load.ts b/packages/sveltekit/src/server-common/load.ts similarity index 94% rename from packages/sveltekit/src/server/load.ts rename to packages/sveltekit/src/server-common/load.ts index e48fa070bf38..3113e8482ff7 100644 --- a/packages/sveltekit/src/server/load.ts +++ b/packages/sveltekit/src/server-common/load.ts @@ -1,9 +1,13 @@ -import { addNonEnumerableProperty } from '@sentry/core'; -import { SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN, SEMANTIC_ATTRIBUTE_SENTRY_SOURCE, startSpan } from '@sentry/node'; +import { + addNonEnumerableProperty, + SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN, + SEMANTIC_ATTRIBUTE_SENTRY_SOURCE, + startSpan, +} from '@sentry/core'; import type { LoadEvent, ServerLoadEvent } from '@sveltejs/kit'; import type { SentryWrappedFlag } from '../common/utils'; -import { flushIfServerless, sendErrorToSentry } from '../server-common/utils'; +import { flushIfServerless, sendErrorToSentry } from './utils'; type PatchedLoadEvent = LoadEvent & SentryWrappedFlag; type PatchedServerLoadEvent = ServerLoadEvent & SentryWrappedFlag; diff --git a/packages/sveltekit/src/server/rewriteFramesIntegration.ts b/packages/sveltekit/src/server-common/rewriteFramesIntegration.ts similarity index 95% rename from packages/sveltekit/src/server/rewriteFramesIntegration.ts rename to packages/sveltekit/src/server-common/rewriteFramesIntegration.ts index 44afbca2d6df..121cb601f6b9 100644 --- a/packages/sveltekit/src/server/rewriteFramesIntegration.ts +++ b/packages/sveltekit/src/server-common/rewriteFramesIntegration.ts @@ -53,8 +53,8 @@ export function rewriteFramesIteratee(frame: StackFrame): StackFrame { if (isWindowsFrame || startsWithSlash) { const filename = isWindowsFrame ? frame.filename - .replace(/^[a-zA-Z]:/, '') // remove Windows-style prefix - .replace(/\\/g, '/') // replace all `\\` instances with `/` + .replace(/^[a-zA-Z]:/, '') // remove Windows-style prefix + .replace(/\\/g, '/') // replace all `\\` instances with `/` : frame.filename; let strippedFilename; diff --git a/packages/sveltekit/src/server/serverRoute.ts b/packages/sveltekit/src/server-common/serverRoute.ts similarity index 89% rename from packages/sveltekit/src/server/serverRoute.ts rename to packages/sveltekit/src/server-common/serverRoute.ts index 5f0edd38b8ff..72607318ecb3 100644 --- a/packages/sveltekit/src/server/serverRoute.ts +++ b/packages/sveltekit/src/server-common/serverRoute.ts @@ -1,7 +1,11 @@ -import { addNonEnumerableProperty } from '@sentry/core'; -import { SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN, SEMANTIC_ATTRIBUTE_SENTRY_SOURCE, startSpan } from '@sentry/node'; +import { + addNonEnumerableProperty, + SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN, + SEMANTIC_ATTRIBUTE_SENTRY_SOURCE, + startSpan, +} from '@sentry/core'; import type { RequestEvent } from '@sveltejs/kit'; -import { flushIfServerless, sendErrorToSentry } from '../server-common/utils'; +import { flushIfServerless, sendErrorToSentry } from './utils'; type PatchedServerRouteEvent = RequestEvent & { __sentry_wrapped__?: boolean }; diff --git a/packages/sveltekit/src/server/index.ts b/packages/sveltekit/src/server/index.ts index 232e0562eb22..88da3cea13ba 100644 --- a/packages/sveltekit/src/server/index.ts +++ b/packages/sveltekit/src/server/index.ts @@ -123,10 +123,10 @@ export * from '@sentry/node'; // ------------------------- // SvelteKit SDK exports: export { init } from './sdk'; -export { handleErrorWithSentry } from './handleError'; -export { wrapLoadWithSentry, wrapServerLoadWithSentry } from './load'; +export { handleErrorWithSentry } from '../server-common/handleError'; +export { wrapLoadWithSentry, wrapServerLoadWithSentry } from '../server-common/load'; export { sentryHandle } from './handle'; -export { wrapServerRouteWithSentry } from './serverRoute'; +export { wrapServerRouteWithSentry } from '../server-common/serverRoute'; /** * Tracks the Svelte component's initialization and mounting operation as well as diff --git a/packages/sveltekit/src/server/sdk.ts b/packages/sveltekit/src/server/sdk.ts index 7f3acbf57fbd..60e6d8e9824c 100644 --- a/packages/sveltekit/src/server/sdk.ts +++ b/packages/sveltekit/src/server/sdk.ts @@ -3,7 +3,7 @@ import type { NodeClient, NodeOptions } from '@sentry/node'; import { getDefaultIntegrations as getDefaultNodeIntegrations } from '@sentry/node'; import { init as initNodeSdk } from '@sentry/node'; -import { rewriteFramesIntegration } from './rewriteFramesIntegration'; +import { rewriteFramesIntegration } from '../server-common/rewriteFramesIntegration'; /** * diff --git a/packages/sveltekit/src/worker/handle.ts b/packages/sveltekit/src/worker/handle.ts index 363a07adc3bd..fb391942e362 100644 --- a/packages/sveltekit/src/worker/handle.ts +++ b/packages/sveltekit/src/worker/handle.ts @@ -1,7 +1,9 @@ import { CloudflareOptions, continueTrace, wrapRequestHandler } from '@sentry/cloudflare'; +import { getDefaultIntegrations as getDefaultCloudflareIntegrations } from '@sentry/cloudflare'; import type { Handle } from '@sveltejs/kit'; import { sentryHandleGeneric, SentryHandleOptions } from '../server-common/handle'; +import { rewriteFramesIntegration } from '../server-common/rewriteFramesIntegration'; /** * A SvelteKit handle function that wraps the request for Sentry error and @@ -29,15 +31,18 @@ export function sentryHandle(handlerOptions?: SentryHandleOptions): Handle { /** Initializes Sentry SvelteKit Cloudflare SDK * This should be before the sentryHandle() call. * */ -export function initCloudflareSentryHandle(handlerOptions: CloudflareOptions): Handle { - const options = { ...handlerOptions }; +export function initCloudflareSentryHandle(options: CloudflareOptions): Handle { + const opts: CloudflareOptions = { + defaultIntegrations: [...getDefaultCloudflareIntegrations(options), rewriteFramesIntegration()], + ...options, + }; const handleInitSentry: Handle = ({ event, resolve }) => { // if event.platform exists (should be there in a cloudflare worker), then do the cloudflare sentry init return event.platform ? wrapRequestHandler( { - options, + options: opts, request: event.request, // @ts-expect-error This will exist in Cloudflare context: event.platform.context, diff --git a/packages/sveltekit/src/worker/handleError.ts b/packages/sveltekit/src/worker/handleError.ts deleted file mode 100644 index e05ac76ec627..000000000000 --- a/packages/sveltekit/src/worker/handleError.ts +++ /dev/null @@ -1,80 +0,0 @@ -import { consoleSandbox } from '@sentry/core'; -import { captureException } from '@sentry/cloudflare'; -import type { HandleServerError } from '@sveltejs/kit'; - -import { flushIfServerless } from '../server-common/utils'; - -// The SvelteKit default error handler just logs the error's stack trace to the console -// see: https://github.com/sveltejs/kit/blob/369e7d6851f543a40c947e033bfc4a9506fdc0a8/packages/kit/src/runtime/server/index.js#L43 -function defaultErrorHandler({ error }: Parameters[0]): ReturnType { - // @ts-expect-error this conforms to the default implementation (including this ts-expect-error) - // eslint-disable-next-line no-console - consoleSandbox(() => console.error(error && error.stack)); -} - -type HandleServerErrorInput = Parameters[0]; - -/** - * Backwards-compatible HandleServerError Input type for SvelteKit 1.x and 2.x - * `message` and `status` were added in 2.x. - * For backwards-compatibility, we make them optional - * - * @see https://kit.svelte.dev/docs/migrating-to-sveltekit-2#improved-error-handling - */ -type SafeHandleServerErrorInput = Omit & - Partial>; - -/** - * Wrapper for the SvelteKit error handler that sends the error to Sentry. - * - * @param handleError The original SvelteKit error handler. - */ -export function handleErrorWithSentry(handleError: HandleServerError = defaultErrorHandler): HandleServerError { - return async (input: SafeHandleServerErrorInput): Promise => { - if (isNotFoundError(input)) { - // We're extra cautious with SafeHandleServerErrorInput - this type is not compatible with HandleServerErrorInput - // @ts-expect-error - we're still passing the same object, just with a different (backwards-compatible) type - return handleError(input); - } - - captureException(input.error, { - mechanism: { - type: 'sveltekit', - handled: false, - }, - }); - - await flushIfServerless(); - - // We're extra cautious with SafeHandleServerErrorInput - this type is not compatible with HandleServerErrorInput - // @ts-expect-error - we're still passing the same object, just with a different (backwards-compatible) type - return handleError(input); - }; -} - -/** - * When a page request fails because the page is not found, SvelteKit throws a "Not found" error. - */ -function isNotFoundError(input: SafeHandleServerErrorInput): boolean { - const { error, event, status } = input; - - // SvelteKit 2.0 offers a reliable way to check for a Not Found error: - if (status === 404) { - return true; - } - - // SvelteKit 1.x doesn't offer a reliable way to check for a Not Found error. - // So we check the route id (shouldn't exist) and the raw stack trace - // We can delete all of this below whenever we drop Kit 1.x support - const hasNoRouteId = !event.route || !event.route.id; - - const rawStack: string = - (error != null && - typeof error === 'object' && - 'stack' in error && - typeof error.stack === 'string' && - error.stack) || - ''; - - return hasNoRouteId && rawStack.startsWith('Error: Not found:'); -} diff --git a/packages/sveltekit/src/worker/index.ts b/packages/sveltekit/src/worker/index.ts index 0159dda0dd1f..9947d60fe5d9 100644 --- a/packages/sveltekit/src/worker/index.ts +++ b/packages/sveltekit/src/worker/index.ts @@ -8,10 +8,10 @@ // // ------------------------- // SvelteKit SDK exports: -export { handleErrorWithSentry } from './handleError'; -export { wrapLoadWithSentry, wrapServerLoadWithSentry } from './load'; +export { handleErrorWithSentry } from '../server-common/handleError'; +export { wrapLoadWithSentry, wrapServerLoadWithSentry } from '../server-common/load'; export { sentryHandle, initCloudflareSentryHandle } from './handle'; -export { wrapServerRouteWithSentry } from './serverRoute'; +export { wrapServerRouteWithSentry } from '../server-common/serverRoute'; // Re-export some functions from Cloudflare SDK export { diff --git a/packages/sveltekit/src/worker/load.ts b/packages/sveltekit/src/worker/load.ts deleted file mode 100644 index 863315607816..000000000000 --- a/packages/sveltekit/src/worker/load.ts +++ /dev/null @@ -1,120 +0,0 @@ -import { addNonEnumerableProperty } from '@sentry/core'; -import { SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN, SEMANTIC_ATTRIBUTE_SENTRY_SOURCE, startSpan } from '@sentry/cloudflare'; -import type { LoadEvent, ServerLoadEvent } from '@sveltejs/kit'; - -import type { SentryWrappedFlag } from '../common/utils'; -import { flushIfServerless, sendErrorToSentry } from '../server-common/utils'; - -type PatchedLoadEvent = LoadEvent & SentryWrappedFlag; -type PatchedServerLoadEvent = ServerLoadEvent & SentryWrappedFlag; - -/** - * @inheritdoc - */ -// The liberal generic typing of `T` is necessary because we cannot let T extend `Load`. -// This function needs to tell TS that it returns exactly the type that it was called with -// because SvelteKit generates the narrowed down `PageLoad` or `LayoutLoad` types -// at build time for every route. -// eslint-disable-next-line @typescript-eslint/no-explicit-any -export function wrapLoadWithSentry any>(origLoad: T): T { - return new Proxy(origLoad, { - apply: async (wrappingTarget, thisArg, args: Parameters) => { - // Type casting here because `T` cannot extend `Load` (see comment above function signature) - // Also, this event possibly already has a sentry wrapped flag attached - const event = args[0] as PatchedLoadEvent; - - if (event.__sentry_wrapped__) { - return wrappingTarget.apply(thisArg, args); - } - - addNonEnumerableProperty(event as unknown as Record, '__sentry_wrapped__', true); - - const routeId = event.route && event.route.id; - - try { - // We need to await before returning, otherwise we won't catch any errors thrown by the load function - return await startSpan( - { - op: 'function.sveltekit.load', - attributes: { - [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.function.sveltekit', - [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: routeId ? 'route' : 'url', - }, - name: routeId ? routeId : event.url.pathname, - }, - () => wrappingTarget.apply(thisArg, args), - ); - } catch (e) { - sendErrorToSentry(e, 'load'); - throw e; - } finally { - await flushIfServerless(); - } - }, - }); -} - -/** - * Wrap a server-only load function (e.g. +page.server.js or +layout.server.js) with Sentry functionality - * - * Usage: - * - * ```js - * // +page.serverjs - * - * import { wrapServerLoadWithSentry } - * - * export const load = wrapServerLoadWithSentry((event) => { - * // your load code - * }); - * ``` - * - * @param origServerLoad SvelteKit user defined server-only load function - */ -// The liberal generic typing of `T` is necessary because we cannot let T extend `ServerLoad`. -// This function needs to tell TS that it returns exactly the type that it was called with -// because SvelteKit generates the narrowed down `PageServerLoad` or `LayoutServerLoad` types -// at build time for every route. -// eslint-disable-next-line @typescript-eslint/no-explicit-any -export function wrapServerLoadWithSentry any>(origServerLoad: T): T { - return new Proxy(origServerLoad, { - apply: async (wrappingTarget, thisArg, args: Parameters) => { - // Type casting here because `T` cannot extend `ServerLoad` (see comment above function signature) - // Also, this event possibly already has a sentry wrapped flag attached - const event = args[0] as PatchedServerLoadEvent; - - if (event.__sentry_wrapped__) { - return wrappingTarget.apply(thisArg, args); - } - - addNonEnumerableProperty(event as unknown as Record, '__sentry_wrapped__', true); - - // Accessing any member of `event.route` causes SvelteKit to invalidate the - // server `load` function's data on every route change. - // To work around this, we use `Object.getOwnPropertyDescriptor` which doesn't invoke the proxy. - // https://github.com/sveltejs/kit/blob/e133aba479fa9ba0e7f9e71512f5f937f0247e2c/packages/kit/src/runtime/server/page/load_data.js#L111C3-L124 - const routeId = event.route && (Object.getOwnPropertyDescriptor(event.route, 'id')?.value as string | undefined); - - try { - // We need to await before returning, otherwise we won't catch any errors thrown by the load function - return await startSpan( - { - op: 'function.sveltekit.server.load', - attributes: { - [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.function.sveltekit', - [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: routeId ? 'route' : 'url', - 'http.method': event.request.method, - }, - name: routeId ? routeId : event.url.pathname, - }, - () => wrappingTarget.apply(thisArg, args), - ); - } catch (e: unknown) { - sendErrorToSentry(e, 'load'); - throw e; - } finally { - await flushIfServerless(); - } - }, - }); -} diff --git a/packages/sveltekit/src/worker/serverRoute.ts b/packages/sveltekit/src/worker/serverRoute.ts deleted file mode 100644 index b736272da2e1..000000000000 --- a/packages/sveltekit/src/worker/serverRoute.ts +++ /dev/null @@ -1,67 +0,0 @@ -import { addNonEnumerableProperty } from '@sentry/core'; -import { SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN, SEMANTIC_ATTRIBUTE_SENTRY_SOURCE, startSpan } from '@sentry/cloudflare'; -import type { RequestEvent } from '@sveltejs/kit'; -import { flushIfServerless, sendErrorToSentry } from '../server-common/utils'; - -type PatchedServerRouteEvent = RequestEvent & { __sentry_wrapped__?: boolean }; - -/** - * Wraps a server route handler for API or server routes registered in `+server.(js|js)` files. - * - * This function will automatically capture any errors that occur during the execution of the route handler - * and it will start a span for the duration of your route handler. - * - * @example - * ```js - * import { wrapServerRouteWithSentry } from '@sentry/sveltekit'; - * - * const get = async event => { - * return new Response(JSON.stringify({ message: 'hello world' })); - * } - * - * export const GET = wrapServerRouteWithSentry(get); - * ``` - * - * @param originalRouteHandler your server route handler - * @param httpMethod the HTTP method of your route handler - * - * @returns a wrapped version of your server route handler - */ -export function wrapServerRouteWithSentry( - originalRouteHandler: (request: T) => Promise, -): (requestEvent: T) => Promise { - return new Proxy(originalRouteHandler, { - apply: async (wrappingTarget, thisArg, args) => { - const event = args[0] as PatchedServerRouteEvent; - - if (event.__sentry_wrapped__) { - return wrappingTarget.apply(thisArg, args); - } - - const routeId = event.route && event.route.id; - const httpMethod = event.request.method; - - addNonEnumerableProperty(event as unknown as Record, '__sentry_wrapped__', true); - - try { - return await startSpan( - { - name: `${httpMethod} ${routeId || 'Server Route'}`, - op: `function.sveltekit.server.${httpMethod.toLowerCase()}`, - attributes: { - [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.function.sveltekit', - [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'route', - }, - onlyIfParent: true, - }, - () => wrappingTarget.apply(thisArg, args), - ); - } catch (e) { - sendErrorToSentry(e, 'serverRoute'); - throw e; - } finally { - await flushIfServerless(); - } - }, - }); -} From 6539014374f2bcc37d3f4e8e5b5240d2868151eb Mon Sep 17 00:00:00 2001 From: Sam Greening <2552620+SG60@users.noreply.github.com> Date: Thu, 12 Dec 2024 12:21:42 +0000 Subject: [PATCH 05/31] fix(sveltekit): avoid importing fs and path in workers contexts --- .../sveltekit/src/server-common/rewriteFramesIntegration.ts | 2 +- packages/sveltekit/src/vite/autoInstrument.ts | 2 +- packages/sveltekit/src/vite/constants.ts | 1 + packages/sveltekit/src/vite/sourceMaps.ts | 2 +- 4 files changed, 4 insertions(+), 3 deletions(-) create mode 100644 packages/sveltekit/src/vite/constants.ts diff --git a/packages/sveltekit/src/server-common/rewriteFramesIntegration.ts b/packages/sveltekit/src/server-common/rewriteFramesIntegration.ts index 121cb601f6b9..3ed3e72c1f49 100644 --- a/packages/sveltekit/src/server-common/rewriteFramesIntegration.ts +++ b/packages/sveltekit/src/server-common/rewriteFramesIntegration.ts @@ -7,7 +7,7 @@ import { join, rewriteFramesIntegration as originalRewriteFramesIntegration, } from '@sentry/core'; -import { WRAPPED_MODULE_SUFFIX } from '../vite/autoInstrument'; +import { WRAPPED_MODULE_SUFFIX } from '../vite/constants'; import type { GlobalWithSentryValues } from '../vite/injectGlobalValues'; type StackFrameIteratee = (frame: StackFrame) => StackFrame; diff --git a/packages/sveltekit/src/vite/autoInstrument.ts b/packages/sveltekit/src/vite/autoInstrument.ts index 1e11f2f61500..ef11981b4262 100644 --- a/packages/sveltekit/src/vite/autoInstrument.ts +++ b/packages/sveltekit/src/vite/autoInstrument.ts @@ -4,7 +4,7 @@ import type { ExportNamedDeclaration } from '@babel/types'; import { parseModule } from 'magicast'; import type { Plugin } from 'vite'; -export const WRAPPED_MODULE_SUFFIX = '?sentry-auto-wrap'; +import { WRAPPED_MODULE_SUFFIX } from './constants'; export type AutoInstrumentSelection = { /** diff --git a/packages/sveltekit/src/vite/constants.ts b/packages/sveltekit/src/vite/constants.ts new file mode 100644 index 000000000000..a0e160fdd272 --- /dev/null +++ b/packages/sveltekit/src/vite/constants.ts @@ -0,0 +1 @@ +export const WRAPPED_MODULE_SUFFIX = '?sentry-auto-wrap'; diff --git a/packages/sveltekit/src/vite/sourceMaps.ts b/packages/sveltekit/src/vite/sourceMaps.ts index 799688b33845..303bf2983cf3 100644 --- a/packages/sveltekit/src/vite/sourceMaps.ts +++ b/packages/sveltekit/src/vite/sourceMaps.ts @@ -9,7 +9,7 @@ import { sentryVitePlugin } from '@sentry/vite-plugin'; import type { Plugin, UserConfig } from 'vite'; import MagicString from 'magic-string'; -import { WRAPPED_MODULE_SUFFIX } from './autoInstrument'; +import { WRAPPED_MODULE_SUFFIX } from './constants'; import type { GlobalSentryValues } from './injectGlobalValues'; import { VIRTUAL_GLOBAL_VALUES_FILE, getGlobalValueInjectionCode } from './injectGlobalValues'; import { getAdapterOutputDir, getHooksFileName, loadSvelteConfig } from './svelteConfig'; From e3e4b61e854b593aa8549d714e59d13a577cf6bd Mon Sep 17 00:00:00 2001 From: Sam Greening <2552620+SG60@users.noreply.github.com> Date: Tue, 17 Dec 2024 11:21:50 +0000 Subject: [PATCH 06/31] test(sveltekit-cloudflare-pages): add an e2e test for @sentry/sveltekit on cloudflare pages Seems to have an issue with wrangler hanging during the playwright test though. --- .../sveltekit-cloudflare-pages/.gitignore | 11 + .../sveltekit-cloudflare-pages/.npmrc | 2 + .../sveltekit-cloudflare-pages/README.md | 41 ++ .../sveltekit-cloudflare-pages/package.json | 36 ++ .../playwright.config.mjs | 8 + .../sveltekit-cloudflare-pages/src/app.d.ts | 13 + .../sveltekit-cloudflare-pages/src/app.html | 12 + .../src/hooks.client.ts | 23 ++ .../src/hooks.server.ts | 17 + .../src/routes/+layout.svelte | 15 + .../src/routes/+page.svelte | 41 ++ .../src/routes/api/users/+server.ts | 3 + .../src/routes/building/+page.server.ts | 5 + .../src/routes/building/+page.svelte | 21 + .../src/routes/building/+page.ts | 5 + .../src/routes/client-error/+page.svelte | 9 + .../src/routes/components/+page.svelte | 15 + .../src/routes/components/Component1.svelte | 10 + .../src/routes/components/Component2.svelte | 9 + .../src/routes/components/Component3.svelte | 6 + .../src/routes/nav1/+page.svelte | 1 + .../src/routes/nav2/+page.svelte | 1 + .../src/routes/redirect1/+page.ts | 5 + .../src/routes/redirect2/+page.ts | 5 + .../routes/server-load-error/+page.server.ts | 6 + .../src/routes/server-load-error/+page.svelte | 9 + .../routes/server-load-fetch/+page.server.ts | 5 + .../src/routes/server-load-fetch/+page.svelte | 8 + .../routes/server-route-error/+page.svelte | 9 + .../src/routes/server-route-error/+page.ts | 7 + .../src/routes/server-route-error/+server.ts | 6 + .../routes/universal-load-error/+page.svelte | 17 + .../src/routes/universal-load-error/+page.ts | 8 + .../routes/universal-load-fetch/+page.svelte | 14 + .../src/routes/universal-load-fetch/+page.ts | 5 + .../src/routes/users/+page.server.ts | 5 + .../src/routes/users/+page.svelte | 10 + .../src/routes/users/[id]/+page.server.ts | 5 + .../src/routes/users/[id]/+page.svelte | 14 + .../start-event-proxy.mjs | 6 + .../static/favicon.png | Bin 0 -> 1571 bytes .../svelte.config.js | 18 + .../tests/errors.client.test.ts | 56 +++ .../tests/errors.server.test.ts | 90 +++++ .../tests/performance.client.test.ts | 139 +++++++ .../tests/performance.server.test.ts | 44 ++ .../tests/performance.test.ts | 381 ++++++++++++++++++ .../sveltekit-cloudflare-pages/tests/utils.ts | 49 +++ .../sveltekit-cloudflare-pages/tsconfig.json | 19 + .../sveltekit-cloudflare-pages/vite.config.ts | 12 + .../sveltekit-cloudflare-pages/wrangler.toml | 2 + 51 files changed, 1258 insertions(+) create mode 100644 dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/.gitignore create mode 100644 dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/.npmrc create mode 100644 dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/README.md create mode 100644 dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/package.json create mode 100644 dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/playwright.config.mjs create mode 100644 dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/src/app.d.ts create mode 100644 dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/src/app.html create mode 100644 dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/src/hooks.client.ts create mode 100644 dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/src/hooks.server.ts create mode 100644 dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/src/routes/+layout.svelte create mode 100644 dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/src/routes/+page.svelte create mode 100644 dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/src/routes/api/users/+server.ts create mode 100644 dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/src/routes/building/+page.server.ts create mode 100644 dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/src/routes/building/+page.svelte create mode 100644 dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/src/routes/building/+page.ts create mode 100644 dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/src/routes/client-error/+page.svelte create mode 100644 dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/src/routes/components/+page.svelte create mode 100644 dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/src/routes/components/Component1.svelte create mode 100644 dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/src/routes/components/Component2.svelte create mode 100644 dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/src/routes/components/Component3.svelte create mode 100644 dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/src/routes/nav1/+page.svelte create mode 100644 dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/src/routes/nav2/+page.svelte create mode 100644 dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/src/routes/redirect1/+page.ts create mode 100644 dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/src/routes/redirect2/+page.ts create mode 100644 dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/src/routes/server-load-error/+page.server.ts create mode 100644 dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/src/routes/server-load-error/+page.svelte create mode 100644 dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/src/routes/server-load-fetch/+page.server.ts create mode 100644 dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/src/routes/server-load-fetch/+page.svelte create mode 100644 dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/src/routes/server-route-error/+page.svelte create mode 100644 dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/src/routes/server-route-error/+page.ts create mode 100644 dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/src/routes/server-route-error/+server.ts create mode 100644 dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/src/routes/universal-load-error/+page.svelte create mode 100644 dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/src/routes/universal-load-error/+page.ts create mode 100644 dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/src/routes/universal-load-fetch/+page.svelte create mode 100644 dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/src/routes/universal-load-fetch/+page.ts create mode 100644 dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/src/routes/users/+page.server.ts create mode 100644 dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/src/routes/users/+page.svelte create mode 100644 dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/src/routes/users/[id]/+page.server.ts create mode 100644 dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/src/routes/users/[id]/+page.svelte create mode 100644 dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/start-event-proxy.mjs create mode 100644 dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/static/favicon.png create mode 100644 dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/svelte.config.js create mode 100644 dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/tests/errors.client.test.ts create mode 100644 dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/tests/errors.server.test.ts create mode 100644 dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/tests/performance.client.test.ts create mode 100644 dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/tests/performance.server.test.ts create mode 100644 dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/tests/performance.test.ts create mode 100644 dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/tests/utils.ts create mode 100644 dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/tsconfig.json create mode 100644 dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/vite.config.ts create mode 100644 dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/wrangler.toml diff --git a/dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/.gitignore b/dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/.gitignore new file mode 100644 index 000000000000..291fe69f6e16 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/.gitignore @@ -0,0 +1,11 @@ +.DS_Store +node_modules +/build +/.svelte-kit +/.wrangler +/package +.env +.env.* +!.env.example +vite.config.js.timestamp-* +vite.config.ts.timestamp-* diff --git a/dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/.npmrc b/dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/.npmrc new file mode 100644 index 000000000000..070f80f05092 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/.npmrc @@ -0,0 +1,2 @@ +@sentry:registry=http://127.0.0.1:4873 +@sentry-internal:registry=http://127.0.0.1:4873 diff --git a/dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/README.md b/dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/README.md new file mode 100644 index 000000000000..684cabccfe02 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/README.md @@ -0,0 +1,41 @@ +# create-svelte + +Everything you need to build a Svelte project, powered by +[`create-svelte`](https://github.com/sveltejs/kit/tree/main/packages/create-svelte). + +## Creating a project + +If you're seeing this, you've probably already done this step. Congrats! + +```bash +# create a new project in the current directory +npm create svelte@latest + +# create a new project in my-app +npm create svelte@latest my-app +``` + +## Developing + +Once you've created a project and installed dependencies with `npm install` (or `pnpm install` or `yarn`), start a +development server: + +```bash +npm run dev + +# or start the server and open the app in a new browser tab +npm run dev -- --open +``` + +## Building + +To create a production version of your app: + +```bash +npm run build +``` + +You can preview the production build with `npm run preview`. + +> To deploy your app, you may need to install an [adapter](https://kit.svelte.dev/docs/adapters) for your target +> environment. diff --git a/dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/package.json b/dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/package.json new file mode 100644 index 000000000000..5d85a7be445b --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/package.json @@ -0,0 +1,36 @@ +{ + "name": "sveltekit-cloudflare-pages", + "version": "0.0.1", + "private": true, + "scripts": { + "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" + }, + "dependencies": { + "@sentry/sveltekit": "latest || *", + "@spotlightjs/spotlight": "2.0.0-alpha.1" + }, + "devDependencies": { + "@playwright/test": "^1.44.1", + "@sentry-internal/test-utils": "link:../../../test-utils", + "@sentry/core": "latest || *", + "@sveltejs/adapter-cloudflare": "^4.0.0", + "@sveltejs/kit": "^2.0.0", + "@sveltejs/vite-plugin-svelte": "^4.0.0", + "svelte": "^5.0.0", + "svelte-check": "^3.6.0", + "tslib": "^2.4.1", + "typescript": "^5.0.0", + "vite": "^5.4.10", + "wrangler": "^3.95.0" + }, + "type": "module" +} diff --git a/dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/playwright.config.mjs b/dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/playwright.config.mjs new file mode 100644 index 000000000000..3135c8dd5f88 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/playwright.config.mjs @@ -0,0 +1,8 @@ +import { getPlaywrightConfig } from '@sentry-internal/test-utils'; + +const config = getPlaywrightConfig({ + startCommand: 'pnpm wrangler pages dev --port 3030 .svelte-kit/cloudflare', + port: 3030, +}); + +export default config; diff --git a/dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/src/app.d.ts b/dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/src/app.d.ts new file mode 100644 index 000000000000..ede601ab93e2 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/src/app.d.ts @@ -0,0 +1,13 @@ +// See https://kit.svelte.dev/docs/types#app +// for information about these interfaces +declare global { + namespace App { + // interface Error {} + // interface Locals {} + // interface PageData {} + // interface PageState {} + // interface Platform {} + } +} + +export {}; diff --git a/dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/src/app.html b/dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/src/app.html new file mode 100644 index 000000000000..77a5ff52c923 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/src/app.html @@ -0,0 +1,12 @@ + + + + + + + %sveltekit.head% + + +
%sveltekit.body%
+ + diff --git a/dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/src/hooks.client.ts b/dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/src/hooks.client.ts new file mode 100644 index 000000000000..91592e7ab932 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/src/hooks.client.ts @@ -0,0 +1,23 @@ +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 to keep transactions + dsn: env.PUBLIC_E2E_TEST_DSN, + debug: !!env.PUBLIC_DEBUG, + tunnel: `http://localhost:3031/`, // proxy server + 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, + }); +} diff --git a/dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/src/hooks.server.ts b/dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/src/hooks.server.ts new file mode 100644 index 000000000000..af0312c62357 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/src/hooks.server.ts @@ -0,0 +1,17 @@ +import { E2E_TEST_DSN } from '$env/static/private'; +import * as Sentry from '@sentry/sveltekit'; +import { sequence } from '@sveltejs/kit/hooks'; + +// not logging anything to console to avoid noise in the test output +export const handleError = Sentry.handleErrorWithSentry(() => { }); + +export const handle = sequence( + Sentry.initCloudflareSentryHandle({ + environment: 'qa', // dynamic sampling bias to keep transactions + dsn: E2E_TEST_DSN, + debug: !!process.env.DEBUG, + tunnel: `http://localhost:3031/`, // proxy server + tracesSampleRate: 1.0, + }), + Sentry.sentryHandle(), +); diff --git a/dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/src/routes/+layout.svelte b/dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/src/routes/+layout.svelte new file mode 100644 index 000000000000..d1fadd2ea5a3 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/src/routes/+layout.svelte @@ -0,0 +1,15 @@ + + +

Sveltekit E2E Test app

+
+ +
diff --git a/dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/src/routes/+page.svelte b/dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/src/routes/+page.svelte new file mode 100644 index 000000000000..575e6827693f --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/src/routes/+page.svelte @@ -0,0 +1,41 @@ +

Welcome to SvelteKit 2 with Svelte 5!

+

Visit kit.svelte.dev to read the documentation

+ + diff --git a/dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/src/routes/api/users/+server.ts b/dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/src/routes/api/users/+server.ts new file mode 100644 index 000000000000..d0e4371c594b --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/src/routes/api/users/+server.ts @@ -0,0 +1,3 @@ +export const GET = () => { + return new Response(JSON.stringify({ users: ['alice', 'bob', 'carol'] })); +}; diff --git a/dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/src/routes/building/+page.server.ts b/dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/src/routes/building/+page.server.ts new file mode 100644 index 000000000000..b07376ba97c9 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/src/routes/building/+page.server.ts @@ -0,0 +1,5 @@ +import type { PageServerLoad } from './$types'; + +export const load = (async _event => { + return { name: 'building (server)' }; +}) satisfies PageServerLoad; diff --git a/dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/src/routes/building/+page.svelte b/dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/src/routes/building/+page.svelte new file mode 100644 index 000000000000..b27edb70053d --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/src/routes/building/+page.svelte @@ -0,0 +1,21 @@ + + +

Check Build

+ +

+ This route only exists to check that Typescript definitions + and auto instrumentation are working when the project is built. +

diff --git a/dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/src/routes/building/+page.ts b/dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/src/routes/building/+page.ts new file mode 100644 index 000000000000..049acdc1fafa --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/src/routes/building/+page.ts @@ -0,0 +1,5 @@ +import type { PageLoad } from './$types'; + +export const load = (async _event => { + return { name: 'building' }; +}) satisfies PageLoad; diff --git a/dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/src/routes/client-error/+page.svelte b/dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/src/routes/client-error/+page.svelte new file mode 100644 index 000000000000..ba6b464e9324 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/src/routes/client-error/+page.svelte @@ -0,0 +1,9 @@ + + +

Client error

+ + diff --git a/dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/src/routes/components/+page.svelte b/dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/src/routes/components/+page.svelte new file mode 100644 index 000000000000..eff3fa3f2e8d --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/src/routes/components/+page.svelte @@ -0,0 +1,15 @@ + +

Demonstrating Component Tracking

+ + + + diff --git a/dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/src/routes/components/Component1.svelte b/dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/src/routes/components/Component1.svelte new file mode 100644 index 000000000000..a675711e4b68 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/src/routes/components/Component1.svelte @@ -0,0 +1,10 @@ + +

Howdy, I'm component 1

+ + diff --git a/dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/src/routes/components/Component2.svelte b/dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/src/routes/components/Component2.svelte new file mode 100644 index 000000000000..2b2f38308077 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/src/routes/components/Component2.svelte @@ -0,0 +1,9 @@ + +

Howdy, I'm component 2

+ + diff --git a/dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/src/routes/components/Component3.svelte b/dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/src/routes/components/Component3.svelte new file mode 100644 index 000000000000..9b4e028f78e7 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/src/routes/components/Component3.svelte @@ -0,0 +1,6 @@ + + +

Howdy, I'm component 3

diff --git a/dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/src/routes/nav1/+page.svelte b/dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/src/routes/nav1/+page.svelte new file mode 100644 index 000000000000..31abffc512a2 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/src/routes/nav1/+page.svelte @@ -0,0 +1 @@ +

Navigation 1

diff --git a/dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/src/routes/nav2/+page.svelte b/dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/src/routes/nav2/+page.svelte new file mode 100644 index 000000000000..20b44bb32da9 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/src/routes/nav2/+page.svelte @@ -0,0 +1 @@ +

Navigation 2

diff --git a/dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/src/routes/redirect1/+page.ts b/dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/src/routes/redirect1/+page.ts new file mode 100644 index 000000000000..3f462bf810fd --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/src/routes/redirect1/+page.ts @@ -0,0 +1,5 @@ +import { redirect } from '@sveltejs/kit'; + +export const load = async () => { + redirect(301, '/redirect2'); +}; diff --git a/dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/src/routes/redirect2/+page.ts b/dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/src/routes/redirect2/+page.ts new file mode 100644 index 000000000000..99a810761d18 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/src/routes/redirect2/+page.ts @@ -0,0 +1,5 @@ +import { redirect } from '@sveltejs/kit'; + +export const load = async () => { + redirect(301, '/users/789'); +}; diff --git a/dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/src/routes/server-load-error/+page.server.ts b/dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/src/routes/server-load-error/+page.server.ts new file mode 100644 index 000000000000..17dd53fb5bbb --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/src/routes/server-load-error/+page.server.ts @@ -0,0 +1,6 @@ +export const load = async () => { + throw new Error('Server Load Error'); + return { + msg: 'Hello World', + }; +}; diff --git a/dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/src/routes/server-load-error/+page.svelte b/dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/src/routes/server-load-error/+page.svelte new file mode 100644 index 000000000000..3a0942971d06 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/src/routes/server-load-error/+page.svelte @@ -0,0 +1,9 @@ + + +

Server load error

+ +

+ Message: {data.msg} +

diff --git a/dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/src/routes/server-load-fetch/+page.server.ts b/dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/src/routes/server-load-fetch/+page.server.ts new file mode 100644 index 000000000000..709e52bcf351 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/src/routes/server-load-fetch/+page.server.ts @@ -0,0 +1,5 @@ +export const load = async ({ fetch }) => { + const res = await fetch('/api/users'); + const data = await res.json(); + return { data }; +}; diff --git a/dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/src/routes/server-load-fetch/+page.svelte b/dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/src/routes/server-load-fetch/+page.svelte new file mode 100644 index 000000000000..f7f814d31b4d --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/src/routes/server-load-fetch/+page.svelte @@ -0,0 +1,8 @@ + + +
+

Server Load Fetch

+

{JSON.stringify(data, null, 2)}

+
diff --git a/dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/src/routes/server-route-error/+page.svelte b/dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/src/routes/server-route-error/+page.svelte new file mode 100644 index 000000000000..3d682e7e3462 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/src/routes/server-route-error/+page.svelte @@ -0,0 +1,9 @@ + + +

Server Route error

+ +

+ Message: {data.msg} +

diff --git a/dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/src/routes/server-route-error/+page.ts b/dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/src/routes/server-route-error/+page.ts new file mode 100644 index 000000000000..298240827714 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/src/routes/server-route-error/+page.ts @@ -0,0 +1,7 @@ +export const load = async ({ fetch }) => { + const res = await fetch('/server-route-error'); + const data = await res.json(); + return { + msg: data, + }; +}; diff --git a/dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/src/routes/server-route-error/+server.ts b/dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/src/routes/server-route-error/+server.ts new file mode 100644 index 000000000000..f1a4b94b7706 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/src/routes/server-route-error/+server.ts @@ -0,0 +1,6 @@ +export const GET = async () => { + throw new Error('Server Route Error'); + return { + msg: 'Hello World', + }; +}; diff --git a/dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/src/routes/universal-load-error/+page.svelte b/dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/src/routes/universal-load-error/+page.svelte new file mode 100644 index 000000000000..dc2d311a0ece --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/src/routes/universal-load-error/+page.svelte @@ -0,0 +1,17 @@ + + +

Universal load error

+ +

+ To trigger from client: Load on another route, then navigate to this route. +

+ +

+ To trigger from server: Load on this route +

+ +

+ Message: {data.msg} +

diff --git a/dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/src/routes/universal-load-error/+page.ts b/dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/src/routes/universal-load-error/+page.ts new file mode 100644 index 000000000000..3d72bf4a890f --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/src/routes/universal-load-error/+page.ts @@ -0,0 +1,8 @@ +import { browser } from '$app/environment'; + +export const load = async () => { + throw new Error(`Universal Load Error (${browser ? 'browser' : 'server'})`); + return { + msg: 'Hello World', + }; +}; diff --git a/dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/src/routes/universal-load-fetch/+page.svelte b/dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/src/routes/universal-load-fetch/+page.svelte new file mode 100644 index 000000000000..563c51e8c850 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/src/routes/universal-load-fetch/+page.svelte @@ -0,0 +1,14 @@ + + +

Fetching in universal load

+ +

Here's a list of a few users:

+ +
    + {#each data.users as user} +
  • {user}
  • + {/each} +
diff --git a/dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/src/routes/universal-load-fetch/+page.ts b/dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/src/routes/universal-load-fetch/+page.ts new file mode 100644 index 000000000000..63c1ee68e1cb --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/src/routes/universal-load-fetch/+page.ts @@ -0,0 +1,5 @@ +export const load = async ({ fetch }) => { + const usersRes = await fetch('/api/users'); + const data = await usersRes.json(); + return { users: data.users }; +}; diff --git a/dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/src/routes/users/+page.server.ts b/dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/src/routes/users/+page.server.ts new file mode 100644 index 000000000000..a34c5450f682 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/src/routes/users/+page.server.ts @@ -0,0 +1,5 @@ +export const load = async () => { + return { + msg: 'Hi everyone!', + }; +}; diff --git a/dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/src/routes/users/+page.svelte b/dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/src/routes/users/+page.svelte new file mode 100644 index 000000000000..aa804a4518fa --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/src/routes/users/+page.svelte @@ -0,0 +1,10 @@ + +

+ All Users: +

+ +

+ message: {data.msg} +

diff --git a/dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/src/routes/users/[id]/+page.server.ts b/dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/src/routes/users/[id]/+page.server.ts new file mode 100644 index 000000000000..9388f3927018 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/src/routes/users/[id]/+page.server.ts @@ -0,0 +1,5 @@ +export const load = async ({ params }) => { + return { + msg: `This is a special message for user ${params.id}`, + }; +}; diff --git a/dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/src/routes/users/[id]/+page.svelte b/dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/src/routes/users/[id]/+page.svelte new file mode 100644 index 000000000000..d348a8c57dad --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/src/routes/users/[id]/+page.svelte @@ -0,0 +1,14 @@ + + +

Route with dynamic params

+ +

+ User id: {$page.params.id} +

+ +

+ Secret message for user: {data.msg} +

diff --git a/dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/start-event-proxy.mjs b/dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/start-event-proxy.mjs new file mode 100644 index 000000000000..ad68e74306ea --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/start-event-proxy.mjs @@ -0,0 +1,6 @@ +import { startEventProxyServer } from '@sentry-internal/test-utils'; + +startEventProxyServer({ + port: 3031, + proxyServerName: 'sveltekit-cloudflare-pages', +}); diff --git a/dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/static/favicon.png b/dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/static/favicon.png new file mode 100644 index 0000000000000000000000000000000000000000..825b9e65af7c104cfb07089bb28659393b4f2097 GIT binary patch literal 1571 zcmV+;2Hg3HP)Px)-AP12RCwC$UE6KzI1p6{F2N z1VK2vi|pOpn{~#djwYcWXTI_im_u^TJgMZ4JMOsSj!0ma>B?-(Hr@X&W@|R-$}W@Z zgj#$x=!~7LGqHW?IO8+*oE1MyDp!G=L0#^lUx?;!fXv@l^6SvTnf^ac{5OurzC#ZMYc20lI%HhX816AYVs1T3heS1*WaWH z%;x>)-J}YB5#CLzU@GBR6sXYrD>Vw(Fmt#|JP;+}<#6b63Ike{Fuo!?M{yEffez;| zp!PfsuaC)>h>-AdbnwN13g*1LowNjT5?+lFVd#9$!8Z9HA|$*6dQ8EHLu}U|obW6f z2%uGv?vr=KNq7YYa2Roj;|zooo<)lf=&2yxM@e`kM$CmCR#x>gI>I|*Ubr({5Y^rb zghxQU22N}F51}^yfDSt786oMTc!W&V;d?76)9KXX1 z+6Okem(d}YXmmOiZq$!IPk5t8nnS{%?+vDFz3BevmFNgpIod~R{>@#@5x9zJKEHLHv!gHeK~n)Ld!M8DB|Kfe%~123&Hz1Z(86nU7*G5chmyDe ziV7$pB7pJ=96hpxHv9rCR29%bLOXlKU<_13_M8x)6;P8E1Kz6G<&P?$P^%c!M5`2` zfY2zg;VK5~^>TJGQzc+33-n~gKt{{of8GzUkWmU110IgI0DLxRIM>0US|TsM=L|@F z0Bun8U!cRB7-2apz=y-7*UxOxz@Z0)@QM)9wSGki1AZ38ceG7Q72z5`i;i=J`ILzL z@iUO?SBBG-0cQuo+an4TsLy-g-x;8P4UVwk|D8{W@U1Zi z!M)+jqy@nQ$p?5tsHp-6J304Q={v-B>66$P0IDx&YT(`IcZ~bZfmn11#rXd7<5s}y zBi9eim&zQc0Dk|2>$bs0PnLmDfMP5lcXRY&cvJ=zKxI^f0%-d$tD!`LBf9^jMSYUA zI8U?CWdY@}cRq6{5~y+)#h1!*-HcGW@+gZ4B};0OnC~`xQOyH19z*TA!!BJ%9s0V3F?CAJ{hTd#*tf+ur-W9MOURF-@B77_-OshsY}6 zOXRY=5%C^*26z?l)1=$bz30!so5tfABdSYzO+H=CpV~aaUefmjvfZ3Ttu9W&W3Iu6 zROlh0MFA5h;my}8lB0tAV-Rvc2Zs_CCSJnx@d`**$idgy-iMob4dJWWw|21b4NB=LfsYp0Aeh{Ov)yztQi;eL4y5 zMi>8^SzKqk8~k?UiQK^^-5d8c%bV?$F8%X~czyiaKCI2=UH { + test('captures error thrown on click', async ({ page }) => { + await waitForInitialPageload(page, { route: '/client-error' }); + + const errorEventPromise = waitForError('sveltekit-cloudflare-pages', 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'); + }); + + test('captures universal load error', async ({ page }) => { + await waitForInitialPageload(page); + await page.reload(); + + const errorEventPromise = waitForError('sveltekit-cloudflare-pages', errorEvent => { + return errorEvent?.exception?.values?.[0]?.value === 'Universal Load Error (browser)'; + }); + + // navigating triggers the error on the client + await page.getByText('Universal Load error').click(); + + const errorEvent = await errorEventPromise; + const errorEventFrames = errorEvent.exception?.values?.[0]?.stacktrace?.frames; + + const lastFrame = errorEventFrames?.[errorEventFrames?.length - 1]; + expect(lastFrame).toEqual( + expect.objectContaining({ + lineno: 1, + in_app: true, + }), + ); + + expect(errorEvent.transaction).toEqual('/universal-load-error'); + }); +}); diff --git a/dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/tests/errors.server.test.ts b/dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/tests/errors.server.test.ts new file mode 100644 index 000000000000..a5aa8cae4380 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/tests/errors.server.test.ts @@ -0,0 +1,90 @@ +import { expect, test } from '@playwright/test'; +import { waitForError } from '@sentry-internal/test-utils'; + +test.describe('server-side errors', () => { + test('captures universal load error', async ({ page }) => { + const errorEventPromise = waitForError('sveltekit-cloudflare-pages', errorEvent => { + return errorEvent?.exception?.values?.[0]?.value === 'Universal Load Error (server)'; + }); + + await page.goto('/universal-load-error'); + + const errorEvent = await errorEventPromise; + const errorEventFrames = errorEvent.exception?.values?.[0]?.stacktrace?.frames; + + expect(errorEventFrames?.[errorEventFrames?.length - 1]).toEqual( + expect.objectContaining({ + function: 'load$1', + in_app: true, + }), + ); + + expect(errorEvent.request).toEqual({ + cookies: {}, + headers: expect.objectContaining({ + accept: expect.any(String), + 'user-agent': expect.any(String), + }), + method: 'GET', + url: 'http://localhost:3030/universal-load-error', + }); + }); + + test('captures server load error', async ({ page }) => { + const errorEventPromise = waitForError('sveltekit-cloudflare-pages', errorEvent => { + return errorEvent?.exception?.values?.[0]?.value === 'Server Load Error'; + }); + + await page.goto('/server-load-error'); + + const errorEvent = await errorEventPromise; + const errorEventFrames = errorEvent.exception?.values?.[0]?.stacktrace?.frames; + + expect(errorEventFrames?.[errorEventFrames?.length - 1]).toEqual( + expect.objectContaining({ + function: 'load$1', + in_app: true, + }), + ); + + expect(errorEvent.request).toEqual({ + cookies: {}, + headers: expect.objectContaining({ + accept: expect.any(String), + 'user-agent': expect.any(String), + }), + method: 'GET', + url: 'http://localhost:3030/server-load-error', + }); + }); + + test('captures server route (GET) error', async ({ page }) => { + const errorEventPromise = waitForError('sveltekit-cloudflare-pages', errorEvent => { + return errorEvent?.exception?.values?.[0]?.value === 'Server Route Error'; + }); + + await page.goto('/server-route-error'); + + const errorEvent = await errorEventPromise; + const errorEventFrames = errorEvent.exception?.values?.[0]?.stacktrace?.frames; + + expect(errorEventFrames?.[errorEventFrames?.length - 1]).toEqual( + expect.objectContaining({ + filename: expect.stringContaining('app:///_server.ts'), + function: 'GET', + in_app: true, + }), + ); + + expect(errorEvent.transaction).toEqual('GET /server-route-error'); + + expect(errorEvent.request).toEqual({ + cookies: {}, + headers: expect.objectContaining({ + accept: expect.any(String), + }), + method: 'GET', + url: 'http://localhost:3030/server-route-error', + }); + }); +}); diff --git a/dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/tests/performance.client.test.ts b/dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/tests/performance.client.test.ts new file mode 100644 index 000000000000..50aa7de6309d --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/tests/performance.client.test.ts @@ -0,0 +1,139 @@ +import { expect, test } from '@playwright/test'; +import { waitForTransaction } from '@sentry-internal/test-utils'; +import { waitForInitialPageload } from './utils'; + +test.describe('client-specific performance events', () => { + test('multiple navigations have distinct traces', async ({ page }) => { + const navigationTxn1EventPromise = waitForTransaction('sveltekit-cloudflare-pages', txnEvent => { + return txnEvent?.transaction === '/nav1' && txnEvent.contexts?.trace?.op === 'navigation'; + }); + + const navigationTxn2EventPromise = waitForTransaction('sveltekit-cloudflare-pages', txnEvent => { + return txnEvent?.transaction === '/' && txnEvent.contexts?.trace?.op === 'navigation'; + }); + + const navigationTxn3EventPromise = waitForTransaction('sveltekit-cloudflare-pages', txnEvent => { + return txnEvent?.transaction === '/nav2' && txnEvent.contexts?.trace?.op === 'navigation'; + }); + + await waitForInitialPageload(page); + + await page.getByText('Nav 1').click(); + const navigationTxn1Event = await navigationTxn1EventPromise; + + await page.goBack(); + const navigationTxn2Event = await navigationTxn2EventPromise; + + await page.getByText('Nav 2').click(); + const navigationTxn3Event = await navigationTxn3EventPromise; + + expect(navigationTxn1Event).toMatchObject({ + transaction: '/nav1', + transaction_info: { source: 'route' }, + type: 'transaction', + contexts: { + trace: { + op: 'navigation', + origin: 'auto.navigation.sveltekit', + trace_id: expect.stringMatching(/[a-f0-9]{32}/), + }, + }, + }); + + expect(navigationTxn2Event).toMatchObject({ + transaction: '/', + transaction_info: { source: 'route' }, + type: 'transaction', + contexts: { + trace: { + op: 'navigation', + origin: 'auto.navigation.sveltekit', + trace_id: expect.stringMatching(/[a-f0-9]{32}/), + }, + }, + }); + + expect(navigationTxn3Event).toMatchObject({ + transaction: '/nav2', + transaction_info: { source: 'route' }, + type: 'transaction', + contexts: { + trace: { + op: 'navigation', + origin: 'auto.navigation.sveltekit', + trace_id: expect.stringMatching(/[a-f0-9]{32}/), + }, + }, + }); + + // traces should NOT be connected + expect(navigationTxn1Event.contexts?.trace?.trace_id).not.toBe(navigationTxn2Event.contexts?.trace?.trace_id); + expect(navigationTxn2Event.contexts?.trace?.trace_id).not.toBe(navigationTxn3Event.contexts?.trace?.trace_id); + expect(navigationTxn1Event.contexts?.trace?.trace_id).not.toBe(navigationTxn3Event.contexts?.trace?.trace_id); + }); + + test('records manually added component tracking spans', async ({ page }) => { + const componentTxnEventPromise = waitForTransaction('sveltekit-cloudflare-pages', txnEvent => { + return txnEvent?.transaction === '/components'; + }); + + await waitForInitialPageload(page); + + await page.getByText('Component Tracking').click(); + + const componentTxnEvent = await componentTxnEventPromise; + + expect(componentTxnEvent.spans).toEqual( + expect.arrayContaining([ + expect.objectContaining({ + data: { 'sentry.op': 'ui.svelte.init', 'sentry.origin': 'auto.ui.svelte' }, + description: '', + op: 'ui.svelte.init', + origin: 'auto.ui.svelte', + }), + expect.objectContaining({ + data: { 'sentry.op': 'ui.svelte.init', 'sentry.origin': 'auto.ui.svelte' }, + description: '', + op: 'ui.svelte.init', + origin: 'auto.ui.svelte', + }), + expect.objectContaining({ + data: { 'sentry.op': 'ui.svelte.init', 'sentry.origin': 'auto.ui.svelte' }, + description: '', + op: 'ui.svelte.init', + origin: 'auto.ui.svelte', + }), + expect.objectContaining({ + data: { 'sentry.op': 'ui.svelte.init', 'sentry.origin': 'auto.ui.svelte' }, + description: '', + op: 'ui.svelte.init', + origin: 'auto.ui.svelte', + }), + expect.objectContaining({ + data: { 'sentry.op': 'ui.svelte.update', 'sentry.origin': 'auto.ui.svelte' }, + description: '', + op: 'ui.svelte.update', + origin: 'auto.ui.svelte', + }), + expect.objectContaining({ + data: { 'sentry.op': 'ui.svelte.update', 'sentry.origin': 'auto.ui.svelte' }, + description: '', + op: 'ui.svelte.update', + origin: 'auto.ui.svelte', + }), + expect.objectContaining({ + data: { 'sentry.op': 'ui.svelte.update', 'sentry.origin': 'auto.ui.svelte' }, + description: '', + op: 'ui.svelte.update', + origin: 'auto.ui.svelte', + }), + expect.objectContaining({ + data: { 'sentry.op': 'ui.svelte.update', 'sentry.origin': 'auto.ui.svelte' }, + description: '', + op: 'ui.svelte.update', + origin: 'auto.ui.svelte', + }), + ]), + ); + }); +}); diff --git a/dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/tests/performance.server.test.ts b/dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/tests/performance.server.test.ts new file mode 100644 index 000000000000..1d409efb08b9 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/tests/performance.server.test.ts @@ -0,0 +1,44 @@ +import { expect, test } from '@playwright/test'; +import { waitForTransaction } from '@sentry-internal/test-utils'; + +test('server pageload request span has nested request span for sub request', async ({ page }) => { + const serverTxnEventPromise = waitForTransaction('sveltekit-cloudflare-pages', txnEvent => { + return txnEvent?.transaction === 'GET /server-load-fetch'; + }); + + await page.goto('/server-load-fetch'); + + const serverTxnEvent = await serverTxnEventPromise; + const spans = serverTxnEvent.spans; + + expect(serverTxnEvent).toMatchObject({ + transaction: 'GET /server-load-fetch', + transaction_info: { source: 'route' }, + type: 'transaction', + contexts: { + trace: { + op: 'http.server', + origin: 'auto.http.sveltekit', + }, + }, + }); + + expect(spans).toEqual( + expect.arrayContaining([ + // load span where the server load function initiates the sub request: + expect.objectContaining({ op: 'function.sveltekit.server.load', description: '/server-load-fetch' }), + // sub request span: + expect.objectContaining({ op: 'http.server', description: 'GET /api/users' }), + ]), + ); + + expect(serverTxnEvent.request).toEqual({ + cookies: {}, + headers: expect.objectContaining({ + accept: expect.any(String), + 'user-agent': expect.any(String), + }), + method: 'GET', + url: 'http://localhost:3030/server-load-fetch', + }); +}); diff --git a/dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/tests/performance.test.ts b/dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/tests/performance.test.ts new file mode 100644 index 000000000000..f1d49a489aa0 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/tests/performance.test.ts @@ -0,0 +1,381 @@ +import { expect, test } from '@playwright/test'; +import { waitForTransaction } from '@sentry-internal/test-utils'; +import { waitForInitialPageload } from './utils'; + +test.describe('performance events', () => { + test('capture a distributed pageload trace', async ({ page }) => { + const clientTxnEventPromise = waitForTransaction('sveltekit-cloudflare-pages', txnEvent => { + return txnEvent?.transaction === '/users/[id]'; + }); + + const serverTxnEventPromise = waitForTransaction('sveltekit-cloudflare-pages', txnEvent => { + return txnEvent?.transaction === 'GET /users/[id]'; + }); + + const [_, clientTxnEvent, serverTxnEvent] = await Promise.all([ + page.goto('/users/123xyz'), + clientTxnEventPromise, + serverTxnEventPromise, + expect(page.getByText('User id: 123xyz')).toBeVisible(), + ]); + + expect(clientTxnEvent).toMatchObject({ + transaction: '/users/[id]', + transaction_info: { source: 'route' }, + type: 'transaction', + contexts: { + trace: { + op: 'pageload', + origin: 'auto.pageload.sveltekit', + }, + }, + }); + + expect(serverTxnEvent).toMatchObject({ + transaction: 'GET /users/[id]', + transaction_info: { source: 'route' }, + type: 'transaction', + contexts: { + trace: { + op: 'http.server', + origin: 'auto.http.sveltekit', + }, + }, + }); + + expect(clientTxnEvent.spans?.length).toBeGreaterThan(5); + + // connected trace + expect(clientTxnEvent.contexts?.trace?.trace_id).toBe(serverTxnEvent.contexts?.trace?.trace_id); + + // weird but server txn is parent of client txn + expect(clientTxnEvent.contexts?.trace?.parent_span_id).toBe(serverTxnEvent.contexts?.trace?.span_id); + }); + + test('capture a distributed navigation trace', async ({ page }) => { + const clientNavigationTxnEventPromise = waitForTransaction('sveltekit-cloudflare-pages', txnEvent => { + return txnEvent?.transaction === '/users' && txnEvent.contexts?.trace?.op === 'navigation'; + }); + + const serverTxnEventPromise = waitForTransaction('sveltekit-cloudflare-pages', txnEvent => { + return txnEvent?.transaction === 'GET /users'; + }); + + await waitForInitialPageload(page); + + // navigation to page + const clickPromise = page.getByText('Route with Server Load').click(); + + const [clientTxnEvent, serverTxnEvent, _1, _2] = await Promise.all([ + clientNavigationTxnEventPromise, + serverTxnEventPromise, + clickPromise, + expect(page.getByText('Hi everyone')).toBeVisible(), + ]); + + expect(clientTxnEvent).toMatchObject({ + transaction: '/users', + transaction_info: { source: 'route' }, + type: 'transaction', + contexts: { + trace: { + op: 'navigation', + origin: 'auto.navigation.sveltekit', + }, + }, + }); + + expect(serverTxnEvent).toMatchObject({ + transaction: 'GET /users', + transaction_info: { source: 'route' }, + type: 'transaction', + contexts: { + trace: { + op: 'http.server', + origin: 'auto.http.sveltekit', + }, + }, + }); + + // trace is connected + expect(clientTxnEvent.contexts?.trace?.trace_id).toBe(serverTxnEvent.contexts?.trace?.trace_id); + }); + + test('record client-side universal load fetch span and trace', async ({ page }) => { + await waitForInitialPageload(page); + + const clientNavigationTxnEventPromise = waitForTransaction('sveltekit-cloudflare-pages', txnEvent => { + return txnEvent?.transaction === '/universal-load-fetch' && txnEvent.contexts?.trace?.op === 'navigation'; + }); + + // this transaction should be created because of the fetch call + // it should also be part of the trace + const serverTxnEventPromise = waitForTransaction('sveltekit-cloudflare-pages', txnEvent => { + return txnEvent?.transaction === 'GET /api/users'; + }); + + // navigation to page + const clickPromise = page.getByText('Route with fetch in universal load').click(); + + const [clientTxnEvent, serverTxnEvent, _1, _2] = await Promise.all([ + clientNavigationTxnEventPromise, + serverTxnEventPromise, + clickPromise, + expect(page.getByText('alice')).toBeVisible(), + ]); + + expect(clientTxnEvent).toMatchObject({ + transaction: '/universal-load-fetch', + transaction_info: { source: 'route' }, + type: 'transaction', + contexts: { + trace: { + op: 'navigation', + origin: 'auto.navigation.sveltekit', + }, + }, + }); + + expect(serverTxnEvent).toMatchObject({ + transaction: 'GET /api/users', + transaction_info: { source: 'route' }, + type: 'transaction', + contexts: { + trace: { + op: 'http.server', + origin: 'auto.http.sveltekit', + }, + }, + }); + + // trace is connected + expect(clientTxnEvent.contexts?.trace?.trace_id).toBe(serverTxnEvent.contexts?.trace?.trace_id); + + const clientFetchSpan = clientTxnEvent.spans?.find(s => s.op === 'http.client'); + + expect(clientFetchSpan).toMatchObject({ + description: expect.stringMatching(/^GET.*\/api\/users/), + op: 'http.client', + origin: 'auto.http.browser', + data: { + url: expect.stringContaining('/api/users'), + type: 'fetch', + 'http.method': 'GET', + 'http.response.status_code': 200, + 'network.protocol.version': '1.1', + 'network.protocol.name': 'http', + 'http.request.redirect_start': expect.any(Number), + 'http.request.fetch_start': expect.any(Number), + 'http.request.domain_lookup_start': expect.any(Number), + 'http.request.domain_lookup_end': expect.any(Number), + 'http.request.connect_start': expect.any(Number), + 'http.request.secure_connection_start': expect.any(Number), + 'http.request.connection_end': expect.any(Number), + 'http.request.request_start': expect.any(Number), + 'http.request.response_start': expect.any(Number), + 'http.request.response_end': expect.any(Number), + }, + }); + }); + + test('captures a navigation transaction directly after pageload', async ({ page }) => { + const clientPageloadTxnPromise = waitForTransaction('sveltekit-cloudflare-pages', txnEvent => { + return txnEvent?.contexts?.trace?.op === 'pageload'; + }); + + const clientNavigationTxnPromise = waitForTransaction('sveltekit-cloudflare-pages', txnEvent => { + return txnEvent?.contexts?.trace?.op === 'navigation'; + }); + + await waitForInitialPageload(page, { route: '/' }); + + const navigationClickPromise = page.locator('#routeWithParamsLink').click(); + + const [pageloadTxnEvent, navigationTxnEvent, _] = await Promise.all([ + clientPageloadTxnPromise, + clientNavigationTxnPromise, + navigationClickPromise, + ]); + + expect(pageloadTxnEvent).toMatchObject({ + transaction: '/', + transaction_info: { source: 'route' }, + type: 'transaction', + contexts: { + trace: { + op: 'pageload', + origin: 'auto.pageload.sveltekit', + }, + }, + }); + + expect(navigationTxnEvent).toMatchObject({ + transaction: '/users/[id]', + transaction_info: { source: 'route' }, + type: 'transaction', + contexts: { + trace: { + op: 'navigation', + origin: 'auto.navigation.sveltekit', + data: { + 'sentry.sveltekit.navigation.from': '/', + 'sentry.sveltekit.navigation.to': '/users/[id]', + 'sentry.sveltekit.navigation.type': 'link', + }, + }, + }, + }); + + const routingSpans = navigationTxnEvent.spans?.filter(s => s.op === 'ui.sveltekit.routing'); + expect(routingSpans).toHaveLength(1); + + const routingSpan = routingSpans && routingSpans[0]; + expect(routingSpan).toMatchObject({ + op: 'ui.sveltekit.routing', + description: 'SvelteKit Route Change', + data: { + 'sentry.op': 'ui.sveltekit.routing', + 'sentry.origin': 'auto.ui.sveltekit', + 'sentry.sveltekit.navigation.from': '/', + 'sentry.sveltekit.navigation.to': '/users/[id]', + 'sentry.sveltekit.navigation.type': 'link', + }, + }); + }); + + test('captures one navigation transaction per redirect', async ({ page }) => { + const clientNavigationRedirect1TxnPromise = waitForTransaction('sveltekit-cloudflare-pages', txnEvent => { + return txnEvent?.contexts?.trace?.op === 'navigation' && txnEvent?.transaction === '/redirect1'; + }); + + const clientNavigationRedirect2TxnPromise = waitForTransaction('sveltekit-cloudflare-pages', txnEvent => { + return txnEvent?.contexts?.trace?.op === 'navigation' && txnEvent?.transaction === '/redirect2'; + }); + + const clientNavigationRedirect3TxnPromise = waitForTransaction('sveltekit-cloudflare-pages', txnEvent => { + return txnEvent?.contexts?.trace?.op === 'navigation' && txnEvent?.transaction === '/users/[id]'; + }); + + await waitForInitialPageload(page, { route: '/' }); + + const navigationClickPromise = page.locator('#redirectLink').click(); + + const [redirect1TxnEvent, redirect2TxnEvent, redirect3TxnEvent, _] = await Promise.all([ + clientNavigationRedirect1TxnPromise, + clientNavigationRedirect2TxnPromise, + clientNavigationRedirect3TxnPromise, + navigationClickPromise, + ]); + + expect(redirect1TxnEvent).toMatchObject({ + transaction: '/redirect1', + transaction_info: { source: 'route' }, + type: 'transaction', + contexts: { + trace: { + op: 'navigation', + origin: 'auto.navigation.sveltekit', + data: { + 'sentry.origin': 'auto.navigation.sveltekit', + 'sentry.op': 'navigation', + 'sentry.source': 'route', + 'sentry.sveltekit.navigation.type': 'link', + 'sentry.sveltekit.navigation.from': '/', + 'sentry.sveltekit.navigation.to': '/redirect1', + 'sentry.sample_rate': 1, + }, + }, + }, + }); + + const redirect1Spans = redirect1TxnEvent.spans?.filter(s => s.op === 'ui.sveltekit.routing'); + expect(redirect1Spans).toHaveLength(1); + + const redirect1Span = redirect1Spans && redirect1Spans[0]; + expect(redirect1Span).toMatchObject({ + op: 'ui.sveltekit.routing', + description: 'SvelteKit Route Change', + data: { + 'sentry.op': 'ui.sveltekit.routing', + 'sentry.origin': 'auto.ui.sveltekit', + 'sentry.sveltekit.navigation.from': '/', + 'sentry.sveltekit.navigation.to': '/redirect1', + 'sentry.sveltekit.navigation.type': 'link', + }, + }); + + expect(redirect2TxnEvent).toMatchObject({ + transaction: '/redirect2', + transaction_info: { source: 'route' }, + type: 'transaction', + contexts: { + trace: { + op: 'navigation', + origin: 'auto.navigation.sveltekit', + data: { + 'sentry.origin': 'auto.navigation.sveltekit', + 'sentry.op': 'navigation', + 'sentry.source': 'route', + 'sentry.sveltekit.navigation.type': 'goto', + 'sentry.sveltekit.navigation.from': '/', + 'sentry.sveltekit.navigation.to': '/redirect2', + 'sentry.sample_rate': 1, + }, + }, + }, + }); + + const redirect2Spans = redirect2TxnEvent.spans?.filter(s => s.op === 'ui.sveltekit.routing'); + expect(redirect2Spans).toHaveLength(1); + + const redirect2Span = redirect2Spans && redirect2Spans[0]; + expect(redirect2Span).toMatchObject({ + op: 'ui.sveltekit.routing', + description: 'SvelteKit Route Change', + data: { + 'sentry.op': 'ui.sveltekit.routing', + 'sentry.origin': 'auto.ui.sveltekit', + 'sentry.sveltekit.navigation.from': '/', + 'sentry.sveltekit.navigation.to': '/redirect2', + 'sentry.sveltekit.navigation.type': 'goto', + }, + }); + + expect(redirect3TxnEvent).toMatchObject({ + transaction: '/users/[id]', + transaction_info: { source: 'route' }, + type: 'transaction', + contexts: { + trace: { + op: 'navigation', + origin: 'auto.navigation.sveltekit', + data: { + 'sentry.origin': 'auto.navigation.sveltekit', + 'sentry.op': 'navigation', + 'sentry.source': 'route', + 'sentry.sveltekit.navigation.type': 'goto', + 'sentry.sveltekit.navigation.from': '/', + 'sentry.sveltekit.navigation.to': '/users/[id]', + 'sentry.sample_rate': 1, + }, + }, + }, + }); + + const redirect3Spans = redirect3TxnEvent.spans?.filter(s => s.op === 'ui.sveltekit.routing'); + expect(redirect3Spans).toHaveLength(1); + + const redirect3Span = redirect3Spans && redirect3Spans[0]; + expect(redirect3Span).toMatchObject({ + op: 'ui.sveltekit.routing', + description: 'SvelteKit Route Change', + data: { + 'sentry.op': 'ui.sveltekit.routing', + 'sentry.origin': 'auto.ui.sveltekit', + 'sentry.sveltekit.navigation.from': '/', + 'sentry.sveltekit.navigation.to': '/users/[id]', + 'sentry.sveltekit.navigation.type': 'goto', + }, + }); + }); +}); diff --git a/dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/tests/utils.ts b/dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/tests/utils.ts new file mode 100644 index 000000000000..1265500d380a --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/tests/utils.ts @@ -0,0 +1,49 @@ +import { Page } from '@playwright/test'; +import { waitForTransaction } from '@sentry-internal/test-utils'; + +/** + * Helper function that waits for the initial pageload to complete. + * + * This function + * - loads the given route ("/" by default) + * - waits for SvelteKit's hydration + * - waits for the pageload transaction to be sent (doesn't assert on it though) + * + * Useful for tests that test outcomes of _navigations_ after an initial pageload. + * Waiting on the pageload transaction excludes edge cases where navigations occur + * so quickly that the pageload idle transaction is still active. This might lead + * to cases where the routing span would be attached to the pageload transaction + * and hence eliminates a lot of flakiness. + * + */ +export async function waitForInitialPageload( + page: Page, + opts?: { route?: string; parameterizedRoute?: string; debug?: boolean }, +) { + const route = opts?.route ?? '/'; + const txnName = opts?.parameterizedRoute ?? route; + const debug = opts?.debug ?? false; + + const clientPageloadTxnEventPromise = waitForTransaction('sveltekit-cloudflare-pages', txnEvent => { + debug && + console.log({ + txn: txnEvent?.transaction, + op: txnEvent.contexts?.trace?.op, + trace: txnEvent.contexts?.trace?.trace_id, + span: txnEvent.contexts?.trace?.span_id, + parent: txnEvent.contexts?.trace?.parent_span_id, + }); + + return txnEvent?.transaction === txnName && txnEvent.contexts?.trace?.op === 'pageload'; + }); + + await Promise.all([ + page.goto(route), + // the test app adds the "hydrated" class to the body when hydrating + page.waitForSelector('body.hydrated'), + // also waiting for the initial pageload txn so that later navigations don't interfere + clientPageloadTxnEventPromise, + ]); + + debug && console.log('hydrated'); +} diff --git a/dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/tsconfig.json b/dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/tsconfig.json new file mode 100644 index 000000000000..ba6aa4e6610a --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/tsconfig.json @@ -0,0 +1,19 @@ +{ + "extends": "./.svelte-kit/tsconfig.json", + "compilerOptions": { + "allowJs": true, + "checkJs": true, + "esModuleInterop": true, + "forceConsistentCasingInFileNames": true, + "resolveJsonModule": true, + "skipLibCheck": true, + "sourceMap": true, + "strict": true, + "moduleResolution": "bundler" + }, + // Path aliases are handled by https://kit.svelte.dev/docs/configuration#alias + // except $lib which is handled by https://kit.svelte.dev/docs/configuration#files + // + // If you want to overwrite includes/excludes, make sure to copy over the relevant includes/excludes + // from the referenced tsconfig.json - TypeScript does not merge them in +} diff --git a/dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/vite.config.ts b/dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/vite.config.ts new file mode 100644 index 000000000000..1a410bee7e11 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/vite.config.ts @@ -0,0 +1,12 @@ +import { sentrySvelteKit } from '@sentry/sveltekit'; +import { sveltekit } from '@sveltejs/kit/vite'; +import { defineConfig } from 'vite'; + +export default defineConfig({ + plugins: [ + sentrySvelteKit({ + autoUploadSourceMaps: false, + }), + sveltekit(), + ], +}); diff --git a/dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/wrangler.toml b/dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/wrangler.toml new file mode 100644 index 000000000000..d31d2fc7f225 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/wrangler.toml @@ -0,0 +1,2 @@ +compatibility_date = "2024-12-17" +compatibility_flags = ["nodejs_compat"] From 355bc225acec3f87172427a3367351d6bb089791 Mon Sep 17 00:00:00 2001 From: Sam Greening <2552620+SG60@users.noreply.github.com> Date: Tue, 17 Dec 2024 14:45:14 +0000 Subject: [PATCH 07/31] test(sveltekit-cloudflare): simpler e2e test Just test whether it builds successfully --- .../sveltekit-cloudflare-pages/.gitignore | 21 +- .../sveltekit-cloudflare-pages/.npmrc | 1 + .../sveltekit-cloudflare-pages/README.md | 15 +- .../e2e/demo.test.ts | 6 + .../sveltekit-cloudflare-pages/package.json | 57 ++- .../playwright.config.mjs | 8 - .../playwright.config.ts | 10 + .../sveltekit-cloudflare-pages/src/app.d.ts | 16 +- .../src/hooks.client.ts | 17 +- .../src/hooks.server.ts | 13 +- .../src/lib/index.ts | 1 + .../src/routes/+layout.svelte | 15 - .../src/routes/+page.server.ts | 7 + .../src/routes/+page.svelte | 47 +-- .../src/routes/api/users/+server.ts | 3 - .../src/routes/building/+page.server.ts | 5 - .../src/routes/building/+page.svelte | 21 - .../src/routes/building/+page.ts | 5 - .../src/routes/client-error/+page.svelte | 9 - .../src/routes/components/+page.svelte | 15 - .../src/routes/components/Component1.svelte | 10 - .../src/routes/components/Component2.svelte | 9 - .../src/routes/components/Component3.svelte | 6 - .../src/routes/nav1/+page.svelte | 1 - .../src/routes/nav2/+page.svelte | 1 - .../src/routes/redirect1/+page.ts | 5 - .../src/routes/redirect2/+page.ts | 5 - .../routes/server-load-error/+page.server.ts | 6 - .../src/routes/server-load-error/+page.svelte | 9 - .../routes/server-load-fetch/+page.server.ts | 5 - .../src/routes/server-load-fetch/+page.svelte | 8 - .../routes/server-route-error/+page.svelte | 9 - .../src/routes/server-route-error/+page.ts | 7 - .../src/routes/server-route-error/+server.ts | 6 - .../routes/universal-load-error/+page.svelte | 17 - .../src/routes/universal-load-error/+page.ts | 8 - .../routes/universal-load-fetch/+page.svelte | 14 - .../src/routes/universal-load-fetch/+page.ts | 5 - .../src/routes/users/+page.server.ts | 5 - .../src/routes/users/+page.svelte | 10 - .../src/routes/users/[id]/+page.server.ts | 5 - .../src/routes/users/[id]/+page.svelte | 14 - .../start-event-proxy.mjs | 6 - .../svelte.config.js | 20 +- .../tests/errors.client.test.ts | 56 --- .../tests/errors.server.test.ts | 90 ----- .../tests/performance.client.test.ts | 139 ------- .../tests/performance.server.test.ts | 44 -- .../tests/performance.test.ts | 381 ------------------ .../sveltekit-cloudflare-pages/tests/utils.ts | 49 --- .../sveltekit-cloudflare-pages/tsconfig.json | 6 +- .../sveltekit-cloudflare-pages/vite.config.ts | 9 +- 52 files changed, 109 insertions(+), 1148 deletions(-) create mode 100644 dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/e2e/demo.test.ts delete mode 100644 dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/playwright.config.mjs create mode 100644 dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/playwright.config.ts create mode 100644 dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/src/lib/index.ts delete mode 100644 dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/src/routes/+layout.svelte create mode 100644 dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/src/routes/+page.server.ts delete mode 100644 dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/src/routes/api/users/+server.ts delete mode 100644 dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/src/routes/building/+page.server.ts delete mode 100644 dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/src/routes/building/+page.svelte delete mode 100644 dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/src/routes/building/+page.ts delete mode 100644 dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/src/routes/client-error/+page.svelte delete mode 100644 dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/src/routes/components/+page.svelte delete mode 100644 dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/src/routes/components/Component1.svelte delete mode 100644 dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/src/routes/components/Component2.svelte delete mode 100644 dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/src/routes/components/Component3.svelte delete mode 100644 dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/src/routes/nav1/+page.svelte delete mode 100644 dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/src/routes/nav2/+page.svelte delete mode 100644 dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/src/routes/redirect1/+page.ts delete mode 100644 dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/src/routes/redirect2/+page.ts delete mode 100644 dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/src/routes/server-load-error/+page.server.ts delete mode 100644 dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/src/routes/server-load-error/+page.svelte delete mode 100644 dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/src/routes/server-load-fetch/+page.server.ts delete mode 100644 dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/src/routes/server-load-fetch/+page.svelte delete mode 100644 dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/src/routes/server-route-error/+page.svelte delete mode 100644 dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/src/routes/server-route-error/+page.ts delete mode 100644 dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/src/routes/server-route-error/+server.ts delete mode 100644 dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/src/routes/universal-load-error/+page.svelte delete mode 100644 dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/src/routes/universal-load-error/+page.ts delete mode 100644 dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/src/routes/universal-load-fetch/+page.svelte delete mode 100644 dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/src/routes/universal-load-fetch/+page.ts delete mode 100644 dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/src/routes/users/+page.server.ts delete mode 100644 dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/src/routes/users/+page.svelte delete mode 100644 dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/src/routes/users/[id]/+page.server.ts delete mode 100644 dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/src/routes/users/[id]/+page.svelte delete mode 100644 dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/start-event-proxy.mjs delete mode 100644 dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/tests/errors.client.test.ts delete mode 100644 dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/tests/errors.server.test.ts delete mode 100644 dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/tests/performance.client.test.ts delete mode 100644 dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/tests/performance.server.test.ts delete mode 100644 dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/tests/performance.test.ts delete mode 100644 dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/tests/utils.ts diff --git a/dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/.gitignore b/dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/.gitignore index 291fe69f6e16..bff793d5eae7 100644 --- a/dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/.gitignore +++ b/dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/.gitignore @@ -1,11 +1,24 @@ -.DS_Store +test-results node_modules -/build + +# Output +.output +.vercel +.netlify +.wrangler /.svelte-kit -/.wrangler -/package +/build + +# OS +.DS_Store +Thumbs.db + +# Env .env .env.* !.env.example +!.env.test + +# Vite vite.config.js.timestamp-* vite.config.ts.timestamp-* diff --git a/dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/.npmrc b/dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/.npmrc index 070f80f05092..0e94f06dacb6 100644 --- a/dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/.npmrc +++ b/dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/.npmrc @@ -1,2 +1,3 @@ @sentry:registry=http://127.0.0.1:4873 @sentry-internal:registry=http://127.0.0.1:4873 +engine-strict=true diff --git a/dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/README.md b/dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/README.md index 684cabccfe02..b5b295070b44 100644 --- a/dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/README.md +++ b/dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/README.md @@ -1,7 +1,6 @@ -# create-svelte +# sv -Everything you need to build a Svelte project, powered by -[`create-svelte`](https://github.com/sveltejs/kit/tree/main/packages/create-svelte). +Everything you need to build a Svelte project, powered by [`sv`](https://github.com/sveltejs/cli). ## Creating a project @@ -9,16 +8,15 @@ If you're seeing this, you've probably already done this step. Congrats! ```bash # create a new project in the current directory -npm create svelte@latest +npx sv create # create a new project in my-app -npm create svelte@latest my-app +npx sv create my-app ``` ## Developing -Once you've created a project and installed dependencies with `npm install` (or `pnpm install` or `yarn`), start a -development server: +Once you've created a project and installed dependencies with `npm install` (or `pnpm install` or `yarn`), start a development server: ```bash npm run dev @@ -37,5 +35,4 @@ npm run build You can preview the production build with `npm run preview`. -> To deploy your app, you may need to install an [adapter](https://kit.svelte.dev/docs/adapters) for your target -> environment. +> To deploy your app, you may need to install an [adapter](https://svelte.dev/docs/kit/adapters) for your target environment. diff --git a/dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/e2e/demo.test.ts b/dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/e2e/demo.test.ts new file mode 100644 index 000000000000..9985ce113eb8 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/e2e/demo.test.ts @@ -0,0 +1,6 @@ +import { expect, test } from '@playwright/test'; + +test('home page has expected h1', async ({ page }) => { + await page.goto('/'); + await expect(page.locator('h1')).toBeVisible(); +}); diff --git a/dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/package.json b/dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/package.json index 5d85a7be445b..412f4281c256 100644 --- a/dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/package.json +++ b/dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/package.json @@ -1,36 +1,31 @@ { - "name": "sveltekit-cloudflare-pages", - "version": "0.0.1", - "private": true, - "scripts": { - "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", + "name": "sveltekit-cloudflare-pages", + "private": true, + "version": "0.0.1", + "type": "module", + "scripts": { + "dev": "vite dev", + "build": "vite build", + "preview": "wrangler pages dev ./.svelte-kit/cloudflare --port 4173", + "check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json", + "check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch", + "test:e2e": "playwright test", + "test": "npm run test:e2e", "test:build": "pnpm install && pnpm build", - "test:assert": "pnpm test:prod" - }, + "test:assert": "npm run test:e2e" + }, "dependencies": { - "@sentry/sveltekit": "latest || *", - "@spotlightjs/spotlight": "2.0.0-alpha.1" - }, - "devDependencies": { - "@playwright/test": "^1.44.1", - "@sentry-internal/test-utils": "link:../../../test-utils", - "@sentry/core": "latest || *", - "@sveltejs/adapter-cloudflare": "^4.0.0", - "@sveltejs/kit": "^2.0.0", - "@sveltejs/vite-plugin-svelte": "^4.0.0", - "svelte": "^5.0.0", - "svelte-check": "^3.6.0", - "tslib": "^2.4.1", - "typescript": "^5.0.0", - "vite": "^5.4.10", - "wrangler": "^3.95.0" + "@sentry/sveltekit": "latest || *" }, - "type": "module" + "devDependencies": { + "@playwright/test": "^1.45.3", + "@sveltejs/adapter-cloudflare": "^4.8.0", + "@sveltejs/kit": "^2.9.0", + "@sveltejs/vite-plugin-svelte": "^5.0.0", + "svelte": "^5.0.0", + "svelte-check": "^4.0.0", + "typescript": "^5.0.0", + "vite": "^6.0.0", + "wrangler": "^3" + } } diff --git a/dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/playwright.config.mjs b/dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/playwright.config.mjs deleted file mode 100644 index 3135c8dd5f88..000000000000 --- a/dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/playwright.config.mjs +++ /dev/null @@ -1,8 +0,0 @@ -import { getPlaywrightConfig } from '@sentry-internal/test-utils'; - -const config = getPlaywrightConfig({ - startCommand: 'pnpm wrangler pages dev --port 3030 .svelte-kit/cloudflare', - port: 3030, -}); - -export default config; diff --git a/dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/playwright.config.ts b/dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/playwright.config.ts new file mode 100644 index 000000000000..db8f8c68c99a --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/playwright.config.ts @@ -0,0 +1,10 @@ +import { defineConfig } from '@playwright/test'; + +export default defineConfig({ + webServer: { + command: 'npm run build && npm run preview', + port: 4173 + }, + + testDir: 'e2e' +}); diff --git a/dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/src/app.d.ts b/dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/src/app.d.ts index ede601ab93e2..da08e6da592d 100644 --- a/dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/src/app.d.ts +++ b/dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/src/app.d.ts @@ -1,13 +1,13 @@ -// See https://kit.svelte.dev/docs/types#app +// See https://svelte.dev/docs/kit/types#app.d.ts // for information about these interfaces declare global { - namespace App { - // interface Error {} - // interface Locals {} - // interface PageData {} - // interface PageState {} - // interface Platform {} - } + namespace App { + // interface Error {} + // interface Locals {} + // interface PageData {} + // interface PageState {} + // interface Platform {} + } } export {}; diff --git a/dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/src/hooks.client.ts b/dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/src/hooks.client.ts index 91592e7ab932..4dc12acebc45 100644 --- a/dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/src/hooks.client.ts +++ b/dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/src/hooks.client.ts @@ -1,23 +1,8 @@ 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 to keep transactions dsn: env.PUBLIC_E2E_TEST_DSN, - debug: !!env.PUBLIC_DEBUG, - tunnel: `http://localhost:3031/`, // proxy server - 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, - }); -} +export const handleError = Sentry.handleErrorWithSentry(); diff --git a/dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/src/hooks.server.ts b/dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/src/hooks.server.ts index af0312c62357..d9dbd4a356a0 100644 --- a/dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/src/hooks.server.ts +++ b/dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/src/hooks.server.ts @@ -1,17 +1,12 @@ import { E2E_TEST_DSN } from '$env/static/private'; -import * as Sentry from '@sentry/sveltekit'; +import { handleErrorWithSentry, initCloudflareSentryHandle, sentryHandle } from '@sentry/sveltekit'; import { sequence } from '@sveltejs/kit/hooks'; -// not logging anything to console to avoid noise in the test output -export const handleError = Sentry.handleErrorWithSentry(() => { }); +export const handleError = handleErrorWithSentry(); export const handle = sequence( - Sentry.initCloudflareSentryHandle({ - environment: 'qa', // dynamic sampling bias to keep transactions + initCloudflareSentryHandle({ dsn: E2E_TEST_DSN, - debug: !!process.env.DEBUG, - tunnel: `http://localhost:3031/`, // proxy server - tracesSampleRate: 1.0, }), - Sentry.sentryHandle(), + sentryHandle(), ); diff --git a/dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/src/lib/index.ts b/dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/src/lib/index.ts new file mode 100644 index 000000000000..856f2b6c38ae --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/src/lib/index.ts @@ -0,0 +1 @@ +// place files you want to import through the `$lib` alias in this folder. diff --git a/dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/src/routes/+layout.svelte b/dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/src/routes/+layout.svelte deleted file mode 100644 index d1fadd2ea5a3..000000000000 --- a/dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/src/routes/+layout.svelte +++ /dev/null @@ -1,15 +0,0 @@ - - -

Sveltekit E2E Test app

-
- -
diff --git a/dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/src/routes/+page.server.ts b/dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/src/routes/+page.server.ts new file mode 100644 index 000000000000..3cbde33753a2 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/src/routes/+page.server.ts @@ -0,0 +1,7 @@ +import type { PageServerLoad } from './$types'; + +export const load: PageServerLoad = async function load() { + return { + message: 'From server load function.', + }; +}; diff --git a/dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/src/routes/+page.svelte b/dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/src/routes/+page.svelte index 575e6827693f..36f518feb34f 100644 --- a/dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/src/routes/+page.svelte +++ b/dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/src/routes/+page.svelte @@ -1,41 +1,8 @@ -

Welcome to SvelteKit 2 with Svelte 5!

-

Visit kit.svelte.dev to read the documentation

+ - +

Welcome to SvelteKit

+

Visit svelte.dev/docs/kit to read the documentation

+ +

{data.message}

diff --git a/dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/src/routes/api/users/+server.ts b/dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/src/routes/api/users/+server.ts deleted file mode 100644 index d0e4371c594b..000000000000 --- a/dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/src/routes/api/users/+server.ts +++ /dev/null @@ -1,3 +0,0 @@ -export const GET = () => { - return new Response(JSON.stringify({ users: ['alice', 'bob', 'carol'] })); -}; diff --git a/dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/src/routes/building/+page.server.ts b/dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/src/routes/building/+page.server.ts deleted file mode 100644 index b07376ba97c9..000000000000 --- a/dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/src/routes/building/+page.server.ts +++ /dev/null @@ -1,5 +0,0 @@ -import type { PageServerLoad } from './$types'; - -export const load = (async _event => { - return { name: 'building (server)' }; -}) satisfies PageServerLoad; diff --git a/dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/src/routes/building/+page.svelte b/dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/src/routes/building/+page.svelte deleted file mode 100644 index b27edb70053d..000000000000 --- a/dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/src/routes/building/+page.svelte +++ /dev/null @@ -1,21 +0,0 @@ - - -

Check Build

- -

- This route only exists to check that Typescript definitions - and auto instrumentation are working when the project is built. -

diff --git a/dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/src/routes/building/+page.ts b/dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/src/routes/building/+page.ts deleted file mode 100644 index 049acdc1fafa..000000000000 --- a/dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/src/routes/building/+page.ts +++ /dev/null @@ -1,5 +0,0 @@ -import type { PageLoad } from './$types'; - -export const load = (async _event => { - return { name: 'building' }; -}) satisfies PageLoad; diff --git a/dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/src/routes/client-error/+page.svelte b/dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/src/routes/client-error/+page.svelte deleted file mode 100644 index ba6b464e9324..000000000000 --- a/dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/src/routes/client-error/+page.svelte +++ /dev/null @@ -1,9 +0,0 @@ - - -

Client error

- - diff --git a/dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/src/routes/components/+page.svelte b/dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/src/routes/components/+page.svelte deleted file mode 100644 index eff3fa3f2e8d..000000000000 --- a/dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/src/routes/components/+page.svelte +++ /dev/null @@ -1,15 +0,0 @@ - -

Demonstrating Component Tracking

- - - - diff --git a/dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/src/routes/components/Component1.svelte b/dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/src/routes/components/Component1.svelte deleted file mode 100644 index a675711e4b68..000000000000 --- a/dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/src/routes/components/Component1.svelte +++ /dev/null @@ -1,10 +0,0 @@ - -

Howdy, I'm component 1

- - diff --git a/dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/src/routes/components/Component2.svelte b/dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/src/routes/components/Component2.svelte deleted file mode 100644 index 2b2f38308077..000000000000 --- a/dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/src/routes/components/Component2.svelte +++ /dev/null @@ -1,9 +0,0 @@ - -

Howdy, I'm component 2

- - diff --git a/dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/src/routes/components/Component3.svelte b/dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/src/routes/components/Component3.svelte deleted file mode 100644 index 9b4e028f78e7..000000000000 --- a/dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/src/routes/components/Component3.svelte +++ /dev/null @@ -1,6 +0,0 @@ - - -

Howdy, I'm component 3

diff --git a/dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/src/routes/nav1/+page.svelte b/dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/src/routes/nav1/+page.svelte deleted file mode 100644 index 31abffc512a2..000000000000 --- a/dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/src/routes/nav1/+page.svelte +++ /dev/null @@ -1 +0,0 @@ -

Navigation 1

diff --git a/dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/src/routes/nav2/+page.svelte b/dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/src/routes/nav2/+page.svelte deleted file mode 100644 index 20b44bb32da9..000000000000 --- a/dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/src/routes/nav2/+page.svelte +++ /dev/null @@ -1 +0,0 @@ -

Navigation 2

diff --git a/dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/src/routes/redirect1/+page.ts b/dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/src/routes/redirect1/+page.ts deleted file mode 100644 index 3f462bf810fd..000000000000 --- a/dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/src/routes/redirect1/+page.ts +++ /dev/null @@ -1,5 +0,0 @@ -import { redirect } from '@sveltejs/kit'; - -export const load = async () => { - redirect(301, '/redirect2'); -}; diff --git a/dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/src/routes/redirect2/+page.ts b/dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/src/routes/redirect2/+page.ts deleted file mode 100644 index 99a810761d18..000000000000 --- a/dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/src/routes/redirect2/+page.ts +++ /dev/null @@ -1,5 +0,0 @@ -import { redirect } from '@sveltejs/kit'; - -export const load = async () => { - redirect(301, '/users/789'); -}; diff --git a/dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/src/routes/server-load-error/+page.server.ts b/dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/src/routes/server-load-error/+page.server.ts deleted file mode 100644 index 17dd53fb5bbb..000000000000 --- a/dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/src/routes/server-load-error/+page.server.ts +++ /dev/null @@ -1,6 +0,0 @@ -export const load = async () => { - throw new Error('Server Load Error'); - return { - msg: 'Hello World', - }; -}; diff --git a/dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/src/routes/server-load-error/+page.svelte b/dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/src/routes/server-load-error/+page.svelte deleted file mode 100644 index 3a0942971d06..000000000000 --- a/dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/src/routes/server-load-error/+page.svelte +++ /dev/null @@ -1,9 +0,0 @@ - - -

Server load error

- -

- Message: {data.msg} -

diff --git a/dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/src/routes/server-load-fetch/+page.server.ts b/dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/src/routes/server-load-fetch/+page.server.ts deleted file mode 100644 index 709e52bcf351..000000000000 --- a/dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/src/routes/server-load-fetch/+page.server.ts +++ /dev/null @@ -1,5 +0,0 @@ -export const load = async ({ fetch }) => { - const res = await fetch('/api/users'); - const data = await res.json(); - return { data }; -}; diff --git a/dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/src/routes/server-load-fetch/+page.svelte b/dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/src/routes/server-load-fetch/+page.svelte deleted file mode 100644 index f7f814d31b4d..000000000000 --- a/dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/src/routes/server-load-fetch/+page.svelte +++ /dev/null @@ -1,8 +0,0 @@ - - -
-

Server Load Fetch

-

{JSON.stringify(data, null, 2)}

-
diff --git a/dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/src/routes/server-route-error/+page.svelte b/dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/src/routes/server-route-error/+page.svelte deleted file mode 100644 index 3d682e7e3462..000000000000 --- a/dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/src/routes/server-route-error/+page.svelte +++ /dev/null @@ -1,9 +0,0 @@ - - -

Server Route error

- -

- Message: {data.msg} -

diff --git a/dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/src/routes/server-route-error/+page.ts b/dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/src/routes/server-route-error/+page.ts deleted file mode 100644 index 298240827714..000000000000 --- a/dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/src/routes/server-route-error/+page.ts +++ /dev/null @@ -1,7 +0,0 @@ -export const load = async ({ fetch }) => { - const res = await fetch('/server-route-error'); - const data = await res.json(); - return { - msg: data, - }; -}; diff --git a/dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/src/routes/server-route-error/+server.ts b/dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/src/routes/server-route-error/+server.ts deleted file mode 100644 index f1a4b94b7706..000000000000 --- a/dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/src/routes/server-route-error/+server.ts +++ /dev/null @@ -1,6 +0,0 @@ -export const GET = async () => { - throw new Error('Server Route Error'); - return { - msg: 'Hello World', - }; -}; diff --git a/dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/src/routes/universal-load-error/+page.svelte b/dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/src/routes/universal-load-error/+page.svelte deleted file mode 100644 index dc2d311a0ece..000000000000 --- a/dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/src/routes/universal-load-error/+page.svelte +++ /dev/null @@ -1,17 +0,0 @@ - - -

Universal load error

- -

- To trigger from client: Load on another route, then navigate to this route. -

- -

- To trigger from server: Load on this route -

- -

- Message: {data.msg} -

diff --git a/dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/src/routes/universal-load-error/+page.ts b/dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/src/routes/universal-load-error/+page.ts deleted file mode 100644 index 3d72bf4a890f..000000000000 --- a/dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/src/routes/universal-load-error/+page.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { browser } from '$app/environment'; - -export const load = async () => { - throw new Error(`Universal Load Error (${browser ? 'browser' : 'server'})`); - return { - msg: 'Hello World', - }; -}; diff --git a/dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/src/routes/universal-load-fetch/+page.svelte b/dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/src/routes/universal-load-fetch/+page.svelte deleted file mode 100644 index 563c51e8c850..000000000000 --- a/dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/src/routes/universal-load-fetch/+page.svelte +++ /dev/null @@ -1,14 +0,0 @@ - - -

Fetching in universal load

- -

Here's a list of a few users:

- -
    - {#each data.users as user} -
  • {user}
  • - {/each} -
diff --git a/dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/src/routes/universal-load-fetch/+page.ts b/dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/src/routes/universal-load-fetch/+page.ts deleted file mode 100644 index 63c1ee68e1cb..000000000000 --- a/dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/src/routes/universal-load-fetch/+page.ts +++ /dev/null @@ -1,5 +0,0 @@ -export const load = async ({ fetch }) => { - const usersRes = await fetch('/api/users'); - const data = await usersRes.json(); - return { users: data.users }; -}; diff --git a/dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/src/routes/users/+page.server.ts b/dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/src/routes/users/+page.server.ts deleted file mode 100644 index a34c5450f682..000000000000 --- a/dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/src/routes/users/+page.server.ts +++ /dev/null @@ -1,5 +0,0 @@ -export const load = async () => { - return { - msg: 'Hi everyone!', - }; -}; diff --git a/dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/src/routes/users/+page.svelte b/dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/src/routes/users/+page.svelte deleted file mode 100644 index aa804a4518fa..000000000000 --- a/dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/src/routes/users/+page.svelte +++ /dev/null @@ -1,10 +0,0 @@ - -

- All Users: -

- -

- message: {data.msg} -

diff --git a/dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/src/routes/users/[id]/+page.server.ts b/dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/src/routes/users/[id]/+page.server.ts deleted file mode 100644 index 9388f3927018..000000000000 --- a/dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/src/routes/users/[id]/+page.server.ts +++ /dev/null @@ -1,5 +0,0 @@ -export const load = async ({ params }) => { - return { - msg: `This is a special message for user ${params.id}`, - }; -}; diff --git a/dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/src/routes/users/[id]/+page.svelte b/dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/src/routes/users/[id]/+page.svelte deleted file mode 100644 index d348a8c57dad..000000000000 --- a/dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/src/routes/users/[id]/+page.svelte +++ /dev/null @@ -1,14 +0,0 @@ - - -

Route with dynamic params

- -

- User id: {$page.params.id} -

- -

- Secret message for user: {data.msg} -

diff --git a/dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/start-event-proxy.mjs b/dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/start-event-proxy.mjs deleted file mode 100644 index ad68e74306ea..000000000000 --- a/dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/start-event-proxy.mjs +++ /dev/null @@ -1,6 +0,0 @@ -import { startEventProxyServer } from '@sentry-internal/test-utils'; - -startEventProxyServer({ - port: 3031, - proxyServerName: 'sveltekit-cloudflare-pages', -}); diff --git a/dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/svelte.config.js b/dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/svelte.config.js index 965404f139a8..581cd159f25d 100644 --- a/dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/svelte.config.js +++ b/dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/svelte.config.js @@ -1,18 +1,18 @@ -import adapter from '@sveltejs/adapter-cloudflare'; +import adapter from "@sveltejs/adapter-cloudflare"; import { vitePreprocess } from '@sveltejs/vite-plugin-svelte'; /** @type {import('@sveltejs/kit').Config} */ const config = { - // Consult https://kit.svelte.dev/docs/integrations#preprocessors - // for more information about preprocessors - preprocess: vitePreprocess(), + // Consult https://svelte.dev/docs/kit/integrations + // for more information about preprocessors + preprocess: vitePreprocess(), - kit: { - // adapter-auto only supports some environments, see https://kit.svelte.dev/docs/adapter-auto for a list. - // If your environment is not supported, or you settled on a specific environment, switch out the adapter. - // See https://kit.svelte.dev/docs/adapters for more information about adapters. - adapter: adapter(), - }, + kit: { + // adapter-auto only supports some environments, see https://svelte.dev/docs/kit/adapter-auto for a list. + // If your environment is not supported, or you settled on a specific environment, switch out the adapter. + // See https://svelte.dev/docs/kit/adapters for more information about adapters. + adapter: adapter() + } }; export default config; diff --git a/dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/tests/errors.client.test.ts b/dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/tests/errors.client.test.ts deleted file mode 100644 index 493f03fa9f4a..000000000000 --- a/dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/tests/errors.client.test.ts +++ /dev/null @@ -1,56 +0,0 @@ -import { expect, test } from '@playwright/test'; -import { waitForError } from '@sentry-internal/test-utils'; -import { waitForInitialPageload } from './utils'; - -test.describe('client-side errors', () => { - test('captures error thrown on click', async ({ page }) => { - await waitForInitialPageload(page, { route: '/client-error' }); - - const errorEventPromise = waitForError('sveltekit-cloudflare-pages', 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'); - }); - - test('captures universal load error', async ({ page }) => { - await waitForInitialPageload(page); - await page.reload(); - - const errorEventPromise = waitForError('sveltekit-cloudflare-pages', errorEvent => { - return errorEvent?.exception?.values?.[0]?.value === 'Universal Load Error (browser)'; - }); - - // navigating triggers the error on the client - await page.getByText('Universal Load error').click(); - - const errorEvent = await errorEventPromise; - const errorEventFrames = errorEvent.exception?.values?.[0]?.stacktrace?.frames; - - const lastFrame = errorEventFrames?.[errorEventFrames?.length - 1]; - expect(lastFrame).toEqual( - expect.objectContaining({ - lineno: 1, - in_app: true, - }), - ); - - expect(errorEvent.transaction).toEqual('/universal-load-error'); - }); -}); diff --git a/dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/tests/errors.server.test.ts b/dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/tests/errors.server.test.ts deleted file mode 100644 index a5aa8cae4380..000000000000 --- a/dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/tests/errors.server.test.ts +++ /dev/null @@ -1,90 +0,0 @@ -import { expect, test } from '@playwright/test'; -import { waitForError } from '@sentry-internal/test-utils'; - -test.describe('server-side errors', () => { - test('captures universal load error', async ({ page }) => { - const errorEventPromise = waitForError('sveltekit-cloudflare-pages', errorEvent => { - return errorEvent?.exception?.values?.[0]?.value === 'Universal Load Error (server)'; - }); - - await page.goto('/universal-load-error'); - - const errorEvent = await errorEventPromise; - const errorEventFrames = errorEvent.exception?.values?.[0]?.stacktrace?.frames; - - expect(errorEventFrames?.[errorEventFrames?.length - 1]).toEqual( - expect.objectContaining({ - function: 'load$1', - in_app: true, - }), - ); - - expect(errorEvent.request).toEqual({ - cookies: {}, - headers: expect.objectContaining({ - accept: expect.any(String), - 'user-agent': expect.any(String), - }), - method: 'GET', - url: 'http://localhost:3030/universal-load-error', - }); - }); - - test('captures server load error', async ({ page }) => { - const errorEventPromise = waitForError('sveltekit-cloudflare-pages', errorEvent => { - return errorEvent?.exception?.values?.[0]?.value === 'Server Load Error'; - }); - - await page.goto('/server-load-error'); - - const errorEvent = await errorEventPromise; - const errorEventFrames = errorEvent.exception?.values?.[0]?.stacktrace?.frames; - - expect(errorEventFrames?.[errorEventFrames?.length - 1]).toEqual( - expect.objectContaining({ - function: 'load$1', - in_app: true, - }), - ); - - expect(errorEvent.request).toEqual({ - cookies: {}, - headers: expect.objectContaining({ - accept: expect.any(String), - 'user-agent': expect.any(String), - }), - method: 'GET', - url: 'http://localhost:3030/server-load-error', - }); - }); - - test('captures server route (GET) error', async ({ page }) => { - const errorEventPromise = waitForError('sveltekit-cloudflare-pages', errorEvent => { - return errorEvent?.exception?.values?.[0]?.value === 'Server Route Error'; - }); - - await page.goto('/server-route-error'); - - const errorEvent = await errorEventPromise; - const errorEventFrames = errorEvent.exception?.values?.[0]?.stacktrace?.frames; - - expect(errorEventFrames?.[errorEventFrames?.length - 1]).toEqual( - expect.objectContaining({ - filename: expect.stringContaining('app:///_server.ts'), - function: 'GET', - in_app: true, - }), - ); - - expect(errorEvent.transaction).toEqual('GET /server-route-error'); - - expect(errorEvent.request).toEqual({ - cookies: {}, - headers: expect.objectContaining({ - accept: expect.any(String), - }), - method: 'GET', - url: 'http://localhost:3030/server-route-error', - }); - }); -}); diff --git a/dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/tests/performance.client.test.ts b/dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/tests/performance.client.test.ts deleted file mode 100644 index 50aa7de6309d..000000000000 --- a/dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/tests/performance.client.test.ts +++ /dev/null @@ -1,139 +0,0 @@ -import { expect, test } from '@playwright/test'; -import { waitForTransaction } from '@sentry-internal/test-utils'; -import { waitForInitialPageload } from './utils'; - -test.describe('client-specific performance events', () => { - test('multiple navigations have distinct traces', async ({ page }) => { - const navigationTxn1EventPromise = waitForTransaction('sveltekit-cloudflare-pages', txnEvent => { - return txnEvent?.transaction === '/nav1' && txnEvent.contexts?.trace?.op === 'navigation'; - }); - - const navigationTxn2EventPromise = waitForTransaction('sveltekit-cloudflare-pages', txnEvent => { - return txnEvent?.transaction === '/' && txnEvent.contexts?.trace?.op === 'navigation'; - }); - - const navigationTxn3EventPromise = waitForTransaction('sveltekit-cloudflare-pages', txnEvent => { - return txnEvent?.transaction === '/nav2' && txnEvent.contexts?.trace?.op === 'navigation'; - }); - - await waitForInitialPageload(page); - - await page.getByText('Nav 1').click(); - const navigationTxn1Event = await navigationTxn1EventPromise; - - await page.goBack(); - const navigationTxn2Event = await navigationTxn2EventPromise; - - await page.getByText('Nav 2').click(); - const navigationTxn3Event = await navigationTxn3EventPromise; - - expect(navigationTxn1Event).toMatchObject({ - transaction: '/nav1', - transaction_info: { source: 'route' }, - type: 'transaction', - contexts: { - trace: { - op: 'navigation', - origin: 'auto.navigation.sveltekit', - trace_id: expect.stringMatching(/[a-f0-9]{32}/), - }, - }, - }); - - expect(navigationTxn2Event).toMatchObject({ - transaction: '/', - transaction_info: { source: 'route' }, - type: 'transaction', - contexts: { - trace: { - op: 'navigation', - origin: 'auto.navigation.sveltekit', - trace_id: expect.stringMatching(/[a-f0-9]{32}/), - }, - }, - }); - - expect(navigationTxn3Event).toMatchObject({ - transaction: '/nav2', - transaction_info: { source: 'route' }, - type: 'transaction', - contexts: { - trace: { - op: 'navigation', - origin: 'auto.navigation.sveltekit', - trace_id: expect.stringMatching(/[a-f0-9]{32}/), - }, - }, - }); - - // traces should NOT be connected - expect(navigationTxn1Event.contexts?.trace?.trace_id).not.toBe(navigationTxn2Event.contexts?.trace?.trace_id); - expect(navigationTxn2Event.contexts?.trace?.trace_id).not.toBe(navigationTxn3Event.contexts?.trace?.trace_id); - expect(navigationTxn1Event.contexts?.trace?.trace_id).not.toBe(navigationTxn3Event.contexts?.trace?.trace_id); - }); - - test('records manually added component tracking spans', async ({ page }) => { - const componentTxnEventPromise = waitForTransaction('sveltekit-cloudflare-pages', txnEvent => { - return txnEvent?.transaction === '/components'; - }); - - await waitForInitialPageload(page); - - await page.getByText('Component Tracking').click(); - - const componentTxnEvent = await componentTxnEventPromise; - - expect(componentTxnEvent.spans).toEqual( - expect.arrayContaining([ - expect.objectContaining({ - data: { 'sentry.op': 'ui.svelte.init', 'sentry.origin': 'auto.ui.svelte' }, - description: '', - op: 'ui.svelte.init', - origin: 'auto.ui.svelte', - }), - expect.objectContaining({ - data: { 'sentry.op': 'ui.svelte.init', 'sentry.origin': 'auto.ui.svelte' }, - description: '', - op: 'ui.svelte.init', - origin: 'auto.ui.svelte', - }), - expect.objectContaining({ - data: { 'sentry.op': 'ui.svelte.init', 'sentry.origin': 'auto.ui.svelte' }, - description: '', - op: 'ui.svelte.init', - origin: 'auto.ui.svelte', - }), - expect.objectContaining({ - data: { 'sentry.op': 'ui.svelte.init', 'sentry.origin': 'auto.ui.svelte' }, - description: '', - op: 'ui.svelte.init', - origin: 'auto.ui.svelte', - }), - expect.objectContaining({ - data: { 'sentry.op': 'ui.svelte.update', 'sentry.origin': 'auto.ui.svelte' }, - description: '', - op: 'ui.svelte.update', - origin: 'auto.ui.svelte', - }), - expect.objectContaining({ - data: { 'sentry.op': 'ui.svelte.update', 'sentry.origin': 'auto.ui.svelte' }, - description: '', - op: 'ui.svelte.update', - origin: 'auto.ui.svelte', - }), - expect.objectContaining({ - data: { 'sentry.op': 'ui.svelte.update', 'sentry.origin': 'auto.ui.svelte' }, - description: '', - op: 'ui.svelte.update', - origin: 'auto.ui.svelte', - }), - expect.objectContaining({ - data: { 'sentry.op': 'ui.svelte.update', 'sentry.origin': 'auto.ui.svelte' }, - description: '', - op: 'ui.svelte.update', - origin: 'auto.ui.svelte', - }), - ]), - ); - }); -}); diff --git a/dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/tests/performance.server.test.ts b/dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/tests/performance.server.test.ts deleted file mode 100644 index 1d409efb08b9..000000000000 --- a/dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/tests/performance.server.test.ts +++ /dev/null @@ -1,44 +0,0 @@ -import { expect, test } from '@playwright/test'; -import { waitForTransaction } from '@sentry-internal/test-utils'; - -test('server pageload request span has nested request span for sub request', async ({ page }) => { - const serverTxnEventPromise = waitForTransaction('sveltekit-cloudflare-pages', txnEvent => { - return txnEvent?.transaction === 'GET /server-load-fetch'; - }); - - await page.goto('/server-load-fetch'); - - const serverTxnEvent = await serverTxnEventPromise; - const spans = serverTxnEvent.spans; - - expect(serverTxnEvent).toMatchObject({ - transaction: 'GET /server-load-fetch', - transaction_info: { source: 'route' }, - type: 'transaction', - contexts: { - trace: { - op: 'http.server', - origin: 'auto.http.sveltekit', - }, - }, - }); - - expect(spans).toEqual( - expect.arrayContaining([ - // load span where the server load function initiates the sub request: - expect.objectContaining({ op: 'function.sveltekit.server.load', description: '/server-load-fetch' }), - // sub request span: - expect.objectContaining({ op: 'http.server', description: 'GET /api/users' }), - ]), - ); - - expect(serverTxnEvent.request).toEqual({ - cookies: {}, - headers: expect.objectContaining({ - accept: expect.any(String), - 'user-agent': expect.any(String), - }), - method: 'GET', - url: 'http://localhost:3030/server-load-fetch', - }); -}); diff --git a/dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/tests/performance.test.ts b/dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/tests/performance.test.ts deleted file mode 100644 index f1d49a489aa0..000000000000 --- a/dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/tests/performance.test.ts +++ /dev/null @@ -1,381 +0,0 @@ -import { expect, test } from '@playwright/test'; -import { waitForTransaction } from '@sentry-internal/test-utils'; -import { waitForInitialPageload } from './utils'; - -test.describe('performance events', () => { - test('capture a distributed pageload trace', async ({ page }) => { - const clientTxnEventPromise = waitForTransaction('sveltekit-cloudflare-pages', txnEvent => { - return txnEvent?.transaction === '/users/[id]'; - }); - - const serverTxnEventPromise = waitForTransaction('sveltekit-cloudflare-pages', txnEvent => { - return txnEvent?.transaction === 'GET /users/[id]'; - }); - - const [_, clientTxnEvent, serverTxnEvent] = await Promise.all([ - page.goto('/users/123xyz'), - clientTxnEventPromise, - serverTxnEventPromise, - expect(page.getByText('User id: 123xyz')).toBeVisible(), - ]); - - expect(clientTxnEvent).toMatchObject({ - transaction: '/users/[id]', - transaction_info: { source: 'route' }, - type: 'transaction', - contexts: { - trace: { - op: 'pageload', - origin: 'auto.pageload.sveltekit', - }, - }, - }); - - expect(serverTxnEvent).toMatchObject({ - transaction: 'GET /users/[id]', - transaction_info: { source: 'route' }, - type: 'transaction', - contexts: { - trace: { - op: 'http.server', - origin: 'auto.http.sveltekit', - }, - }, - }); - - expect(clientTxnEvent.spans?.length).toBeGreaterThan(5); - - // connected trace - expect(clientTxnEvent.contexts?.trace?.trace_id).toBe(serverTxnEvent.contexts?.trace?.trace_id); - - // weird but server txn is parent of client txn - expect(clientTxnEvent.contexts?.trace?.parent_span_id).toBe(serverTxnEvent.contexts?.trace?.span_id); - }); - - test('capture a distributed navigation trace', async ({ page }) => { - const clientNavigationTxnEventPromise = waitForTransaction('sveltekit-cloudflare-pages', txnEvent => { - return txnEvent?.transaction === '/users' && txnEvent.contexts?.trace?.op === 'navigation'; - }); - - const serverTxnEventPromise = waitForTransaction('sveltekit-cloudflare-pages', txnEvent => { - return txnEvent?.transaction === 'GET /users'; - }); - - await waitForInitialPageload(page); - - // navigation to page - const clickPromise = page.getByText('Route with Server Load').click(); - - const [clientTxnEvent, serverTxnEvent, _1, _2] = await Promise.all([ - clientNavigationTxnEventPromise, - serverTxnEventPromise, - clickPromise, - expect(page.getByText('Hi everyone')).toBeVisible(), - ]); - - expect(clientTxnEvent).toMatchObject({ - transaction: '/users', - transaction_info: { source: 'route' }, - type: 'transaction', - contexts: { - trace: { - op: 'navigation', - origin: 'auto.navigation.sveltekit', - }, - }, - }); - - expect(serverTxnEvent).toMatchObject({ - transaction: 'GET /users', - transaction_info: { source: 'route' }, - type: 'transaction', - contexts: { - trace: { - op: 'http.server', - origin: 'auto.http.sveltekit', - }, - }, - }); - - // trace is connected - expect(clientTxnEvent.contexts?.trace?.trace_id).toBe(serverTxnEvent.contexts?.trace?.trace_id); - }); - - test('record client-side universal load fetch span and trace', async ({ page }) => { - await waitForInitialPageload(page); - - const clientNavigationTxnEventPromise = waitForTransaction('sveltekit-cloudflare-pages', txnEvent => { - return txnEvent?.transaction === '/universal-load-fetch' && txnEvent.contexts?.trace?.op === 'navigation'; - }); - - // this transaction should be created because of the fetch call - // it should also be part of the trace - const serverTxnEventPromise = waitForTransaction('sveltekit-cloudflare-pages', txnEvent => { - return txnEvent?.transaction === 'GET /api/users'; - }); - - // navigation to page - const clickPromise = page.getByText('Route with fetch in universal load').click(); - - const [clientTxnEvent, serverTxnEvent, _1, _2] = await Promise.all([ - clientNavigationTxnEventPromise, - serverTxnEventPromise, - clickPromise, - expect(page.getByText('alice')).toBeVisible(), - ]); - - expect(clientTxnEvent).toMatchObject({ - transaction: '/universal-load-fetch', - transaction_info: { source: 'route' }, - type: 'transaction', - contexts: { - trace: { - op: 'navigation', - origin: 'auto.navigation.sveltekit', - }, - }, - }); - - expect(serverTxnEvent).toMatchObject({ - transaction: 'GET /api/users', - transaction_info: { source: 'route' }, - type: 'transaction', - contexts: { - trace: { - op: 'http.server', - origin: 'auto.http.sveltekit', - }, - }, - }); - - // trace is connected - expect(clientTxnEvent.contexts?.trace?.trace_id).toBe(serverTxnEvent.contexts?.trace?.trace_id); - - const clientFetchSpan = clientTxnEvent.spans?.find(s => s.op === 'http.client'); - - expect(clientFetchSpan).toMatchObject({ - description: expect.stringMatching(/^GET.*\/api\/users/), - op: 'http.client', - origin: 'auto.http.browser', - data: { - url: expect.stringContaining('/api/users'), - type: 'fetch', - 'http.method': 'GET', - 'http.response.status_code': 200, - 'network.protocol.version': '1.1', - 'network.protocol.name': 'http', - 'http.request.redirect_start': expect.any(Number), - 'http.request.fetch_start': expect.any(Number), - 'http.request.domain_lookup_start': expect.any(Number), - 'http.request.domain_lookup_end': expect.any(Number), - 'http.request.connect_start': expect.any(Number), - 'http.request.secure_connection_start': expect.any(Number), - 'http.request.connection_end': expect.any(Number), - 'http.request.request_start': expect.any(Number), - 'http.request.response_start': expect.any(Number), - 'http.request.response_end': expect.any(Number), - }, - }); - }); - - test('captures a navigation transaction directly after pageload', async ({ page }) => { - const clientPageloadTxnPromise = waitForTransaction('sveltekit-cloudflare-pages', txnEvent => { - return txnEvent?.contexts?.trace?.op === 'pageload'; - }); - - const clientNavigationTxnPromise = waitForTransaction('sveltekit-cloudflare-pages', txnEvent => { - return txnEvent?.contexts?.trace?.op === 'navigation'; - }); - - await waitForInitialPageload(page, { route: '/' }); - - const navigationClickPromise = page.locator('#routeWithParamsLink').click(); - - const [pageloadTxnEvent, navigationTxnEvent, _] = await Promise.all([ - clientPageloadTxnPromise, - clientNavigationTxnPromise, - navigationClickPromise, - ]); - - expect(pageloadTxnEvent).toMatchObject({ - transaction: '/', - transaction_info: { source: 'route' }, - type: 'transaction', - contexts: { - trace: { - op: 'pageload', - origin: 'auto.pageload.sveltekit', - }, - }, - }); - - expect(navigationTxnEvent).toMatchObject({ - transaction: '/users/[id]', - transaction_info: { source: 'route' }, - type: 'transaction', - contexts: { - trace: { - op: 'navigation', - origin: 'auto.navigation.sveltekit', - data: { - 'sentry.sveltekit.navigation.from': '/', - 'sentry.sveltekit.navigation.to': '/users/[id]', - 'sentry.sveltekit.navigation.type': 'link', - }, - }, - }, - }); - - const routingSpans = navigationTxnEvent.spans?.filter(s => s.op === 'ui.sveltekit.routing'); - expect(routingSpans).toHaveLength(1); - - const routingSpan = routingSpans && routingSpans[0]; - expect(routingSpan).toMatchObject({ - op: 'ui.sveltekit.routing', - description: 'SvelteKit Route Change', - data: { - 'sentry.op': 'ui.sveltekit.routing', - 'sentry.origin': 'auto.ui.sveltekit', - 'sentry.sveltekit.navigation.from': '/', - 'sentry.sveltekit.navigation.to': '/users/[id]', - 'sentry.sveltekit.navigation.type': 'link', - }, - }); - }); - - test('captures one navigation transaction per redirect', async ({ page }) => { - const clientNavigationRedirect1TxnPromise = waitForTransaction('sveltekit-cloudflare-pages', txnEvent => { - return txnEvent?.contexts?.trace?.op === 'navigation' && txnEvent?.transaction === '/redirect1'; - }); - - const clientNavigationRedirect2TxnPromise = waitForTransaction('sveltekit-cloudflare-pages', txnEvent => { - return txnEvent?.contexts?.trace?.op === 'navigation' && txnEvent?.transaction === '/redirect2'; - }); - - const clientNavigationRedirect3TxnPromise = waitForTransaction('sveltekit-cloudflare-pages', txnEvent => { - return txnEvent?.contexts?.trace?.op === 'navigation' && txnEvent?.transaction === '/users/[id]'; - }); - - await waitForInitialPageload(page, { route: '/' }); - - const navigationClickPromise = page.locator('#redirectLink').click(); - - const [redirect1TxnEvent, redirect2TxnEvent, redirect3TxnEvent, _] = await Promise.all([ - clientNavigationRedirect1TxnPromise, - clientNavigationRedirect2TxnPromise, - clientNavigationRedirect3TxnPromise, - navigationClickPromise, - ]); - - expect(redirect1TxnEvent).toMatchObject({ - transaction: '/redirect1', - transaction_info: { source: 'route' }, - type: 'transaction', - contexts: { - trace: { - op: 'navigation', - origin: 'auto.navigation.sveltekit', - data: { - 'sentry.origin': 'auto.navigation.sveltekit', - 'sentry.op': 'navigation', - 'sentry.source': 'route', - 'sentry.sveltekit.navigation.type': 'link', - 'sentry.sveltekit.navigation.from': '/', - 'sentry.sveltekit.navigation.to': '/redirect1', - 'sentry.sample_rate': 1, - }, - }, - }, - }); - - const redirect1Spans = redirect1TxnEvent.spans?.filter(s => s.op === 'ui.sveltekit.routing'); - expect(redirect1Spans).toHaveLength(1); - - const redirect1Span = redirect1Spans && redirect1Spans[0]; - expect(redirect1Span).toMatchObject({ - op: 'ui.sveltekit.routing', - description: 'SvelteKit Route Change', - data: { - 'sentry.op': 'ui.sveltekit.routing', - 'sentry.origin': 'auto.ui.sveltekit', - 'sentry.sveltekit.navigation.from': '/', - 'sentry.sveltekit.navigation.to': '/redirect1', - 'sentry.sveltekit.navigation.type': 'link', - }, - }); - - expect(redirect2TxnEvent).toMatchObject({ - transaction: '/redirect2', - transaction_info: { source: 'route' }, - type: 'transaction', - contexts: { - trace: { - op: 'navigation', - origin: 'auto.navigation.sveltekit', - data: { - 'sentry.origin': 'auto.navigation.sveltekit', - 'sentry.op': 'navigation', - 'sentry.source': 'route', - 'sentry.sveltekit.navigation.type': 'goto', - 'sentry.sveltekit.navigation.from': '/', - 'sentry.sveltekit.navigation.to': '/redirect2', - 'sentry.sample_rate': 1, - }, - }, - }, - }); - - const redirect2Spans = redirect2TxnEvent.spans?.filter(s => s.op === 'ui.sveltekit.routing'); - expect(redirect2Spans).toHaveLength(1); - - const redirect2Span = redirect2Spans && redirect2Spans[0]; - expect(redirect2Span).toMatchObject({ - op: 'ui.sveltekit.routing', - description: 'SvelteKit Route Change', - data: { - 'sentry.op': 'ui.sveltekit.routing', - 'sentry.origin': 'auto.ui.sveltekit', - 'sentry.sveltekit.navigation.from': '/', - 'sentry.sveltekit.navigation.to': '/redirect2', - 'sentry.sveltekit.navigation.type': 'goto', - }, - }); - - expect(redirect3TxnEvent).toMatchObject({ - transaction: '/users/[id]', - transaction_info: { source: 'route' }, - type: 'transaction', - contexts: { - trace: { - op: 'navigation', - origin: 'auto.navigation.sveltekit', - data: { - 'sentry.origin': 'auto.navigation.sveltekit', - 'sentry.op': 'navigation', - 'sentry.source': 'route', - 'sentry.sveltekit.navigation.type': 'goto', - 'sentry.sveltekit.navigation.from': '/', - 'sentry.sveltekit.navigation.to': '/users/[id]', - 'sentry.sample_rate': 1, - }, - }, - }, - }); - - const redirect3Spans = redirect3TxnEvent.spans?.filter(s => s.op === 'ui.sveltekit.routing'); - expect(redirect3Spans).toHaveLength(1); - - const redirect3Span = redirect3Spans && redirect3Spans[0]; - expect(redirect3Span).toMatchObject({ - op: 'ui.sveltekit.routing', - description: 'SvelteKit Route Change', - data: { - 'sentry.op': 'ui.sveltekit.routing', - 'sentry.origin': 'auto.ui.sveltekit', - 'sentry.sveltekit.navigation.from': '/', - 'sentry.sveltekit.navigation.to': '/users/[id]', - 'sentry.sveltekit.navigation.type': 'goto', - }, - }); - }); -}); diff --git a/dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/tests/utils.ts b/dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/tests/utils.ts deleted file mode 100644 index 1265500d380a..000000000000 --- a/dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/tests/utils.ts +++ /dev/null @@ -1,49 +0,0 @@ -import { Page } from '@playwright/test'; -import { waitForTransaction } from '@sentry-internal/test-utils'; - -/** - * Helper function that waits for the initial pageload to complete. - * - * This function - * - loads the given route ("/" by default) - * - waits for SvelteKit's hydration - * - waits for the pageload transaction to be sent (doesn't assert on it though) - * - * Useful for tests that test outcomes of _navigations_ after an initial pageload. - * Waiting on the pageload transaction excludes edge cases where navigations occur - * so quickly that the pageload idle transaction is still active. This might lead - * to cases where the routing span would be attached to the pageload transaction - * and hence eliminates a lot of flakiness. - * - */ -export async function waitForInitialPageload( - page: Page, - opts?: { route?: string; parameterizedRoute?: string; debug?: boolean }, -) { - const route = opts?.route ?? '/'; - const txnName = opts?.parameterizedRoute ?? route; - const debug = opts?.debug ?? false; - - const clientPageloadTxnEventPromise = waitForTransaction('sveltekit-cloudflare-pages', txnEvent => { - debug && - console.log({ - txn: txnEvent?.transaction, - op: txnEvent.contexts?.trace?.op, - trace: txnEvent.contexts?.trace?.trace_id, - span: txnEvent.contexts?.trace?.span_id, - parent: txnEvent.contexts?.trace?.parent_span_id, - }); - - return txnEvent?.transaction === txnName && txnEvent.contexts?.trace?.op === 'pageload'; - }); - - await Promise.all([ - page.goto(route), - // the test app adds the "hydrated" class to the body when hydrating - page.waitForSelector('body.hydrated'), - // also waiting for the initial pageload txn so that later navigations don't interfere - clientPageloadTxnEventPromise, - ]); - - debug && console.log('hydrated'); -} diff --git a/dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/tsconfig.json b/dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/tsconfig.json index ba6aa4e6610a..0b2d8865f4ef 100644 --- a/dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/tsconfig.json +++ b/dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/tsconfig.json @@ -10,9 +10,9 @@ "sourceMap": true, "strict": true, "moduleResolution": "bundler" - }, - // Path aliases are handled by https://kit.svelte.dev/docs/configuration#alias - // except $lib which is handled by https://kit.svelte.dev/docs/configuration#files + } + // Path aliases are handled by https://svelte.dev/docs/kit/configuration#alias + // except $lib which is handled by https://svelte.dev/docs/kit/configuration#files // // If you want to overwrite includes/excludes, make sure to copy over the relevant includes/excludes // from the referenced tsconfig.json - TypeScript does not merge them in diff --git a/dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/vite.config.ts b/dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/vite.config.ts index 1a410bee7e11..e68fbfd2778d 100644 --- a/dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/vite.config.ts +++ b/dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/vite.config.ts @@ -1,12 +1,7 @@ -import { sentrySvelteKit } from '@sentry/sveltekit'; import { sveltekit } from '@sveltejs/kit/vite'; import { defineConfig } from 'vite'; +import { sentrySvelteKit } from '@sentry/sveltekit'; export default defineConfig({ - plugins: [ - sentrySvelteKit({ - autoUploadSourceMaps: false, - }), - sveltekit(), - ], + plugins: [sentrySvelteKit({ autoUploadSourceMaps: false }), sveltekit()], }); From f6da869df920fc8793729c303232a75079d24d6c Mon Sep 17 00:00:00 2001 From: Sam Greening <2552620+SG60@users.noreply.github.com> Date: Tue, 24 Dec 2024 12:52:07 +0000 Subject: [PATCH 08/31] test(sveltekit-cloudflare): rename e2e test dir to tests To match other e2e apps --- .../sveltekit-cloudflare-pages/playwright.config.ts | 2 +- .../sveltekit-cloudflare-pages/{e2e => tests}/demo.test.ts | 0 2 files changed, 1 insertion(+), 1 deletion(-) rename dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/{e2e => tests}/demo.test.ts (100%) diff --git a/dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/playwright.config.ts b/dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/playwright.config.ts index db8f8c68c99a..43613d664655 100644 --- a/dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/playwright.config.ts +++ b/dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/playwright.config.ts @@ -6,5 +6,5 @@ export default defineConfig({ port: 4173 }, - testDir: 'e2e' + testDir: 'tests' }); diff --git a/dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/e2e/demo.test.ts b/dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/tests/demo.test.ts similarity index 100% rename from dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/e2e/demo.test.ts rename to dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/tests/demo.test.ts From 159426a0ce5ad75fd0dca3f8931eeb0261bb7c85 Mon Sep 17 00:00:00 2001 From: Sam Greening <2552620+SG60@users.noreply.github.com> Date: Tue, 24 Dec 2024 12:49:58 +0000 Subject: [PATCH 09/31] test(sveltekit-cloudflare): add test of prerendered page test(sveltekit-cloudflare): actually test the prerendered page --- .../sveltekit-cloudflare-pages/src/routes/+page.svelte | 2 ++ .../src/routes/prerender-test/+page.server.ts | 9 +++++++++ .../src/routes/prerender-test/+page.svelte | 6 ++++++ .../sveltekit-cloudflare-pages/tests/demo.test.ts | 5 +++++ 4 files changed, 22 insertions(+) create mode 100644 dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/src/routes/prerender-test/+page.server.ts create mode 100644 dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/src/routes/prerender-test/+page.svelte diff --git a/dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/src/routes/+page.svelte b/dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/src/routes/+page.svelte index 36f518feb34f..e17881ceaca9 100644 --- a/dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/src/routes/+page.svelte +++ b/dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/src/routes/+page.svelte @@ -5,4 +5,6 @@

Welcome to SvelteKit

Visit svelte.dev/docs/kit to read the documentation

+prerender test +

{data.message}

diff --git a/dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/src/routes/prerender-test/+page.server.ts b/dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/src/routes/prerender-test/+page.server.ts new file mode 100644 index 000000000000..a3ede141fc59 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/src/routes/prerender-test/+page.server.ts @@ -0,0 +1,9 @@ +import type { PageServerLoad } from './$types'; + +export const prerender = true; + +export const load: PageServerLoad = async function load() { + return { + message: 'From server load function.', + }; +}; diff --git a/dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/src/routes/prerender-test/+page.svelte b/dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/src/routes/prerender-test/+page.svelte new file mode 100644 index 000000000000..1aea39c3032b --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/src/routes/prerender-test/+page.svelte @@ -0,0 +1,6 @@ + + +

{data.message}

+

Visit svelte.dev/docs/kit to read the documentation

diff --git a/dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/tests/demo.test.ts b/dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/tests/demo.test.ts index 9985ce113eb8..81eaa6f809f4 100644 --- a/dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/tests/demo.test.ts +++ b/dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/tests/demo.test.ts @@ -4,3 +4,8 @@ test('home page has expected h1', async ({ page }) => { await page.goto('/'); await expect(page.locator('h1')).toBeVisible(); }); + +test('prerendered page has expected h1', async ({ page }) => { + await page.goto('/prerender-test'); + await expect(page.locator('h1')).toHaveText('From server load function.'); +}); From f1e6d73883a079d15f0b2748e29c282bcea028b2 Mon Sep 17 00:00:00 2001 From: Sam Greening <2552620+SG60@users.noreply.github.com> Date: Tue, 24 Dec 2024 13:31:44 +0000 Subject: [PATCH 10/31] fix(sveltekit): fix prerender failure when using cloudflare workers --- packages/sveltekit/src/index.types.ts | 4 +++- packages/sveltekit/src/server/handle.ts | 6 ++++++ packages/sveltekit/src/server/index.ts | 2 +- packages/sveltekit/src/worker/handle.ts | 2 ++ 4 files changed, 12 insertions(+), 2 deletions(-) diff --git a/packages/sveltekit/src/index.types.ts b/packages/sveltekit/src/index.types.ts index 1399ffeede3e..bf2edbfb0a0f 100644 --- a/packages/sveltekit/src/index.types.ts +++ b/packages/sveltekit/src/index.types.ts @@ -7,7 +7,9 @@ export * from './server'; export * from './worker'; // Use the ./server version of some functions that are also exported from ./worker -export { wrapServerLoadWithSentry, wrapServerRouteWithSentry, sentryHandle } from './server'; +export { sentryHandle } from './server'; +// Use the ./worker version of some functions that are also exported from ./server +export { initCloudflareSentryHandle } from './worker'; import type { Client, Integration, Options, StackParser } from '@sentry/core'; import type { HandleClientError, HandleServerError } from '@sveltejs/kit'; diff --git a/packages/sveltekit/src/server/handle.ts b/packages/sveltekit/src/server/handle.ts index 359e24c0ee5f..52d2b8f707a6 100644 --- a/packages/sveltekit/src/server/handle.ts +++ b/packages/sveltekit/src/server/handle.ts @@ -68,6 +68,7 @@ export function sentryHandle(handlerOptions?: SentryHandleOptions): Handle { return sentryRequestHandler; } +<<<<<<< HEAD async function instrumentHandle( { event, resolve }: Parameters[0], options: SentryHandleOptions, @@ -147,4 +148,9 @@ export function isFetchProxyRequired(version: string): boolean { // ignore } return true; +======= +/** Documented in `worker/handle.ts` */ +export function initCloudflareSentryHandle(_options: any): Handle { + return ({ event, resolve }) => resolve(event); +>>>>>>> 0f35b2f6a (fix(sveltekit): fix prerender failure when using cloudflare workers) } diff --git a/packages/sveltekit/src/server/index.ts b/packages/sveltekit/src/server/index.ts index 88da3cea13ba..543f2dee402b 100644 --- a/packages/sveltekit/src/server/index.ts +++ b/packages/sveltekit/src/server/index.ts @@ -125,7 +125,7 @@ export * from '@sentry/node'; export { init } from './sdk'; export { handleErrorWithSentry } from '../server-common/handleError'; export { wrapLoadWithSentry, wrapServerLoadWithSentry } from '../server-common/load'; -export { sentryHandle } from './handle'; +export { sentryHandle, initCloudflareSentryHandle } from './handle'; export { wrapServerRouteWithSentry } from '../server-common/serverRoute'; /** diff --git a/packages/sveltekit/src/worker/handle.ts b/packages/sveltekit/src/worker/handle.ts index fb391942e362..15865c0efb83 100644 --- a/packages/sveltekit/src/worker/handle.ts +++ b/packages/sveltekit/src/worker/handle.ts @@ -30,6 +30,8 @@ export function sentryHandle(handlerOptions?: SentryHandleOptions): Handle { /** Initializes Sentry SvelteKit Cloudflare SDK * This should be before the sentryHandle() call. + * + * In Node.js, this is a stub that does nothing. * */ export function initCloudflareSentryHandle(options: CloudflareOptions): Handle { const opts: CloudflareOptions = { From 728cd95a1c10aba614c2ff4ac57dae496a16613f Mon Sep 17 00:00:00 2001 From: Sam Greening <2552620+SG60@users.noreply.github.com> Date: Sat, 28 Dec 2024 22:41:06 +0000 Subject: [PATCH 11/31] test(sveltekit): refactor tests to use new server-common folder --- packages/sveltekit/test/server/handleError.test.ts | 2 +- packages/sveltekit/test/server/load.test.ts | 2 +- packages/sveltekit/test/server/rewriteFramesIntegration.test.ts | 2 +- packages/sveltekit/test/server/utils.test.ts | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/sveltekit/test/server/handleError.test.ts b/packages/sveltekit/test/server/handleError.test.ts index b9a91a0b0e1d..ee62c2fd145b 100644 --- a/packages/sveltekit/test/server/handleError.test.ts +++ b/packages/sveltekit/test/server/handleError.test.ts @@ -3,7 +3,7 @@ import { beforeEach, describe, expect, it, vi } from 'vitest'; import * as SentryNode from '@sentry/node'; import type { HandleServerError, RequestEvent } from '@sveltejs/kit'; -import { handleErrorWithSentry } from '../../src/server/handleError'; +import { handleErrorWithSentry } from '../../src/server-common/handleError'; const mockCaptureException = vi.spyOn(SentryNode, 'captureException').mockImplementation(() => 'xx'); diff --git a/packages/sveltekit/test/server/load.test.ts b/packages/sveltekit/test/server/load.test.ts index 1001d8464ad4..b1722cd21cee 100644 --- a/packages/sveltekit/test/server/load.test.ts +++ b/packages/sveltekit/test/server/load.test.ts @@ -11,7 +11,7 @@ import * as SentryNode from '@sentry/node'; import type { Load, ServerLoad } from '@sveltejs/kit'; import { error, redirect } from '@sveltejs/kit'; -import { wrapLoadWithSentry, wrapServerLoadWithSentry } from '../../src/server/load'; +import { wrapLoadWithSentry, wrapServerLoadWithSentry } from '../../src/server-common/load'; import { getDefaultNodeClientOptions } from '../utils'; const mockCaptureException = vi.spyOn(SentryNode, 'captureException').mockImplementation(() => 'xx'); diff --git a/packages/sveltekit/test/server/rewriteFramesIntegration.test.ts b/packages/sveltekit/test/server/rewriteFramesIntegration.test.ts index 3dfd5d3e460e..1d5ca8d4d695 100644 --- a/packages/sveltekit/test/server/rewriteFramesIntegration.test.ts +++ b/packages/sveltekit/test/server/rewriteFramesIntegration.test.ts @@ -2,7 +2,7 @@ import { rewriteFramesIntegration } from '@sentry/browser'; import { basename } from '@sentry/core'; import type { Event, StackFrame } from '@sentry/core'; -import { rewriteFramesIteratee } from '../../src/server/rewriteFramesIntegration'; +import { rewriteFramesIteratee } from '../../src/server-common/rewriteFramesIntegration'; import type { GlobalWithSentryValues } from '../../src/vite/injectGlobalValues'; describe('rewriteFramesIteratee', () => { diff --git a/packages/sveltekit/test/server/utils.test.ts b/packages/sveltekit/test/server/utils.test.ts index 5e8b9b2b99a3..53e588d683ec 100644 --- a/packages/sveltekit/test/server/utils.test.ts +++ b/packages/sveltekit/test/server/utils.test.ts @@ -1,6 +1,6 @@ import { describe, expect, it } from 'vitest'; -import { getTracePropagationData } from '../../src/server/utils'; +import { getTracePropagationData } from '../../src/server-common/utils'; const MOCK_REQUEST_EVENT: any = { request: { From ed56c93ec00e5cbdb91a0718c54fa72321adaaa5 Mon Sep 17 00:00:00 2001 From: Sam Greening <2552620+SG60@users.noreply.github.com> Date: Sun, 29 Dec 2024 01:22:29 +0000 Subject: [PATCH 12/31] fix(sveltekit): remove deprecated API usage Some APIs imported from `@sentry/cloudflare` have been deprecated on the main branch. --- packages/sveltekit/src/worker/index.ts | 4 ---- 1 file changed, 4 deletions(-) diff --git a/packages/sveltekit/src/worker/index.ts b/packages/sveltekit/src/worker/index.ts index 9947d60fe5d9..139f9cd7594f 100644 --- a/packages/sveltekit/src/worker/index.ts +++ b/packages/sveltekit/src/worker/index.ts @@ -27,8 +27,6 @@ export { close, continueTrace, createTransport, - // eslint-disable-next-line deprecation/deprecation - debugIntegration, dedupeIntegration, extraErrorDataIntegration, flush, @@ -48,8 +46,6 @@ export { isInitialized, lastEventId, linkedErrorsIntegration, - // eslint-disable-next-line deprecation/deprecation - metrics, requestDataIntegration, rewriteFramesIntegration, Scope, From 131f8f6f6c9a55ec7dc9ce512e3efe2d9f98105c Mon Sep 17 00:00:00 2001 From: Sam Greening <2552620+SG60@users.noreply.github.com> Date: Sun, 29 Dec 2024 01:31:59 +0000 Subject: [PATCH 13/31] fix(sveltekit): use the new unified continueTrace function --- .../sveltekit/src/server-common/handle.ts | 22 ++++++---- packages/sveltekit/src/server/handle.ts | 5 +-- packages/sveltekit/src/server/index.ts | 3 +- packages/sveltekit/src/worker/handle.ts | 42 ++++--------------- packages/sveltekit/src/worker/index.ts | 3 +- 5 files changed, 29 insertions(+), 46 deletions(-) diff --git a/packages/sveltekit/src/server-common/handle.ts b/packages/sveltekit/src/server-common/handle.ts index 1811faf3c66b..3031c0bb8ee4 100644 --- a/packages/sveltekit/src/server-common/handle.ts +++ b/packages/sveltekit/src/server-common/handle.ts @@ -1,7 +1,8 @@ -import type { continueTrace, Span } from '@sentry/core'; +import type { Span } from '@sentry/core'; import { SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN, SEMANTIC_ATTRIBUTE_SENTRY_SOURCE, + continueTrace, getActiveSpan, getCurrentScope, getDefaultIsolationScope, @@ -153,13 +154,18 @@ export function isFetchProxyRequired(version: string): boolean { * A SvelteKit handle function that wraps the request for Sentry error and * performance monitoring. * - * Some environments require a different continueTrace function. E.g. Node can use - * the Opentelemetry SDK, whereas Cloudflare cannot. + * Usage: + * ``` + * // src/hooks.server.ts + * import { sentryHandle } from '@sentry/sveltekit'; + * + * export const handle = sentryHandle(); + * + * // Optionally use the `sequence` function to add additional handlers. + * // export const handle = sequence(sentryHandle(), yourCustomHandler); + * ``` */ -export function sentryHandleGeneric( - continueTraceFunction: typeof continueTrace, - handlerOptions?: SentryHandleOptions, -): Handle { +export function sentryHandle(handlerOptions?: SentryHandleOptions): Handle { const options = { handleUnknownRoutes: false, injectFetchProxyScript: true, @@ -189,7 +195,7 @@ export function sentryHandleGeneric( isolationScope.setSDKProcessingMetadata({ normalizedRequest: winterCGRequestToRequestData(input.event.request.clone()), }); - return continueTraceFunction(getTracePropagationData(input.event), () => instrumentHandle(input, options)); + return continueTrace(getTracePropagationData(input.event), () => instrumentHandle(input, options)); }); }; diff --git a/packages/sveltekit/src/server/handle.ts b/packages/sveltekit/src/server/handle.ts index 52d2b8f707a6..6eee6d67dbf5 100644 --- a/packages/sveltekit/src/server/handle.ts +++ b/packages/sveltekit/src/server/handle.ts @@ -68,7 +68,6 @@ export function sentryHandle(handlerOptions?: SentryHandleOptions): Handle { return sentryRequestHandler; } -<<<<<<< HEAD async function instrumentHandle( { event, resolve }: Parameters[0], options: SentryHandleOptions, @@ -148,9 +147,9 @@ export function isFetchProxyRequired(version: string): boolean { // ignore } return true; -======= +} + /** Documented in `worker/handle.ts` */ export function initCloudflareSentryHandle(_options: any): Handle { return ({ event, resolve }) => resolve(event); ->>>>>>> 0f35b2f6a (fix(sveltekit): fix prerender failure when using cloudflare workers) } diff --git a/packages/sveltekit/src/server/index.ts b/packages/sveltekit/src/server/index.ts index 543f2dee402b..ccd09570b674 100644 --- a/packages/sveltekit/src/server/index.ts +++ b/packages/sveltekit/src/server/index.ts @@ -125,7 +125,8 @@ export * from '@sentry/node'; export { init } from './sdk'; export { handleErrorWithSentry } from '../server-common/handleError'; export { wrapLoadWithSentry, wrapServerLoadWithSentry } from '../server-common/load'; -export { sentryHandle, initCloudflareSentryHandle } from './handle'; +export { sentryHandle } from '../server-common/handle'; +export { initCloudflareSentryHandle } from './handle'; export { wrapServerRouteWithSentry } from '../server-common/serverRoute'; /** diff --git a/packages/sveltekit/src/worker/handle.ts b/packages/sveltekit/src/worker/handle.ts index 15865c0efb83..be9609f11d7e 100644 --- a/packages/sveltekit/src/worker/handle.ts +++ b/packages/sveltekit/src/worker/handle.ts @@ -1,33 +1,9 @@ -import { CloudflareOptions, continueTrace, wrapRequestHandler } from '@sentry/cloudflare'; +import { CloudflareOptions, wrapRequestHandler } from '@sentry/cloudflare'; import { getDefaultIntegrations as getDefaultCloudflareIntegrations } from '@sentry/cloudflare'; import type { Handle } from '@sveltejs/kit'; -import { sentryHandleGeneric, SentryHandleOptions } from '../server-common/handle'; import { rewriteFramesIntegration } from '../server-common/rewriteFramesIntegration'; -/** - * A SvelteKit handle function that wraps the request for Sentry error and - * performance monitoring. - * - * This doesn't currently use OTEL, as it isn't available outside of Node - * - * Usage: - * ``` - * // src/hooks.server.ts - * import { sentryHandle } from '@sentry/sveltekit'; - * - * export const handle = sentryHandle(); - * - * // Optionally use the `sequence` function to add additional handlers. - * // export const handle = sequence(sentryHandle(), yourCustomHandler); - * ``` - */ -export function sentryHandle(handlerOptions?: SentryHandleOptions): Handle { - const sentryRequestHandler = sentryHandleGeneric(continueTrace, handlerOptions); - - return sentryRequestHandler; -} - /** Initializes Sentry SvelteKit Cloudflare SDK * This should be before the sentryHandle() call. * @@ -43,14 +19,14 @@ export function initCloudflareSentryHandle(options: CloudflareOptions): Handle { // if event.platform exists (should be there in a cloudflare worker), then do the cloudflare sentry init return event.platform ? wrapRequestHandler( - { - options: opts, - request: event.request, - // @ts-expect-error This will exist in Cloudflare - context: event.platform.context, - }, - () => resolve(event), - ) + { + options: opts, + request: event.request, + // @ts-expect-error This will exist in Cloudflare + context: event.platform.context, + }, + () => resolve(event), + ) : resolve(event); }; diff --git a/packages/sveltekit/src/worker/index.ts b/packages/sveltekit/src/worker/index.ts index 139f9cd7594f..79f206097af6 100644 --- a/packages/sveltekit/src/worker/index.ts +++ b/packages/sveltekit/src/worker/index.ts @@ -10,7 +10,8 @@ // SvelteKit SDK exports: export { handleErrorWithSentry } from '../server-common/handleError'; export { wrapLoadWithSentry, wrapServerLoadWithSentry } from '../server-common/load'; -export { sentryHandle, initCloudflareSentryHandle } from './handle'; +export { sentryHandle } from '../server-common/handle'; +export { initCloudflareSentryHandle } from './handle'; export { wrapServerRouteWithSentry } from '../server-common/serverRoute'; // Re-export some functions from Cloudflare SDK From d27fa35fabd24fef981c8e340c2905643e763bf7 Mon Sep 17 00:00:00 2001 From: Sam Greening <2552620+SG60@users.noreply.github.com> Date: Tue, 21 Jan 2025 12:39:10 +0000 Subject: [PATCH 14/31] chore(sveltekit): fix formatting and lints --- .../playwright.config.ts | 4 ++-- .../sveltekit-cloudflare-pages/src/app.d.ts | 14 ++++++------- .../svelte.config.js | 20 +++++++++---------- .../tests/demo.test.ts | 8 ++++---- .../sveltekit-cloudflare-pages/vite.config.ts | 2 +- packages/sveltekit/rollup.npm.config.mjs | 9 ++++++++- packages/sveltekit/src/server-common/load.ts | 2 +- .../server-common/rewriteFramesIntegration.ts | 4 ++-- .../src/server-common/serverRoute.ts | 2 +- packages/sveltekit/src/server/handle.ts | 2 +- packages/sveltekit/src/worker/handle.ts | 2 +- 11 files changed, 38 insertions(+), 31 deletions(-) diff --git a/dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/playwright.config.ts b/dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/playwright.config.ts index 43613d664655..c4756a54a0e4 100644 --- a/dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/playwright.config.ts +++ b/dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/playwright.config.ts @@ -3,8 +3,8 @@ import { defineConfig } from '@playwright/test'; export default defineConfig({ webServer: { command: 'npm run build && npm run preview', - port: 4173 + port: 4173, }, - testDir: 'tests' + testDir: 'tests', }); diff --git a/dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/src/app.d.ts b/dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/src/app.d.ts index da08e6da592d..520c4217a10c 100644 --- a/dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/src/app.d.ts +++ b/dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/src/app.d.ts @@ -1,13 +1,13 @@ // See https://svelte.dev/docs/kit/types#app.d.ts // for information about these interfaces declare global { - namespace App { - // interface Error {} - // interface Locals {} - // interface PageData {} - // interface PageState {} - // interface Platform {} - } + namespace App { + // interface Error {} + // interface Locals {} + // interface PageData {} + // interface PageState {} + // interface Platform {} + } } export {}; diff --git a/dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/svelte.config.js b/dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/svelte.config.js index 581cd159f25d..3e5d9ebe25ff 100644 --- a/dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/svelte.config.js +++ b/dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/svelte.config.js @@ -1,18 +1,18 @@ -import adapter from "@sveltejs/adapter-cloudflare"; +import adapter from '@sveltejs/adapter-cloudflare'; import { vitePreprocess } from '@sveltejs/vite-plugin-svelte'; /** @type {import('@sveltejs/kit').Config} */ const config = { - // Consult https://svelte.dev/docs/kit/integrations - // for more information about preprocessors - preprocess: vitePreprocess(), + // Consult https://svelte.dev/docs/kit/integrations + // for more information about preprocessors + preprocess: vitePreprocess(), - kit: { - // adapter-auto only supports some environments, see https://svelte.dev/docs/kit/adapter-auto for a list. - // If your environment is not supported, or you settled on a specific environment, switch out the adapter. - // See https://svelte.dev/docs/kit/adapters for more information about adapters. - adapter: adapter() - } + kit: { + // adapter-auto only supports some environments, see https://svelte.dev/docs/kit/adapter-auto for a list. + // If your environment is not supported, or you settled on a specific environment, switch out the adapter. + // See https://svelte.dev/docs/kit/adapters for more information about adapters. + adapter: adapter(), + }, }; export default config; diff --git a/dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/tests/demo.test.ts b/dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/tests/demo.test.ts index 81eaa6f809f4..c3c78acce514 100644 --- a/dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/tests/demo.test.ts +++ b/dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/tests/demo.test.ts @@ -1,11 +1,11 @@ import { expect, test } from '@playwright/test'; test('home page has expected h1', async ({ page }) => { - await page.goto('/'); - await expect(page.locator('h1')).toBeVisible(); + await page.goto('/'); + await expect(page.locator('h1')).toBeVisible(); }); test('prerendered page has expected h1', async ({ page }) => { - await page.goto('/prerender-test'); - await expect(page.locator('h1')).toHaveText('From server load function.'); + await page.goto('/prerender-test'); + await expect(page.locator('h1')).toHaveText('From server load function.'); }); diff --git a/dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/vite.config.ts b/dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/vite.config.ts index e68fbfd2778d..706faf25f2b5 100644 --- a/dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/vite.config.ts +++ b/dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/vite.config.ts @@ -1,6 +1,6 @@ +import { sentrySvelteKit } from '@sentry/sveltekit'; import { sveltekit } from '@sveltejs/kit/vite'; import { defineConfig } from 'vite'; -import { sentrySvelteKit } from '@sentry/sveltekit'; export default defineConfig({ plugins: [sentrySvelteKit({ autoUploadSourceMaps: false }), sveltekit()], diff --git a/packages/sveltekit/rollup.npm.config.mjs b/packages/sveltekit/rollup.npm.config.mjs index 91a460933251..ca0792cb4868 100644 --- a/packages/sveltekit/rollup.npm.config.mjs +++ b/packages/sveltekit/rollup.npm.config.mjs @@ -2,7 +2,14 @@ import { makeBaseNPMConfig, makeNPMConfigVariants } from '@sentry-internal/rollu export default makeNPMConfigVariants( makeBaseNPMConfig({ - entrypoints: ['src/index.server.ts', 'src/index.client.ts', 'src/index.worker.ts', 'src/client/index.ts', 'src/server/index.ts', 'src/worker/index.ts'], + entrypoints: [ + 'src/index.server.ts', + 'src/index.client.ts', + 'src/index.worker.ts', + 'src/client/index.ts', + 'src/server/index.ts', + 'src/worker/index.ts', + ], packageSpecificConfig: { external: ['$app/stores'], output: { diff --git a/packages/sveltekit/src/server-common/load.ts b/packages/sveltekit/src/server-common/load.ts index 3113e8482ff7..49160a65b4a5 100644 --- a/packages/sveltekit/src/server-common/load.ts +++ b/packages/sveltekit/src/server-common/load.ts @@ -1,7 +1,7 @@ import { - addNonEnumerableProperty, SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN, SEMANTIC_ATTRIBUTE_SENTRY_SOURCE, + addNonEnumerableProperty, startSpan, } from '@sentry/core'; import type { LoadEvent, ServerLoadEvent } from '@sveltejs/kit'; diff --git a/packages/sveltekit/src/server-common/rewriteFramesIntegration.ts b/packages/sveltekit/src/server-common/rewriteFramesIntegration.ts index 3ed3e72c1f49..a5cb7f484a31 100644 --- a/packages/sveltekit/src/server-common/rewriteFramesIntegration.ts +++ b/packages/sveltekit/src/server-common/rewriteFramesIntegration.ts @@ -53,8 +53,8 @@ export function rewriteFramesIteratee(frame: StackFrame): StackFrame { if (isWindowsFrame || startsWithSlash) { const filename = isWindowsFrame ? frame.filename - .replace(/^[a-zA-Z]:/, '') // remove Windows-style prefix - .replace(/\\/g, '/') // replace all `\\` instances with `/` + .replace(/^[a-zA-Z]:/, '') // remove Windows-style prefix + .replace(/\\/g, '/') // replace all `\\` instances with `/` : frame.filename; let strippedFilename; diff --git a/packages/sveltekit/src/server-common/serverRoute.ts b/packages/sveltekit/src/server-common/serverRoute.ts index 72607318ecb3..1b2169c58b8c 100644 --- a/packages/sveltekit/src/server-common/serverRoute.ts +++ b/packages/sveltekit/src/server-common/serverRoute.ts @@ -1,7 +1,7 @@ import { - addNonEnumerableProperty, SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN, SEMANTIC_ATTRIBUTE_SENTRY_SOURCE, + addNonEnumerableProperty, startSpan, } from '@sentry/core'; import type { RequestEvent } from '@sveltejs/kit'; diff --git a/packages/sveltekit/src/server/handle.ts b/packages/sveltekit/src/server/handle.ts index 6eee6d67dbf5..0d806b0a8641 100644 --- a/packages/sveltekit/src/server/handle.ts +++ b/packages/sveltekit/src/server/handle.ts @@ -150,6 +150,6 @@ export function isFetchProxyRequired(version: string): boolean { } /** Documented in `worker/handle.ts` */ -export function initCloudflareSentryHandle(_options: any): Handle { +export function initCloudflareSentryHandle(_options: unknown): Handle { return ({ event, resolve }) => resolve(event); } diff --git a/packages/sveltekit/src/worker/handle.ts b/packages/sveltekit/src/worker/handle.ts index be9609f11d7e..fb8637044f8f 100644 --- a/packages/sveltekit/src/worker/handle.ts +++ b/packages/sveltekit/src/worker/handle.ts @@ -1,4 +1,4 @@ -import { CloudflareOptions, wrapRequestHandler } from '@sentry/cloudflare'; +import { type CloudflareOptions, wrapRequestHandler } from '@sentry/cloudflare'; import { getDefaultIntegrations as getDefaultCloudflareIntegrations } from '@sentry/cloudflare'; import type { Handle } from '@sveltejs/kit'; From a42a8c8dfb07e6df386d449c0be232da2e604917 Mon Sep 17 00:00:00 2001 From: Sam Greening <2552620+SG60@users.noreply.github.com> Date: Tue, 21 Jan 2025 13:05:04 +0000 Subject: [PATCH 15/31] test(sveltekit): fix sveltekit unit tests Need to use imports from @sentry/core, otherwise the scope contexts are not matching. --- packages/sveltekit/test/server/handle.test.ts | 9 +++++---- packages/sveltekit/test/server/handleError.test.ts | 4 ++-- packages/sveltekit/test/server/load.test.ts | 4 ++-- packages/sveltekit/test/server/serverRoute.test.ts | 6 +++--- 4 files changed, 12 insertions(+), 11 deletions(-) diff --git a/packages/sveltekit/test/server/handle.test.ts b/packages/sveltekit/test/server/handle.test.ts index b2adb50d91b8..9485b3c025b6 100644 --- a/packages/sveltekit/test/server/handle.test.ts +++ b/packages/sveltekit/test/server/handle.test.ts @@ -9,15 +9,16 @@ import { } from '@sentry/core'; import type { EventEnvelopeHeaders, Span } from '@sentry/core'; import { NodeClient, setCurrentClient } from '@sentry/node'; -import * as SentryNode from '@sentry/node'; +import * as SentryCore from '@sentry/core'; import type { Handle } from '@sveltejs/kit'; import { redirect } from '@sveltejs/kit'; import { vi } from 'vitest'; -import { FETCH_PROXY_SCRIPT, addSentryCodeToPage, isFetchProxyRequired, sentryHandle } from '../../src/server/handle'; +import { FETCH_PROXY_SCRIPT, addSentryCodeToPage, isFetchProxyRequired } from '../../src/server-common/handle'; +import { sentryHandle } from '../../src/server-common/handle'; import { getDefaultNodeClientOptions } from '../utils'; -const mockCaptureException = vi.spyOn(SentryNode, 'captureException').mockImplementation(() => 'xx'); +const mockCaptureException = vi.spyOn(SentryCore, 'captureException').mockImplementation(() => 'xx'); function mockEvent(override: Record = {}): Parameters[0]['event'] { const event: Parameters[0]['event'] = { @@ -394,7 +395,7 @@ describe('addSentryCodeToPage', () => { it('adds meta tags and the fetch proxy script if there is an active transaction', () => { const transformPageChunk = addSentryCodeToPage({ injectFetchProxyScript: true }); - SentryNode.startSpan({ name: 'test' }, () => { + SentryCore.startSpan({ name: 'test' }, () => { const transformed = transformPageChunk({ html, done: true }) as string; expect(transformed).toContain(' 'xx'); +const mockCaptureException = vi.spyOn(SentryCore, 'captureException').mockImplementation(() => 'xx'); const captureExceptionEventHint = { mechanism: { handled: false, type: 'sveltekit' }, diff --git a/packages/sveltekit/test/server/load.test.ts b/packages/sveltekit/test/server/load.test.ts index b1722cd21cee..d55488ea151d 100644 --- a/packages/sveltekit/test/server/load.test.ts +++ b/packages/sveltekit/test/server/load.test.ts @@ -7,14 +7,14 @@ import { } from '@sentry/core'; import type { Event } from '@sentry/core'; import { NodeClient, getCurrentScope, getIsolationScope, setCurrentClient } from '@sentry/node'; -import * as SentryNode from '@sentry/node'; +import * as SentryCore from '@sentry/core'; import type { Load, ServerLoad } from '@sveltejs/kit'; import { error, redirect } from '@sveltejs/kit'; import { wrapLoadWithSentry, wrapServerLoadWithSentry } from '../../src/server-common/load'; import { getDefaultNodeClientOptions } from '../utils'; -const mockCaptureException = vi.spyOn(SentryNode, 'captureException').mockImplementation(() => 'xx'); +const mockCaptureException = vi.spyOn(SentryCore, 'captureException').mockImplementation(() => 'xx'); const mockStartSpan = vi.fn(); diff --git a/packages/sveltekit/test/server/serverRoute.test.ts b/packages/sveltekit/test/server/serverRoute.test.ts index de99db5a548e..046c3673a8c7 100644 --- a/packages/sveltekit/test/server/serverRoute.test.ts +++ b/packages/sveltekit/test/server/serverRoute.test.ts @@ -1,4 +1,4 @@ -import * as SentryNode from '@sentry/node'; +import * as SentryCore from '@sentry/core'; import type { NumericRange } from '@sveltejs/kit'; import { type RequestEvent, error, redirect } from '@sveltejs/kit'; import { beforeEach, describe, expect, it, vi } from 'vitest'; @@ -26,7 +26,7 @@ describe('wrapServerRouteWithSentry', () => { }); describe('wraps a server route span around the original server route handler', () => { - const startSpanSpy = vi.spyOn(SentryNode, 'startSpan'); + const startSpanSpy = vi.spyOn(SentryCore, 'startSpan'); it('assigns the route id as name if available', () => { const wrappedRouteHandler = wrapServerRouteWithSentry(originalRouteHandler); @@ -71,7 +71,7 @@ describe('wrapServerRouteWithSentry', () => { }); }); - const captureExceptionSpy = vi.spyOn(SentryNode, 'captureException'); + const captureExceptionSpy = vi.spyOn(SentryCore, 'captureException'); describe('captures server route errors', () => { it('captures and rethrows normal server route error', async () => { const error = new Error('Server Route Error'); From 4c2f4d7eacf37cf369884fee257ccef4ce08e701 Mon Sep 17 00:00:00 2001 From: Sam Greening <2552620+SG60@users.noreply.github.com> Date: Tue, 21 Jan 2025 13:14:27 +0000 Subject: [PATCH 16/31] chore(sveltekit): formatting --- packages/sveltekit/test/server/handle.test.ts | 2 +- packages/sveltekit/test/server/load.test.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/sveltekit/test/server/handle.test.ts b/packages/sveltekit/test/server/handle.test.ts index 9485b3c025b6..f0ca31bfd41f 100644 --- a/packages/sveltekit/test/server/handle.test.ts +++ b/packages/sveltekit/test/server/handle.test.ts @@ -8,8 +8,8 @@ import { spanToJSON, } from '@sentry/core'; import type { EventEnvelopeHeaders, Span } from '@sentry/core'; -import { NodeClient, setCurrentClient } from '@sentry/node'; import * as SentryCore from '@sentry/core'; +import { NodeClient, setCurrentClient } from '@sentry/node'; import type { Handle } from '@sveltejs/kit'; import { redirect } from '@sveltejs/kit'; import { vi } from 'vitest'; diff --git a/packages/sveltekit/test/server/load.test.ts b/packages/sveltekit/test/server/load.test.ts index d55488ea151d..8530208347a4 100644 --- a/packages/sveltekit/test/server/load.test.ts +++ b/packages/sveltekit/test/server/load.test.ts @@ -6,8 +6,8 @@ import { SEMANTIC_ATTRIBUTE_SENTRY_SOURCE, } from '@sentry/core'; import type { Event } from '@sentry/core'; -import { NodeClient, getCurrentScope, getIsolationScope, setCurrentClient } from '@sentry/node'; import * as SentryCore from '@sentry/core'; +import { NodeClient, getCurrentScope, getIsolationScope, setCurrentClient } from '@sentry/node'; import type { Load, ServerLoad } from '@sveltejs/kit'; import { error, redirect } from '@sveltejs/kit'; From 83be44fff2541f1452d82ccda306a908908c2f3c Mon Sep 17 00:00:00 2001 From: Lukas Stracke Date: Fri, 31 Jan 2025 12:35:02 +0100 Subject: [PATCH 17/31] avoid double request isolation, add tests --- .../sveltekit/src/server-common/handle.ts | 44 ++++++++++--------- packages/sveltekit/src/server/handle.ts | 5 ++- packages/sveltekit/src/worker/handle.ts | 34 ++++++++------ packages/sveltekit/test/server/handle.test.ts | 18 ++++++++ 4 files changed, 67 insertions(+), 34 deletions(-) diff --git a/packages/sveltekit/src/server-common/handle.ts b/packages/sveltekit/src/server-common/handle.ts index 3031c0bb8ee4..bde2ba701196 100644 --- a/packages/sveltekit/src/server-common/handle.ts +++ b/packages/sveltekit/src/server-common/handle.ts @@ -73,6 +73,22 @@ export function addSentryCodeToPage(options: { injectFetchProxyScript: boolean } }; } +/** + * We only need to inject the fetch proxy script for SvelteKit versions < 2.16.0. + * Exported only for testing. + */ +export function isFetchProxyRequired(version: string): boolean { + try { + const [major, minor] = version.trim().replace(/-.*/, '').split('.').map(Number); + if (major != null && minor != null && (major > 2 || (major === 2 && minor >= 16))) { + return false; + } + } catch { + // ignore + } + return true; +} + async function instrumentHandle( { event, resolve }: Parameters[0], options: SentryHandleOptions, @@ -134,22 +150,6 @@ async function instrumentHandle( } } -/** - * We only need to inject the fetch proxy script for SvelteKit versions < 2.16.0. - * Exported only for testing. - */ -export function isFetchProxyRequired(version: string): boolean { - try { - const [major, minor] = version.trim().replace(/-.*/, '').split('.').map(Number); - if (major != null && minor != null && (major > 2 || (major === 2 && minor >= 16))) { - return false; - } - } catch { - // ignore - } - return true; -} - /** * A SvelteKit handle function that wraps the request for Sentry error and * performance monitoring. @@ -166,10 +166,10 @@ export function isFetchProxyRequired(version: string): boolean { * ``` */ export function sentryHandle(handlerOptions?: SentryHandleOptions): Handle { + const { handleUnknownRoutes, ...rest } = handlerOptions ?? {}; const options = { - handleUnknownRoutes: false, - injectFetchProxyScript: true, - ...handlerOptions, + handleUnknownRoutes: handleUnknownRoutes ?? false, + ...rest, }; const sentryRequestHandler: Handle = input => { @@ -185,7 +185,11 @@ export function sentryHandle(handlerOptions?: SentryHandleOptions): Handle { // we create a new execution context. const isSubRequest = typeof input.event.isSubRequest === 'boolean' ? input.event.isSubRequest : !!getActiveSpan(); - if (isSubRequest) { + // Escape hatch to suppress request isolation and trace continuation (see initCloudflareSentryHandle) + const skipIsolation = + '_sentrySkipRequestIsolation' in input.event.locals && input.event.locals._sentrySkipRequestIsolation; + + if (isSubRequest || skipIsolation) { return instrumentHandle(input, options); } diff --git a/packages/sveltekit/src/server/handle.ts b/packages/sveltekit/src/server/handle.ts index 0d806b0a8641..6c4eca0e9b07 100644 --- a/packages/sveltekit/src/server/handle.ts +++ b/packages/sveltekit/src/server/handle.ts @@ -149,7 +149,10 @@ export function isFetchProxyRequired(version: string): boolean { return true; } -/** Documented in `worker/handle.ts` */ +/** + * actual implementation in ../worker/handle.ts + * @return no-op handler when initCLoudflareSentryHandle is called via node/server entry point + */ export function initCloudflareSentryHandle(_options: unknown): Handle { return ({ event, resolve }) => resolve(event); } diff --git a/packages/sveltekit/src/worker/handle.ts b/packages/sveltekit/src/worker/handle.ts index fb8637044f8f..1831c9de5906 100644 --- a/packages/sveltekit/src/worker/handle.ts +++ b/packages/sveltekit/src/worker/handle.ts @@ -3,12 +3,13 @@ import { getDefaultIntegrations as getDefaultCloudflareIntegrations } from '@sen import type { Handle } from '@sveltejs/kit'; import { rewriteFramesIntegration } from '../server-common/rewriteFramesIntegration'; +import { addNonEnumerableProperty } from '@sentry/core'; /** Initializes Sentry SvelteKit Cloudflare SDK * This should be before the sentryHandle() call. * - * In Node.js, this is a stub that does nothing. - * */ + * In the Node export, this is a stub that does nothing. + */ export function initCloudflareSentryHandle(options: CloudflareOptions): Handle { const opts: CloudflareOptions = { defaultIntegrations: [...getDefaultCloudflareIntegrations(options), rewriteFramesIntegration()], @@ -17,17 +18,24 @@ export function initCloudflareSentryHandle(options: CloudflareOptions): Handle { const handleInitSentry: Handle = ({ event, resolve }) => { // if event.platform exists (should be there in a cloudflare worker), then do the cloudflare sentry init - return event.platform - ? wrapRequestHandler( - { - options: opts, - request: event.request, - // @ts-expect-error This will exist in Cloudflare - context: event.platform.context, - }, - () => resolve(event), - ) - : resolve(event); + if (event.platform) { + // This is an optional local that the `sentryHandle` handler checks for to avoid double isolation + // In Cloudflare the `wrapRequestHandler` function already takes care of + // - request isolation + // - trace continuation + // -setting the request onto the scope + addNonEnumerableProperty(event.locals, '_sentrySkipRequestIsolation', true); + return wrapRequestHandler( + { + options: opts, + request: event.request, + // @ts-expect-error This will exist in Cloudflare + context: event.platform.context, + }, + () => resolve(event), + ); + } + return resolve(event); }; return handleInitSentry; diff --git a/packages/sveltekit/test/server/handle.test.ts b/packages/sveltekit/test/server/handle.test.ts index f0ca31bfd41f..9c6e2b71d330 100644 --- a/packages/sveltekit/test/server/handle.test.ts +++ b/packages/sveltekit/test/server/handle.test.ts @@ -99,6 +99,7 @@ beforeEach(() => { client.init(); mockCaptureException.mockClear(); + vi.clearAllMocks(); }); describe('sentryHandle', () => { @@ -367,6 +368,23 @@ describe('sentryHandle', () => { expect(_span!).toBeDefined(); }); + + it("doesn't create an isolation scope when the `_sentrySkipRequestIsolation` local is set", async () => { + const withIsolationScopeSpy = vi.spyOn(SentryCore, 'withIsolationScope'); + const continueTraceSpy = vi.spyOn(SentryCore, 'continueTrace'); + + try { + await sentryHandle({ handleUnknownRoutes: true })({ + event: { ...mockEvent({ route: undefined }), locals: { _sentrySkipRequestIsolation: true } }, + resolve: resolve(type, isError), + }); + } catch { + // + } + + expect(withIsolationScopeSpy).not.toHaveBeenCalled(); + expect(continueTraceSpy).not.toHaveBeenCalled(); + }); }); }); From 189aa5cbd7f9fc261490f6d32491af82fc779aba Mon Sep 17 00:00:00 2001 From: Lukas Stracke Date: Fri, 31 Jan 2025 12:35:57 +0100 Subject: [PATCH 18/31] formatting --- packages/sveltekit/src/worker/handle.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/sveltekit/src/worker/handle.ts b/packages/sveltekit/src/worker/handle.ts index 1831c9de5906..5db74ad423f2 100644 --- a/packages/sveltekit/src/worker/handle.ts +++ b/packages/sveltekit/src/worker/handle.ts @@ -2,8 +2,8 @@ import { type CloudflareOptions, wrapRequestHandler } from '@sentry/cloudflare'; import { getDefaultIntegrations as getDefaultCloudflareIntegrations } from '@sentry/cloudflare'; import type { Handle } from '@sveltejs/kit'; -import { rewriteFramesIntegration } from '../server-common/rewriteFramesIntegration'; import { addNonEnumerableProperty } from '@sentry/core'; +import { rewriteFramesIntegration } from '../server-common/rewriteFramesIntegration'; /** Initializes Sentry SvelteKit Cloudflare SDK * This should be before the sentryHandle() call. From fee1d2b9152d4c65eab511df267fa9f07f19bcdf Mon Sep 17 00:00:00 2001 From: Lukas Stracke Date: Fri, 31 Jan 2025 16:43:19 +0100 Subject: [PATCH 19/31] cleanup --- .../sveltekit-cloudflare-pages/package.json | 48 +++++++++---------- .../src/hooks.server.ts | 1 + .../src/lib/index.ts | 1 - packages/sveltekit/src/common/utils.ts | 2 + .../server-common/rewriteFramesIntegration.ts | 2 +- packages/sveltekit/src/server/sdk.ts | 2 +- packages/sveltekit/src/vite/autoInstrument.ts | 3 +- packages/sveltekit/src/vite/constants.ts | 1 - packages/sveltekit/src/vite/sourceMaps.ts | 2 +- packages/sveltekit/src/worker/handle.ts | 42 ---------------- packages/sveltekit/src/worker/index.ts | 2 +- 11 files changed, 32 insertions(+), 74 deletions(-) delete mode 100644 dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/src/lib/index.ts delete mode 100644 packages/sveltekit/src/vite/constants.ts delete mode 100644 packages/sveltekit/src/worker/handle.ts diff --git a/dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/package.json b/dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/package.json index 412f4281c256..25806f07bfbf 100644 --- a/dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/package.json +++ b/dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/package.json @@ -1,31 +1,31 @@ { - "name": "sveltekit-cloudflare-pages", - "private": true, - "version": "0.0.1", - "type": "module", - "scripts": { - "dev": "vite dev", - "build": "vite build", - "preview": "wrangler pages dev ./.svelte-kit/cloudflare --port 4173", - "check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json", - "check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch", - "test:e2e": "playwright test", - "test": "npm run test:e2e", + "name": "sveltekit-cloudflare-pages", + "private": true, + "version": "0.0.1", + "type": "module", + "scripts": { + "dev": "vite dev", + "build": "vite build", + "preview": "wrangler pages dev ./.svelte-kit/cloudflare --port 4173", + "check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json", + "check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch", + "test:e2e": "playwright test", + "test": "npm run test:e2e", "test:build": "pnpm install && pnpm build", "test:assert": "npm run test:e2e" - }, + }, "dependencies": { "@sentry/sveltekit": "latest || *" }, - "devDependencies": { - "@playwright/test": "^1.45.3", - "@sveltejs/adapter-cloudflare": "^4.8.0", - "@sveltejs/kit": "^2.9.0", - "@sveltejs/vite-plugin-svelte": "^5.0.0", - "svelte": "^5.0.0", - "svelte-check": "^4.0.0", - "typescript": "^5.0.0", - "vite": "^6.0.0", - "wrangler": "^3" - } + "devDependencies": { + "@playwright/test": "^1.45.3", + "@sveltejs/adapter-cloudflare": "^4.8.0", + "@sveltejs/kit": "^2.16.0", + "@sveltejs/vite-plugin-svelte": "^5.0.0", + "svelte": "^5.0.0", + "svelte-check": "^4.0.0", + "typescript": "^5.0.0", + "vite": "^6.0.0", + "wrangler": "^3" + } } diff --git a/dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/src/hooks.server.ts b/dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/src/hooks.server.ts index d9dbd4a356a0..d5067459d565 100644 --- a/dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/src/hooks.server.ts +++ b/dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/src/hooks.server.ts @@ -7,6 +7,7 @@ export const handleError = handleErrorWithSentry(); export const handle = sequence( initCloudflareSentryHandle({ dsn: E2E_TEST_DSN, + tracesSampleRate: 1.0, }), sentryHandle(), ); diff --git a/dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/src/lib/index.ts b/dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/src/lib/index.ts deleted file mode 100644 index 856f2b6c38ae..000000000000 --- a/dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/src/lib/index.ts +++ /dev/null @@ -1 +0,0 @@ -// place files you want to import through the `$lib` alias in this folder. diff --git a/packages/sveltekit/src/common/utils.ts b/packages/sveltekit/src/common/utils.ts index 84b384861dff..1362ee82293c 100644 --- a/packages/sveltekit/src/common/utils.ts +++ b/packages/sveltekit/src/common/utils.ts @@ -1,5 +1,7 @@ import type { HttpError, Redirect } from '@sveltejs/kit'; +export const WRAPPED_MODULE_SUFFIX = '?sentry-auto-wrap'; + export type SentryWrappedFlag = { /** * If this flag is set, we know that the load event was already wrapped once diff --git a/packages/sveltekit/src/server-common/rewriteFramesIntegration.ts b/packages/sveltekit/src/server-common/rewriteFramesIntegration.ts index a5cb7f484a31..a0e8351ba84f 100644 --- a/packages/sveltekit/src/server-common/rewriteFramesIntegration.ts +++ b/packages/sveltekit/src/server-common/rewriteFramesIntegration.ts @@ -7,8 +7,8 @@ import { join, rewriteFramesIntegration as originalRewriteFramesIntegration, } from '@sentry/core'; -import { WRAPPED_MODULE_SUFFIX } from '../vite/constants'; import type { GlobalWithSentryValues } from '../vite/injectGlobalValues'; +import { WRAPPED_MODULE_SUFFIX } from '../common/utils'; type StackFrameIteratee = (frame: StackFrame) => StackFrame; interface RewriteFramesOptions { diff --git a/packages/sveltekit/src/server/sdk.ts b/packages/sveltekit/src/server/sdk.ts index 60e6d8e9824c..66362e96a729 100644 --- a/packages/sveltekit/src/server/sdk.ts +++ b/packages/sveltekit/src/server/sdk.ts @@ -6,7 +6,7 @@ import { init as initNodeSdk } from '@sentry/node'; import { rewriteFramesIntegration } from '../server-common/rewriteFramesIntegration'; /** - * + * Initialize the Server-side Sentry SDK * @param options */ export function init(options: NodeOptions): NodeClient | undefined { diff --git a/packages/sveltekit/src/vite/autoInstrument.ts b/packages/sveltekit/src/vite/autoInstrument.ts index ef11981b4262..8303af502f90 100644 --- a/packages/sveltekit/src/vite/autoInstrument.ts +++ b/packages/sveltekit/src/vite/autoInstrument.ts @@ -3,8 +3,7 @@ import * as path from 'path'; import type { ExportNamedDeclaration } from '@babel/types'; import { parseModule } from 'magicast'; import type { Plugin } from 'vite'; - -import { WRAPPED_MODULE_SUFFIX } from './constants'; +import { WRAPPED_MODULE_SUFFIX } from '../common/utils'; export type AutoInstrumentSelection = { /** diff --git a/packages/sveltekit/src/vite/constants.ts b/packages/sveltekit/src/vite/constants.ts deleted file mode 100644 index a0e160fdd272..000000000000 --- a/packages/sveltekit/src/vite/constants.ts +++ /dev/null @@ -1 +0,0 @@ -export const WRAPPED_MODULE_SUFFIX = '?sentry-auto-wrap'; diff --git a/packages/sveltekit/src/vite/sourceMaps.ts b/packages/sveltekit/src/vite/sourceMaps.ts index 303bf2983cf3..6e41d7f4950b 100644 --- a/packages/sveltekit/src/vite/sourceMaps.ts +++ b/packages/sveltekit/src/vite/sourceMaps.ts @@ -9,11 +9,11 @@ import { sentryVitePlugin } from '@sentry/vite-plugin'; import type { Plugin, UserConfig } from 'vite'; import MagicString from 'magic-string'; -import { WRAPPED_MODULE_SUFFIX } from './constants'; import type { GlobalSentryValues } from './injectGlobalValues'; import { VIRTUAL_GLOBAL_VALUES_FILE, getGlobalValueInjectionCode } from './injectGlobalValues'; import { getAdapterOutputDir, getHooksFileName, loadSvelteConfig } from './svelteConfig'; import type { CustomSentryVitePluginOptions } from './types'; +import { WRAPPED_MODULE_SUFFIX } from '../common/utils'; // sorcery has no types, so these are some basic type definitions: type Chain = { diff --git a/packages/sveltekit/src/worker/handle.ts b/packages/sveltekit/src/worker/handle.ts deleted file mode 100644 index 5db74ad423f2..000000000000 --- a/packages/sveltekit/src/worker/handle.ts +++ /dev/null @@ -1,42 +0,0 @@ -import { type CloudflareOptions, wrapRequestHandler } from '@sentry/cloudflare'; -import { getDefaultIntegrations as getDefaultCloudflareIntegrations } from '@sentry/cloudflare'; -import type { Handle } from '@sveltejs/kit'; - -import { addNonEnumerableProperty } from '@sentry/core'; -import { rewriteFramesIntegration } from '../server-common/rewriteFramesIntegration'; - -/** Initializes Sentry SvelteKit Cloudflare SDK - * This should be before the sentryHandle() call. - * - * In the Node export, this is a stub that does nothing. - */ -export function initCloudflareSentryHandle(options: CloudflareOptions): Handle { - const opts: CloudflareOptions = { - defaultIntegrations: [...getDefaultCloudflareIntegrations(options), rewriteFramesIntegration()], - ...options, - }; - - const handleInitSentry: Handle = ({ event, resolve }) => { - // if event.platform exists (should be there in a cloudflare worker), then do the cloudflare sentry init - if (event.platform) { - // This is an optional local that the `sentryHandle` handler checks for to avoid double isolation - // In Cloudflare the `wrapRequestHandler` function already takes care of - // - request isolation - // - trace continuation - // -setting the request onto the scope - addNonEnumerableProperty(event.locals, '_sentrySkipRequestIsolation', true); - return wrapRequestHandler( - { - options: opts, - request: event.request, - // @ts-expect-error This will exist in Cloudflare - context: event.platform.context, - }, - () => resolve(event), - ); - } - return resolve(event); - }; - - return handleInitSentry; -} diff --git a/packages/sveltekit/src/worker/index.ts b/packages/sveltekit/src/worker/index.ts index 79f206097af6..a74989b7d28e 100644 --- a/packages/sveltekit/src/worker/index.ts +++ b/packages/sveltekit/src/worker/index.ts @@ -11,7 +11,7 @@ export { handleErrorWithSentry } from '../server-common/handleError'; export { wrapLoadWithSentry, wrapServerLoadWithSentry } from '../server-common/load'; export { sentryHandle } from '../server-common/handle'; -export { initCloudflareSentryHandle } from './handle'; +export { initCloudflareSentryHandle } from './cloudflare'; export { wrapServerRouteWithSentry } from '../server-common/serverRoute'; // Re-export some functions from Cloudflare SDK From 99b0d150ad8433fbee84edfc1ceca58824db6049 Mon Sep 17 00:00:00 2001 From: Lukas Stracke Date: Fri, 31 Jan 2025 16:47:57 +0100 Subject: [PATCH 20/31] init SDK configured for CF in dev mode --- packages/sveltekit/src/server/handle.ts | 21 +++++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/packages/sveltekit/src/server/handle.ts b/packages/sveltekit/src/server/handle.ts index 6c4eca0e9b07..fee633f6f803 100644 --- a/packages/sveltekit/src/server/handle.ts +++ b/packages/sveltekit/src/server/handle.ts @@ -150,9 +150,22 @@ export function isFetchProxyRequired(version: string): boolean { } /** - * actual implementation in ../worker/handle.ts - * @return no-op handler when initCLoudflareSentryHandle is called via node/server entry point + * Actual implementation in ../worker/handle.ts + * + * This handler initializes the Sentry Node(!) SDK with the passed options. This is necessary to get + * the SDK configured for cloudflare working in dev mode. + * + * @return version of initCLoudflareSentryHandle that is called via node/server entry point */ -export function initCloudflareSentryHandle(_options: unknown): Handle { - return ({ event, resolve }) => resolve(event); +export function initCloudflareSentryHandle(options: CloudflareOptions): Handle { + let sentryInitialized = false; + + return ({ event, resolve }) => { + if (!sentryInitialized) { + sentryInitialized = true; + init(options); + } + + return resolve(event); + }; } From 7ef333d6833998ce739aadb4f4a1a2c8b80175a1 Mon Sep 17 00:00:00 2001 From: Lukas Stracke Date: Fri, 31 Jan 2025 16:52:49 +0100 Subject: [PATCH 21/31] add renamed file --- packages/sveltekit/src/worker/cloudflare.ts | 43 +++++++++++++++++++++ 1 file changed, 43 insertions(+) create mode 100644 packages/sveltekit/src/worker/cloudflare.ts diff --git a/packages/sveltekit/src/worker/cloudflare.ts b/packages/sveltekit/src/worker/cloudflare.ts new file mode 100644 index 000000000000..0d26c566ea10 --- /dev/null +++ b/packages/sveltekit/src/worker/cloudflare.ts @@ -0,0 +1,43 @@ +import { type CloudflareOptions, wrapRequestHandler } from '@sentry/cloudflare'; +import { getDefaultIntegrations as getDefaultCloudflareIntegrations } from '@sentry/cloudflare'; +import type { Handle } from '@sveltejs/kit'; + +import { addNonEnumerableProperty } from '@sentry/core'; +import { rewriteFramesIntegration } from '../server-common/rewriteFramesIntegration'; + +/** + * Initializes Sentry SvelteKit Cloudflare SDK + * This should be before the sentryHandle() call. + * + * In the Node export, this is a stub that does nothing. + */ +export function initCloudflareSentryHandle(options: CloudflareOptions): Handle { + const opts: CloudflareOptions = { + defaultIntegrations: [...getDefaultCloudflareIntegrations(options), rewriteFramesIntegration()], + ...options, + }; + + const handleInitSentry: Handle = ({ event, resolve }) => { + // if event.platform exists (should be there in a cloudflare worker), then do the cloudflare sentry init + if (event.platform) { + // This is an optional local that the `sentryHandle` handler checks for to avoid double isolation + // In Cloudflare the `wrapRequestHandler` function already takes care of + // - request isolation + // - trace continuation + // -setting the request onto the scope + addNonEnumerableProperty(event.locals, '_sentrySkipRequestIsolation', true); + return wrapRequestHandler( + { + options: opts, + request: event.request, + // @ts-expect-error This will exist in Cloudflare + context: event.platform.context, + }, + () => resolve(event), + ); + } + return resolve(event); + }; + + return handleInitSentry; +} From 5c66a5699a7b9e53cb34ea79bd00249f910af730 Mon Sep 17 00:00:00 2001 From: Lukas Stracke Date: Fri, 31 Jan 2025 16:53:30 +0100 Subject: [PATCH 22/31] biome :(( --- .../sveltekit/src/server-common/handle.ts | 29 ++-- .../server-common/rewriteFramesIntegration.ts | 2 +- packages/sveltekit/src/server/handle.ts | 153 +----------------- packages/sveltekit/src/vite/sourceMaps.ts | 2 +- 4 files changed, 20 insertions(+), 166 deletions(-) diff --git a/packages/sveltekit/src/server-common/handle.ts b/packages/sveltekit/src/server-common/handle.ts index bde2ba701196..48167066c6d7 100644 --- a/packages/sveltekit/src/server-common/handle.ts +++ b/packages/sveltekit/src/server-common/handle.ts @@ -3,7 +3,6 @@ import { SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN, SEMANTIC_ATTRIBUTE_SENTRY_SOURCE, continueTrace, - getActiveSpan, getCurrentScope, getDefaultIsolationScope, getIsolationScope, @@ -130,7 +129,11 @@ async function instrumentHandle( }, async (span?: Span) => { getCurrentScope().setSDKProcessingMetadata({ - normalizedRequest: winterCGRequestToRequestData(event.request.clone()), + // We specifically avoid cloning the request here to avoid double read errors. + // We only read request headers so we're not consuming the body anyway. + // Note to future readers: This sounds counter-intuitive but please read + // https://github.com/getsentry/sentry-javascript/issues/14583 + normalizedRequest: winterCGRequestToRequestData(event.request), }); const res = await resolve(event, { transformPageChunk: addSentryCodeToPage({ injectFetchProxyScript: options.injectFetchProxyScript ?? true }), @@ -173,23 +176,17 @@ export function sentryHandle(handlerOptions?: SentryHandleOptions): Handle { }; const sentryRequestHandler: Handle = input => { - // event.isSubRequest was added in SvelteKit 1.21.0 and we can use it to check - // if we should create a new execution context or not. + // Escape hatch to suppress request isolation and trace continuation (see initCloudflareSentryHandle) + const skipIsolation = + '_sentrySkipRequestIsolation' in input.event.locals && input.event.locals._sentrySkipRequestIsolation; + // In case of a same-origin `fetch` call within a server`load` function, // SvelteKit will actually just re-enter the `handle` function and set `isSubRequest` // to `true` so that no additional network call is made. // We want the `http.server` span of that nested call to be a child span of the // currently active span instead of a new root span to correctly reflect this // behavior. - // As a fallback for Kit < 1.21.0, we check if there is an active span only if there's none, - // we create a new execution context. - const isSubRequest = typeof input.event.isSubRequest === 'boolean' ? input.event.isSubRequest : !!getActiveSpan(); - - // Escape hatch to suppress request isolation and trace continuation (see initCloudflareSentryHandle) - const skipIsolation = - '_sentrySkipRequestIsolation' in input.event.locals && input.event.locals._sentrySkipRequestIsolation; - - if (isSubRequest || skipIsolation) { + if (skipIsolation || input.event.isSubRequest) { return instrumentHandle(input, options); } @@ -197,7 +194,11 @@ export function sentryHandle(handlerOptions?: SentryHandleOptions): Handle { // We only call continueTrace in the initial top level request to avoid // creating a new root span for the sub request. isolationScope.setSDKProcessingMetadata({ - normalizedRequest: winterCGRequestToRequestData(input.event.request.clone()), + // We specifically avoid cloning the request here to avoid double read errors. + // We only read request headers so we're not consuming the body anyway. + // Note to future readers: This sounds counter-intuitive but please read + // https://github.com/getsentry/sentry-javascript/issues/14583 + normalizedRequest: winterCGRequestToRequestData(input.event.request), }); return continueTrace(getTracePropagationData(input.event), () => instrumentHandle(input, options)); }); diff --git a/packages/sveltekit/src/server-common/rewriteFramesIntegration.ts b/packages/sveltekit/src/server-common/rewriteFramesIntegration.ts index a0e8351ba84f..d5928f8974b0 100644 --- a/packages/sveltekit/src/server-common/rewriteFramesIntegration.ts +++ b/packages/sveltekit/src/server-common/rewriteFramesIntegration.ts @@ -7,8 +7,8 @@ import { join, rewriteFramesIntegration as originalRewriteFramesIntegration, } from '@sentry/core'; -import type { GlobalWithSentryValues } from '../vite/injectGlobalValues'; import { WRAPPED_MODULE_SUFFIX } from '../common/utils'; +import type { GlobalWithSentryValues } from '../vite/injectGlobalValues'; type StackFrameIteratee = (frame: StackFrame) => StackFrame; interface RewriteFramesOptions { diff --git a/packages/sveltekit/src/server/handle.ts b/packages/sveltekit/src/server/handle.ts index fee633f6f803..da429bc1040f 100644 --- a/packages/sveltekit/src/server/handle.ts +++ b/packages/sveltekit/src/server/handle.ts @@ -1,153 +1,6 @@ -import type { Span } from '@sentry/core'; -import { - SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN, - SEMANTIC_ATTRIBUTE_SENTRY_SOURCE, - continueTrace, - getCurrentScope, - getDefaultIsolationScope, - getIsolationScope, - getTraceMetaTags, - logger, - setHttpStatus, - startSpan, - winterCGRequestToRequestData, - withIsolationScope, -} from '@sentry/core'; -import type { Handle, ResolveOptions } from '@sveltejs/kit'; - -import type { SentryHandleOptions } from '../server-common/handle'; -import { sentryHandleGeneric } from '../server-common/handle'; - -/** - * A SvelteKit handle function that wraps the request for Sentry error and - * performance monitoring. - * - * Usage: - * ``` - * // src/hooks.server.ts - * import { sentryHandle } from '@sentry/sveltekit'; - * - * export const handle = sentryHandle(); - * - * // Optionally use the `sequence` function to add additional handlers. - * // export const handle = sequence(sentryHandle(), yourCustomHandler); - * ``` - */ -export function sentryHandle(handlerOptions?: SentryHandleOptions): Handle { - const { handleUnknownRoutes, ...rest } = handlerOptions ?? {}; - const options = { - handleUnknownRoutes: handleUnknownRoutes ?? false, - ...rest, - }; - - const sentryRequestHandler: Handle = input => { - // In case of a same-origin `fetch` call within a server`load` function, - // SvelteKit will actually just re-enter the `handle` function and set `isSubRequest` - // to `true` so that no additional network call is made. - // We want the `http.server` span of that nested call to be a child span of the - // currently active span instead of a new root span to correctly reflect this - // behavior. - if (input.event.isSubRequest) { - return instrumentHandle(input, options); - } - - return withIsolationScope(isolationScope => { - // We only call continueTrace in the initial top level request to avoid - // creating a new root span for the sub request. - isolationScope.setSDKProcessingMetadata({ - // We specifically avoid cloning the request here to avoid double read errors. - // We only read request headers so we're not consuming the body anyway. - // Note to future readers: This sounds counter-intuitive but please read - // https://github.com/getsentry/sentry-javascript/issues/14583 - normalizedRequest: winterCGRequestToRequestData(input.event.request), - }); - return continueTrace(getTracePropagationData(input.event), () => instrumentHandle(input, options)); - }); - }; - - return sentryRequestHandler; -} - -async function instrumentHandle( - { event, resolve }: Parameters[0], - options: SentryHandleOptions, -): Promise { - if (!event.route?.id && !options.handleUnknownRoutes) { - return resolve(event); - } - - // caching the result of the version check in `options.injectFetchProxyScript` - // to avoid doing the dynamic import on every request - if (options.injectFetchProxyScript == null) { - try { - // @ts-expect-error - the dynamic import is fine here - const { VERSION } = await import('@sveltejs/kit'); - options.injectFetchProxyScript = isFetchProxyRequired(VERSION); - } catch { - options.injectFetchProxyScript = true; - } - } - - const routeName = `${event.request.method} ${event.route?.id || event.url.pathname}`; - - if (getIsolationScope() !== getDefaultIsolationScope()) { - getIsolationScope().setTransactionName(routeName); - } else { - DEBUG_BUILD && logger.warn('Isolation scope is default isolation scope - skipping setting transactionName'); - } - - try { - const resolveResult = await startSpan( - { - op: 'http.server', - attributes: { - [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.http.sveltekit', - [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: event.route?.id ? 'route' : 'url', - 'http.method': event.request.method, - }, - name: routeName, - }, - async (span?: Span) => { - getCurrentScope().setSDKProcessingMetadata({ - // We specifically avoid cloning the request here to avoid double read errors. - // We only read request headers so we're not consuming the body anyway. - // Note to future readers: This sounds counter-intuitive but please read - // https://github.com/getsentry/sentry-javascript/issues/14583 - normalizedRequest: winterCGRequestToRequestData(event.request), - }); - const res = await resolve(event, { - transformPageChunk: addSentryCodeToPage({ injectFetchProxyScript: options.injectFetchProxyScript ?? true }), - }); - if (span) { - setHttpStatus(span, res.status); - } - return res; - }, - ); - return resolveResult; - } catch (e: unknown) { - sendErrorToSentry(e, 'handle'); - throw e; - } finally { - await flushIfServerless(); - } -} - -/** - * We only need to inject the fetch proxy script for SvelteKit versions < 2.16.0. - * Exported only for testing. - */ -export function isFetchProxyRequired(version: string): boolean { - try { - const [major, minor] = version.trim().replace(/-.*/, '').split('.').map(Number); - if (major != null && minor != null && (major > 2 || (major === 2 && minor >= 16))) { - return false; - } - } catch { - // ignore - } - return true; -} +import type { CloudflareOptions } from '@sentry/cloudflare'; +import type { Handle } from '@sveltejs/kit'; +import { init } from './sdk'; /** * Actual implementation in ../worker/handle.ts diff --git a/packages/sveltekit/src/vite/sourceMaps.ts b/packages/sveltekit/src/vite/sourceMaps.ts index 6e41d7f4950b..5d93849a1281 100644 --- a/packages/sveltekit/src/vite/sourceMaps.ts +++ b/packages/sveltekit/src/vite/sourceMaps.ts @@ -9,11 +9,11 @@ import { sentryVitePlugin } from '@sentry/vite-plugin'; import type { Plugin, UserConfig } from 'vite'; import MagicString from 'magic-string'; +import { WRAPPED_MODULE_SUFFIX } from '../common/utils'; import type { GlobalSentryValues } from './injectGlobalValues'; import { VIRTUAL_GLOBAL_VALUES_FILE, getGlobalValueInjectionCode } from './injectGlobalValues'; import { getAdapterOutputDir, getHooksFileName, loadSvelteConfig } from './svelteConfig'; import type { CustomSentryVitePluginOptions } from './types'; -import { WRAPPED_MODULE_SUFFIX } from '../common/utils'; // sorcery has no types, so these are some basic type definitions: type Chain = { From 62a1f90bf1ac9d43bda8f55cc3db0fe347441499 Mon Sep 17 00:00:00 2001 From: Lukas Stracke Date: Thu, 20 Feb 2025 17:57:05 +0100 Subject: [PATCH 23/31] test something --- .../test-applications/sveltekit-cloudflare-pages/vite.config.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/vite.config.ts b/dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/vite.config.ts index 706faf25f2b5..2d7e5c20039b 100644 --- a/dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/vite.config.ts +++ b/dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/vite.config.ts @@ -3,5 +3,5 @@ import { sveltekit } from '@sveltejs/kit/vite'; import { defineConfig } from 'vite'; export default defineConfig({ - plugins: [sentrySvelteKit({ autoUploadSourceMaps: false }), sveltekit()], + plugins: [/*sentrySvelteKit({ autoUploadSourceMaps: false })*/ , sveltekit()], }); From 9a82e141060ffcb82ce5f4bcae8f93a021a461c1 Mon Sep 17 00:00:00 2001 From: Lukas Stracke Date: Thu, 20 Feb 2025 18:02:52 +0100 Subject: [PATCH 24/31] of course biome complains --- .../test-applications/sveltekit-cloudflare-pages/vite.config.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/vite.config.ts b/dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/vite.config.ts index 2d7e5c20039b..c22966201c30 100644 --- a/dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/vite.config.ts +++ b/dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/vite.config.ts @@ -1,4 +1,3 @@ -import { sentrySvelteKit } from '@sentry/sveltekit'; import { sveltekit } from '@sveltejs/kit/vite'; import { defineConfig } from 'vite'; From 16548a6578d134758c3a7219a4e800d8ac12a13a Mon Sep 17 00:00:00 2001 From: Lukas Stracke Date: Fri, 21 Feb 2025 09:07:52 +0100 Subject: [PATCH 25/31] maybe fix tests? npm was used instead of pnpm in some test commands --- .../sveltekit-cloudflare-pages/package.json | 18 +++++++++--------- .../sveltekit-cloudflare-pages/vite.config.ts | 3 ++- 2 files changed, 11 insertions(+), 10 deletions(-) diff --git a/dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/package.json b/dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/package.json index 25806f07bfbf..a23f2224a4d9 100644 --- a/dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/package.json +++ b/dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/package.json @@ -10,22 +10,22 @@ "check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json", "check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch", "test:e2e": "playwright test", - "test": "npm run test:e2e", + "test": "pnpm run test:e2e", "test:build": "pnpm install && pnpm build", - "test:assert": "npm run test:e2e" + "test:assert": "pnpm run test:e2e" }, "dependencies": { "@sentry/sveltekit": "latest || *" }, "devDependencies": { "@playwright/test": "^1.45.3", - "@sveltejs/adapter-cloudflare": "^4.8.0", - "@sveltejs/kit": "^2.16.0", - "@sveltejs/vite-plugin-svelte": "^5.0.0", - "svelte": "^5.0.0", - "svelte-check": "^4.0.0", + "@sveltejs/adapter-cloudflare": "^5.0.3", + "@sveltejs/kit": "^2.17.2", + "@sveltejs/vite-plugin-svelte": "^5.0.3", + "svelte": "^5.20.2", + "svelte-check": "^4.1.4", "typescript": "^5.0.0", - "vite": "^6.0.0", - "wrangler": "^3" + "vite": "^6.1.1", + "wrangler": "^3.109.2" } } diff --git a/dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/vite.config.ts b/dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/vite.config.ts index c22966201c30..706faf25f2b5 100644 --- a/dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/vite.config.ts +++ b/dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/vite.config.ts @@ -1,6 +1,7 @@ +import { sentrySvelteKit } from '@sentry/sveltekit'; import { sveltekit } from '@sveltejs/kit/vite'; import { defineConfig } from 'vite'; export default defineConfig({ - plugins: [/*sentrySvelteKit({ autoUploadSourceMaps: false })*/ , sveltekit()], + plugins: [sentrySvelteKit({ autoUploadSourceMaps: false }), sveltekit()], }); From 2fd7bcc2e4c85412b6e0f06e93a3ca0bb75c61bf Mon Sep 17 00:00:00 2001 From: Lukas Stracke Date: Fri, 21 Feb 2025 09:29:46 +0100 Subject: [PATCH 26/31] replace more npm with pnpm --- .../sveltekit-cloudflare-pages/playwright.config.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/playwright.config.ts b/dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/playwright.config.ts index c4756a54a0e4..18bda456025e 100644 --- a/dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/playwright.config.ts +++ b/dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/playwright.config.ts @@ -2,7 +2,7 @@ import { defineConfig } from '@playwright/test'; export default defineConfig({ webServer: { - command: 'npm run build && npm run preview', + command: 'pnpm run build && pnpm run preview', port: 4173, }, From 67b94ef43ceceee6d767de4783066a5234ee774e Mon Sep 17 00:00:00 2001 From: Lukas Stracke Date: Fri, 21 Feb 2025 09:46:01 +0100 Subject: [PATCH 27/31] handle http prerender error --- .../sveltekit-cloudflare-pages/svelte.config.js | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/svelte.config.js b/dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/svelte.config.js index 3e5d9ebe25ff..19c950d2ad72 100644 --- a/dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/svelte.config.js +++ b/dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/svelte.config.js @@ -12,6 +12,11 @@ const config = { // If your environment is not supported, or you settled on a specific environment, switch out the adapter. // See https://svelte.dev/docs/kit/adapters for more information about adapters. adapter: adapter(), + prerender: { + handleHttpError: err => { + console.log(JSON.stringify(err, null, 2)); + }, + }, }, }; From 87a8484913d01cc1ec8731c3b0a35bf949448b9f Mon Sep 17 00:00:00 2001 From: Lukas Stracke Date: Fri, 21 Feb 2025 09:59:32 +0100 Subject: [PATCH 28/31] maybe a pnpm problem? --- .../sveltekit-cloudflare-pages/.npmrc | 1 - .../src/routes/prerender-test/+page.server.ts | 12 ++++++------ .../src/routes/prerender-test/+page.svelte | 4 ---- 3 files changed, 6 insertions(+), 11 deletions(-) diff --git a/dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/.npmrc b/dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/.npmrc index 0e94f06dacb6..070f80f05092 100644 --- a/dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/.npmrc +++ b/dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/.npmrc @@ -1,3 +1,2 @@ @sentry:registry=http://127.0.0.1:4873 @sentry-internal:registry=http://127.0.0.1:4873 -engine-strict=true diff --git a/dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/src/routes/prerender-test/+page.server.ts b/dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/src/routes/prerender-test/+page.server.ts index a3ede141fc59..457d375fe8ab 100644 --- a/dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/src/routes/prerender-test/+page.server.ts +++ b/dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/src/routes/prerender-test/+page.server.ts @@ -1,9 +1,9 @@ -import type { PageServerLoad } from './$types'; +// import type { PageServerLoad } from './$types'; export const prerender = true; -export const load: PageServerLoad = async function load() { - return { - message: 'From server load function.', - }; -}; +// export const load: PageServerLoad = async function load() { +// return { +// message: 'From server load function.', +// }; +// }; diff --git a/dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/src/routes/prerender-test/+page.svelte b/dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/src/routes/prerender-test/+page.svelte index 1aea39c3032b..ca6d9ce076a1 100644 --- a/dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/src/routes/prerender-test/+page.svelte +++ b/dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/src/routes/prerender-test/+page.svelte @@ -1,6 +1,2 @@ - -

{data.message}

Visit svelte.dev/docs/kit to read the documentation

From b9371895e1f508192c0c49f494af355cd50a5299 Mon Sep 17 00:00:00 2001 From: Lukas Stracke Date: Fri, 21 Feb 2025 10:27:48 +0100 Subject: [PATCH 29/31] ignore prerender error? --- .../sveltekit-cloudflare-pages/svelte.config.js | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/svelte.config.js b/dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/svelte.config.js index 19c950d2ad72..4e0314356cde 100644 --- a/dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/svelte.config.js +++ b/dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/svelte.config.js @@ -13,9 +13,7 @@ const config = { // See https://svelte.dev/docs/kit/adapters for more information about adapters. adapter: adapter(), prerender: { - handleHttpError: err => { - console.log(JSON.stringify(err, null, 2)); - }, + handleHttpError: 'ignore', }, }, }; From 38554d5530b25d2ce496554f95a44c776911acfc Mon Sep 17 00:00:00 2001 From: Lukas Stracke Date: Fri, 21 Feb 2025 10:37:47 +0100 Subject: [PATCH 30/31] does CI pass if I remove the prerendered page? --- .../src/routes/prerender-test/+page.server.ts | 9 --------- .../src/routes/prerender-test/+page.svelte | 2 -- .../sveltekit-cloudflare-pages/tests/demo.test.ts | 5 ----- 3 files changed, 16 deletions(-) delete mode 100644 dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/src/routes/prerender-test/+page.server.ts delete mode 100644 dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/src/routes/prerender-test/+page.svelte diff --git a/dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/src/routes/prerender-test/+page.server.ts b/dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/src/routes/prerender-test/+page.server.ts deleted file mode 100644 index 457d375fe8ab..000000000000 --- a/dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/src/routes/prerender-test/+page.server.ts +++ /dev/null @@ -1,9 +0,0 @@ -// import type { PageServerLoad } from './$types'; - -export const prerender = true; - -// export const load: PageServerLoad = async function load() { -// return { -// message: 'From server load function.', -// }; -// }; diff --git a/dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/src/routes/prerender-test/+page.svelte b/dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/src/routes/prerender-test/+page.svelte deleted file mode 100644 index ca6d9ce076a1..000000000000 --- a/dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/src/routes/prerender-test/+page.svelte +++ /dev/null @@ -1,2 +0,0 @@ - -

Visit svelte.dev/docs/kit to read the documentation

diff --git a/dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/tests/demo.test.ts b/dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/tests/demo.test.ts index c3c78acce514..a67e4e7f299d 100644 --- a/dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/tests/demo.test.ts +++ b/dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/tests/demo.test.ts @@ -4,8 +4,3 @@ test('home page has expected h1', async ({ page }) => { await page.goto('/'); await expect(page.locator('h1')).toBeVisible(); }); - -test('prerendered page has expected h1', async ({ page }) => { - await page.goto('/prerender-test'); - await expect(page.locator('h1')).toHaveText('From server load function.'); -}); From 0f6c1fedde419b6c266523af6a632c0f9b9ecc64 Mon Sep 17 00:00:00 2001 From: Lukas Stracke Date: Fri, 21 Feb 2025 11:02:58 +0100 Subject: [PATCH 31/31] pin wrangler --- .../test-applications/sveltekit-cloudflare-pages/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/package.json b/dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/package.json index a23f2224a4d9..51fe00136f06 100644 --- a/dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/package.json +++ b/dev-packages/e2e-tests/test-applications/sveltekit-cloudflare-pages/package.json @@ -26,6 +26,6 @@ "svelte-check": "^4.1.4", "typescript": "^5.0.0", "vite": "^6.1.1", - "wrangler": "^3.109.2" + "wrangler": "3.105.0" } }