1
- import type { Span } from '@sentry/core' ;
2
- import {
3
- SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN ,
4
- SEMANTIC_ATTRIBUTE_SENTRY_SOURCE ,
5
- getActiveSpan ,
6
- getCurrentScope ,
7
- getDefaultIsolationScope ,
8
- getIsolationScope ,
9
- getTraceMetaTags ,
10
- logger ,
11
- setHttpStatus ,
12
- startSpan ,
13
- winterCGRequestToRequestData ,
14
- withIsolationScope ,
15
- } from '@sentry/core' ;
16
1
import { continueTrace } from '@sentry/node' ;
17
- import type { Handle , ResolveOptions } from '@sveltejs/kit' ;
2
+ import type { Handle } from '@sveltejs/kit' ;
18
3
19
- import { DEBUG_BUILD } from '../common/debug-build' ;
20
- import { flushIfServerless , getTracePropagationData , sendErrorToSentry } from './utils' ;
21
-
22
- export type SentryHandleOptions = {
23
- /**
24
- * Controls whether the SDK should capture errors and traces in requests that don't belong to a
25
- * route defined in your SvelteKit application.
26
- *
27
- * By default, this option is set to `false` to reduce noise (e.g. bots sending random requests to your server).
28
- *
29
- * Set this option to `true` if you want to monitor requests events without a route. This might be useful in certain
30
- * scenarios, for instance if you registered other handlers that handle these requests.
31
- * If you set this option, you might want adjust the the transaction name in the `beforeSendTransaction`
32
- * callback of your server-side `Sentry.init` options. You can also use `beforeSendTransaction` to filter out
33
- * transactions that you still don't want to be sent to Sentry.
34
- *
35
- * @default false
36
- */
37
- handleUnknownRoutes ?: boolean ;
38
-
39
- /**
40
- * Controls if `sentryHandle` should inject a script tag into the page that enables instrumentation
41
- * of `fetch` calls in `load` functions.
42
- *
43
- * @default true
44
- */
45
- injectFetchProxyScript ?: boolean ;
46
-
47
- /**
48
- * If this option is set, the `sentryHandle` handler will add a nonce attribute to the script
49
- * tag it injects into the page. This script is used to enable instrumentation of `fetch` calls
50
- * in `load` functions.
51
- *
52
- * Use this if your CSP policy blocks the fetch proxy script injected by `sentryHandle`.
53
- */
54
- fetchProxyScriptNonce ?: string ;
55
- } ;
56
-
57
- /**
58
- * Exported only for testing
59
- */
60
- export const FETCH_PROXY_SCRIPT = `
61
- const f = window.fetch;
62
- if(f){
63
- window._sentryFetchProxy = function(...a){return f(...a)}
64
- window.fetch = function(...a){return window._sentryFetchProxy(...a)}
65
- }
66
- ` ;
67
-
68
- /**
69
- * Adds Sentry tracing <meta> tags to the returned html page.
70
- * Adds Sentry fetch proxy script to the returned html page if enabled in options.
71
- * Also adds a nonce attribute to the script tag if users specified one for CSP.
72
- *
73
- * Exported only for testing
74
- */
75
- export function addSentryCodeToPage ( options : SentryHandleOptions ) : NonNullable < ResolveOptions [ 'transformPageChunk' ] > {
76
- const { fetchProxyScriptNonce, injectFetchProxyScript } = options ;
77
- // if injectFetchProxyScript is not set, we default to true
78
- const shouldInjectScript = injectFetchProxyScript !== false ;
79
- const nonce = fetchProxyScriptNonce ? `nonce="${ fetchProxyScriptNonce } "` : '' ;
80
-
81
- return ( { html } ) => {
82
- const metaTags = getTraceMetaTags ( ) ;
83
- const headWithMetaTags = metaTags ? `<head>\n${ metaTags } ` : '<head>' ;
84
-
85
- const headWithFetchScript = shouldInjectScript ? `\n<script ${ nonce } >${ FETCH_PROXY_SCRIPT } </script>` : '' ;
86
-
87
- const modifiedHead = `${ headWithMetaTags } ${ headWithFetchScript } ` ;
88
-
89
- return html . replace ( '<head>' , modifiedHead ) ;
90
- } ;
91
- }
4
+ import { sentryHandleGeneric , SentryHandleOptions } from '../server-common/handle' ;
92
5
93
6
/**
94
7
* A SvelteKit handle function that wraps the request for Sentry error and
@@ -106,87 +19,7 @@ export function addSentryCodeToPage(options: SentryHandleOptions): NonNullable<R
106
19
* ```
107
20
*/
108
21
export function sentryHandle ( handlerOptions ?: SentryHandleOptions ) : Handle {
109
- const options = {
110
- handleUnknownRoutes : false ,
111
- injectFetchProxyScript : true ,
112
- ...handlerOptions ,
113
- } ;
114
-
115
- const sentryRequestHandler : Handle = input => {
116
- // event.isSubRequest was added in SvelteKit 1.21.0 and we can use it to check
117
- // if we should create a new execution context or not.
118
- // In case of a same-origin `fetch` call within a server`load` function,
119
- // SvelteKit will actually just re-enter the `handle` function and set `isSubRequest`
120
- // to `true` so that no additional network call is made.
121
- // We want the `http.server` span of that nested call to be a child span of the
122
- // currently active span instead of a new root span to correctly reflect this
123
- // behavior.
124
- // As a fallback for Kit < 1.21.0, we check if there is an active span only if there's none,
125
- // we create a new execution context.
126
- const isSubRequest = typeof input . event . isSubRequest === 'boolean' ? input . event . isSubRequest : ! ! getActiveSpan ( ) ;
127
-
128
- if ( isSubRequest ) {
129
- return instrumentHandle ( input , options ) ;
130
- }
131
-
132
- return withIsolationScope ( isolationScope => {
133
- // We only call continueTrace in the initial top level request to avoid
134
- // creating a new root span for the sub request.
135
- isolationScope . setSDKProcessingMetadata ( {
136
- normalizedRequest : winterCGRequestToRequestData ( input . event . request . clone ( ) ) ,
137
- } ) ;
138
- return continueTrace ( getTracePropagationData ( input . event ) , ( ) => instrumentHandle ( input , options ) ) ;
139
- } ) ;
140
- } ;
22
+ const sentryRequestHandler = sentryHandleGeneric ( continueTrace , handlerOptions ) ;
141
23
142
24
return sentryRequestHandler ;
143
25
}
144
-
145
- async function instrumentHandle (
146
- { event, resolve } : Parameters < Handle > [ 0 ] ,
147
- options : SentryHandleOptions ,
148
- ) : Promise < Response > {
149
- if ( ! event . route ?. id && ! options . handleUnknownRoutes ) {
150
- return resolve ( event ) ;
151
- }
152
-
153
- const routeName = `${ event . request . method } ${ event . route ?. id || event . url . pathname } ` ;
154
-
155
- if ( getIsolationScope ( ) !== getDefaultIsolationScope ( ) ) {
156
- getIsolationScope ( ) . setTransactionName ( routeName ) ;
157
- } else {
158
- DEBUG_BUILD && logger . warn ( 'Isolation scope is default isolation scope - skipping setting transactionName' ) ;
159
- }
160
-
161
- try {
162
- const resolveResult = await startSpan (
163
- {
164
- op : 'http.server' ,
165
- attributes : {
166
- [ SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN ] : 'auto.http.sveltekit' ,
167
- [ SEMANTIC_ATTRIBUTE_SENTRY_SOURCE ] : event . route ?. id ? 'route' : 'url' ,
168
- 'http.method' : event . request . method ,
169
- } ,
170
- name : routeName ,
171
- } ,
172
- async ( span ?: Span ) => {
173
- getCurrentScope ( ) . setSDKProcessingMetadata ( {
174
- normalizedRequest : winterCGRequestToRequestData ( event . request . clone ( ) ) ,
175
- } ) ;
176
- const res = await resolve ( event , {
177
- transformPageChunk : addSentryCodeToPage ( options ) ,
178
- } ) ;
179
- if ( span ) {
180
- setHttpStatus ( span , res . status ) ;
181
- }
182
- return res ;
183
- } ,
184
- ) ;
185
- return resolveResult ;
186
- } catch ( e : unknown ) {
187
- sendErrorToSentry ( e , 'handle' ) ;
188
- throw e ;
189
- } finally {
190
- await flushIfServerless ( ) ;
191
- }
192
- }
0 commit comments