|
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, Load, ServerLoad } 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,65 @@ function sendErrorToSentry(e: unknown): unknown {
|
45 | 42 | }
|
46 | 43 |
|
47 | 44 | /**
|
48 |
| - * Wrap load function with Sentry |
| 45 | + * Wrap a universal load function (e.g. +page.js or +layout.js) with Sentry functionality |
| 46 | + * |
| 47 | + * Usage: |
| 48 | + * ```js |
| 49 | + * import { } |
| 50 | + * ``` |
49 | 51 | *
|
50 | 52 | * @param origLoad SvelteKit user defined load function
|
51 | 53 | */
|
52 |
| -export function wrapLoadWithSentry<T extends ServerLoad | Load>(origLoad: T): T { |
| 54 | +export function wrapLoadWithSentry<T extends Load>(origLoad: T): T { |
53 | 55 | return new Proxy(origLoad, {
|
54 |
| - apply: (wrappingTarget, thisArg, args: Parameters<ServerLoad | Load>) => { |
| 56 | + apply: (wrappingTarget, thisArg, args: Parameters<T>) => { |
55 | 57 | const [event] = args;
|
56 | 58 | const routeId = event.route && event.route.id;
|
57 | 59 |
|
58 |
| - const { traceparentData, dynamicSamplingContext } = getTracePropagationData(event); |
59 |
| - |
60 | 60 | const traceLoadContext: TransactionContext = {
|
61 |
| - op: `function.sveltekit${isServerOnlyLoad(event) ? '.server' : ''}.load`, |
| 61 | + op: 'function.sveltekit.load', |
62 | 62 | name: routeId ? routeId : event.url.pathname,
|
63 | 63 | status: 'ok',
|
64 | 64 | metadata: {
|
65 | 65 | source: routeId ? 'route' : 'url',
|
66 |
| - dynamicSamplingContext: traceparentData && !dynamicSamplingContext ? {} : dynamicSamplingContext, |
67 | 66 | },
|
68 |
| - ...traceparentData, |
69 | 67 | };
|
70 | 68 |
|
71 | 69 | return trace(traceLoadContext, () => wrappingTarget.apply(thisArg, args), sendErrorToSentry);
|
72 | 70 | },
|
73 | 71 | });
|
74 | 72 | }
|
75 | 73 |
|
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 | 74 | /**
|
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 |
| 75 | + * Wrap a server-only load function (e.g. +page.server.js or +layout.server.js) with Sentry functionality |
| 76 | + * TODO: usage |
96 | 77 | *
|
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). |
| 78 | + * @param origServerLoad SvelteKit user defined server-only load function |
99 | 79 | */
|
100 |
| -function isServerOnlyLoad(event: ServerLoadEvent | LoadEvent): event is ServerLoadEvent { |
101 |
| - return 'request' in event; |
| 80 | +export function wrapServerLoadWithSentry<T extends ServerLoad>(origServerLoad: T): T { |
| 81 | + return new Proxy(origServerLoad, { |
| 82 | + apply: (wrappingTarget, thisArg, args: Parameters<T>) => { |
| 83 | + const [event] = args; |
| 84 | + const routeId = event.route && event.route.id; |
| 85 | + |
| 86 | + // Usually, the `handleWithSentry` hook handler should already create a transaction and store |
| 87 | + // traceparent and DSC on that transaction before the server-only load function is called. |
| 88 | + // However, since we have access to `event.request` we can still pass it to `trace` |
| 89 | + // in case our handler isn't called or for some reason the handle hook is bypassed. |
| 90 | + const { dynamicSamplingContext, traceparentData } = getTracePropagationData(event); |
| 91 | + |
| 92 | + const traceLoadContext: TransactionContext = { |
| 93 | + op: 'function.sveltekit.server.load', |
| 94 | + name: routeId ? routeId : event.url.pathname, |
| 95 | + status: 'ok', |
| 96 | + metadata: { |
| 97 | + source: routeId ? 'route' : 'url', |
| 98 | + dynamicSamplingContext: traceparentData && !dynamicSamplingContext ? {} : dynamicSamplingContext, |
| 99 | + }, |
| 100 | + ...traceparentData, |
| 101 | + }; |
| 102 | + |
| 103 | + return trace(traceLoadContext, () => wrappingTarget.apply(thisArg, args), sendErrorToSentry); |
| 104 | + }, |
| 105 | + }); |
102 | 106 | }
|
0 commit comments