|
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