|
1 | 1 | /* eslint-disable @sentry-internal/sdk/no-optional-chaining */ |
2 | 2 | import { trace } from '@sentry/core'; |
3 | 3 | import { captureException } from '@sentry/node'; |
4 | | -import type { DynamicSamplingContext, TraceparentData, TransactionContext } from '@sentry/types'; |
5 | | -import { |
6 | | - addExceptionMechanism, |
7 | | - baggageHeaderToDynamicSamplingContext, |
8 | | - extractTraceparentData, |
9 | | - objectify, |
10 | | -} from '@sentry/utils'; |
11 | | -import type { HttpError, Load, LoadEvent, ServerLoad, ServerLoadEvent } from '@sveltejs/kit'; |
| 4 | +import type { TransactionContext } from '@sentry/types'; |
| 5 | +import { addExceptionMechanism, objectify } from '@sentry/utils'; |
| 6 | +import type { HttpError, LoadEvent, ServerLoadEvent } from '@sveltejs/kit'; |
| 7 | + |
| 8 | +import { getTracePropagationData } from './utils'; |
12 | 9 |
|
13 | 10 | function isHttpError(err: unknown): err is HttpError { |
14 | 11 | return typeof err === 'object' && err !== null && 'status' in err && 'body' in err; |
@@ -45,58 +42,77 @@ function sendErrorToSentry(e: unknown): unknown { |
45 | 42 | } |
46 | 43 |
|
47 | 44 | /** |
48 | | - * Wrap load function with Sentry |
49 | | - * |
50 | | - * @param origLoad SvelteKit user defined load function |
| 45 | + * @inheritdoc |
51 | 46 | */ |
52 | | -export function wrapLoadWithSentry<T extends ServerLoad | Load>(origLoad: T): T { |
| 47 | +// The liberal generic typing of `T` is necessary because we cannot let T extend `Load`. |
| 48 | +// This function needs to tell TS that it returns exactly the type that it was called with |
| 49 | +// because SvelteKit generates the narrowed down `PageLoad` or `LayoutLoad` types |
| 50 | +// at build time for every route. |
| 51 | +// eslint-disable-next-line @typescript-eslint/no-explicit-any |
| 52 | +export function wrapLoadWithSentry<T extends (...args: any) => any>(origLoad: T): T { |
53 | 53 | return new Proxy(origLoad, { |
54 | | - apply: (wrappingTarget, thisArg, args: Parameters<ServerLoad | Load>) => { |
55 | | - const [event] = args; |
| 54 | + apply: (wrappingTarget, thisArg, args: Parameters<T>) => { |
| 55 | + // Type casting here because `T` cannot extend `Load` (see comment above function signature) |
| 56 | + const event = args[0] as LoadEvent; |
56 | 57 | const routeId = event.route && event.route.id; |
57 | 58 |
|
58 | | - const { traceparentData, dynamicSamplingContext } = getTracePropagationData(event); |
59 | | - |
60 | 59 | const traceLoadContext: TransactionContext = { |
61 | | - op: `function.sveltekit${isServerOnlyLoad(event) ? '.server' : ''}.load`, |
| 60 | + op: 'function.sveltekit.load', |
62 | 61 | name: routeId ? routeId : event.url.pathname, |
63 | 62 | status: 'ok', |
64 | 63 | metadata: { |
65 | 64 | source: routeId ? 'route' : 'url', |
66 | | - dynamicSamplingContext: traceparentData && !dynamicSamplingContext ? {} : dynamicSamplingContext, |
67 | 65 | }, |
68 | | - ...traceparentData, |
69 | 66 | }; |
70 | 67 |
|
71 | 68 | return trace(traceLoadContext, () => wrappingTarget.apply(thisArg, args), sendErrorToSentry); |
72 | 69 | }, |
73 | 70 | }); |
74 | 71 | } |
75 | 72 |
|
76 | | -function getTracePropagationData(event: ServerLoadEvent | LoadEvent): { |
77 | | - traceparentData?: TraceparentData; |
78 | | - dynamicSamplingContext?: Partial<DynamicSamplingContext>; |
79 | | -} { |
80 | | - if (!isServerOnlyLoad(event)) { |
81 | | - return {}; |
82 | | - } |
83 | | - |
84 | | - const sentryTraceHeader = event.request.headers.get('sentry-trace'); |
85 | | - const baggageHeader = event.request.headers.get('baggage'); |
86 | | - const traceparentData = sentryTraceHeader ? extractTraceparentData(sentryTraceHeader) : undefined; |
87 | | - const dynamicSamplingContext = baggageHeaderToDynamicSamplingContext(baggageHeader); |
88 | | - |
89 | | - return { traceparentData, dynamicSamplingContext }; |
90 | | -} |
91 | | - |
92 | 73 | /** |
93 | | - * Our server-side wrapLoadWithSentry can be used to wrap two different kinds of `load` functions: |
94 | | - * - load functions from `+(page|layout).ts`: These can be called both on client and on server |
95 | | - * - load functions from `+(page|layout).server.ts`: These are only called on the server |
| 74 | + * Wrap a server-only load function (e.g. +page.server.js or +layout.server.js) with Sentry functionality |
| 75 | + * |
| 76 | + * Usage: |
| 77 | + * |
| 78 | + * ```js |
| 79 | + * // +page.serverjs |
96 | 80 | * |
97 | | - * In both cases, load events look differently. We can distinguish them by checking if the |
98 | | - * event has a `request` field (which only the server-exclusive load event has). |
| 81 | + * import { wrapServerLoadWithSentry } |
| 82 | + * |
| 83 | + * export const load = wrapServerLoadWithSentry((event) => { |
| 84 | + * // your load code |
| 85 | + * }); |
| 86 | + * ``` |
| 87 | + * |
| 88 | + * @param origServerLoad SvelteKit user defined server-only load function |
99 | 89 | */ |
100 | | -function isServerOnlyLoad(event: ServerLoadEvent | LoadEvent): event is ServerLoadEvent { |
101 | | - return 'request' in event; |
| 90 | +// The liberal generic typing of `T` is necessary because we cannot let T extend `ServerLoad`. |
| 91 | +// This function needs to tell TS that it returns exactly the type that it was called with |
| 92 | +// because SvelteKit generates the narrowed down `PageServerLoad` or `LayoutServerLoad` types |
| 93 | +// at build time for every route. |
| 94 | +// eslint-disable-next-line @typescript-eslint/no-explicit-any |
| 95 | +export function wrapServerLoadWithSentry<T extends (...args: any) => any>(origServerLoad: T): T { |
| 96 | + return new Proxy(origServerLoad, { |
| 97 | + apply: (wrappingTarget, thisArg, args: Parameters<T>) => { |
| 98 | + // Type casting here because `T` cannot extend `ServerLoad` (see comment above function signature) |
| 99 | + const event = args[0] as ServerLoadEvent; |
| 100 | + const routeId = event.route && event.route.id; |
| 101 | + |
| 102 | + const { dynamicSamplingContext, traceparentData } = getTracePropagationData(event); |
| 103 | + |
| 104 | + const traceLoadContext: TransactionContext = { |
| 105 | + op: 'function.sveltekit.server.load', |
| 106 | + name: routeId ? routeId : event.url.pathname, |
| 107 | + status: 'ok', |
| 108 | + metadata: { |
| 109 | + source: routeId ? 'route' : 'url', |
| 110 | + dynamicSamplingContext: traceparentData && !dynamicSamplingContext ? {} : dynamicSamplingContext, |
| 111 | + }, |
| 112 | + ...traceparentData, |
| 113 | + }; |
| 114 | + |
| 115 | + return trace(traceLoadContext, () => wrappingTarget.apply(thisArg, args), sendErrorToSentry); |
| 116 | + }, |
| 117 | + }); |
102 | 118 | } |
0 commit comments