File tree Expand file tree Collapse file tree 6 files changed +81
-2
lines changed Expand file tree Collapse file tree 6 files changed +81
-2
lines changed Original file line number Diff line number Diff line change @@ -3,11 +3,18 @@ import { captureException } from '@sentry/svelte';
3
3
import { addExceptionMechanism , objectify } from '@sentry/utils' ;
4
4
import type { LoadEvent } from '@sveltejs/kit' ;
5
5
6
+ import { isRedirect } from '../common/utils' ;
7
+
6
8
function sendErrorToSentry ( e : unknown ) : unknown {
7
9
// In case we have a primitive, wrap it in the equivalent wrapper class (string -> String, etc.) so that we can
8
10
// store a seen flag on it.
9
11
const objectifiedErr = objectify ( e ) ;
10
12
13
+ // We don't want to capture thrown `Redirect`s as these are not errors but expected behaviour
14
+ if ( isRedirect ( objectifiedErr ) ) {
15
+ return objectifiedErr ;
16
+ }
17
+
11
18
captureException ( objectifiedErr , scope => {
12
19
scope . addEventProcessor ( event => {
13
20
addExceptionMechanism ( event , {
Original file line number Diff line number Diff line change
1
+ import type { Redirect } from '@sveltejs/kit' ;
2
+
3
+ /**
4
+ * Determines if a thrown "error" is a Redirect object which SvelteKit users can throw to redirect to another route
5
+ * see: https://kit.svelte.dev/docs/modules#sveltejs-kit-redirect
6
+ * @param error the potential redirect error
7
+ */
8
+ export function isRedirect ( error : unknown ) : error is Redirect {
9
+ if ( error == null || typeof error !== 'object' ) {
10
+ return false ;
11
+ }
12
+ const hasValidLocation = 'location' in error && typeof error . location === 'string' ;
13
+ const hasValidStatus =
14
+ 'status' in error && typeof error . status === 'number' && error . status >= 300 && error . status <= 308 ;
15
+ return hasValidLocation && hasValidStatus ;
16
+ }
Original file line number Diff line number Diff line change @@ -5,6 +5,7 @@ import type { TransactionContext } from '@sentry/types';
5
5
import { addExceptionMechanism , objectify } from '@sentry/utils' ;
6
6
import type { HttpError , LoadEvent , ServerLoadEvent } from '@sveltejs/kit' ;
7
7
8
+ import { isRedirect } from '../common/utils' ;
8
9
import { getTracePropagationData } from './utils' ;
9
10
10
11
function isHttpError ( err : unknown ) : err is HttpError {
@@ -19,7 +20,12 @@ function sendErrorToSentry(e: unknown): unknown {
19
20
// The error() helper is commonly used to throw errors in load functions: https://kit.svelte.dev/docs/modules#sveltejs-kit-error
20
21
// If we detect a thrown error that is an instance of HttpError, we don't want to capture 4xx errors as they
21
22
// could be noisy.
22
- if ( isHttpError ( objectifiedErr ) && objectifiedErr . status < 500 && objectifiedErr . status >= 400 ) {
23
+ // Also the `redirect(...)` helper is used to redirect users from one page to another. We don't want to capture thrown
24
+ // `Redirect`s as they're not errors but expected behaviour
25
+ if (
26
+ isRedirect ( objectifiedErr ) ||
27
+ ( isHttpError ( objectifiedErr ) && objectifiedErr . status < 500 && objectifiedErr . status >= 400 )
28
+ ) {
23
29
return objectifiedErr ;
24
30
}
25
31
Original file line number Diff line number Diff line change 1
1
import { addTracingExtensions , Scope } from '@sentry/svelte' ;
2
2
import type { Load } from '@sveltejs/kit' ;
3
+ import { redirect } from '@sveltejs/kit' ;
3
4
import { vi } from 'vitest' ;
4
5
5
6
import { wrapLoadWithSentry } from '../../src/client/load' ;
@@ -99,6 +100,18 @@ describe('wrapLoadWithSentry', () => {
99
100
expect ( mockCaptureException ) . toHaveBeenCalledTimes ( 1 ) ;
100
101
} ) ;
101
102
103
+ it ( "doesn't call captureException for thrown `Redirect`s" , async ( ) => {
104
+ async function load ( _ : Parameters < Load > [ 0 ] ) : Promise < ReturnType < Load > > {
105
+ throw redirect ( 300 , 'other/route' ) ;
106
+ }
107
+
108
+ const wrappedLoad = wrapLoadWithSentry ( load ) ;
109
+ const res = wrappedLoad ( MOCK_LOAD_ARGS ) ;
110
+ await expect ( res ) . rejects . toThrow ( ) ;
111
+
112
+ expect ( mockCaptureException ) . not . toHaveBeenCalled ( ) ;
113
+ } ) ;
114
+
102
115
it ( 'calls trace function' , async ( ) => {
103
116
async function load ( { params } : Parameters < Load > [ 0 ] ) : Promise < ReturnType < Load > > {
104
117
return {
Original file line number Diff line number Diff line change
1
+ import { isRedirect } from '../../src/common/utils' ;
2
+
3
+ describe ( 'isRedirect' , ( ) => {
4
+ it . each ( [
5
+ { location : '/users/id' , status : 300 } ,
6
+ { location : '/users/id' , status : 304 } ,
7
+ { location : '/users/id' , status : 308 } ,
8
+ { location : '' , status : 308 } ,
9
+ ] ) ( 'returns `true` for valid Redirect objects' , redirectObject => {
10
+ expect ( isRedirect ( redirectObject ) ) . toBe ( true ) ;
11
+ } ) ;
12
+
13
+ it . each ( [
14
+ 300 ,
15
+ 'redirect' ,
16
+ { location : { route : { id : 'users/id' } } , status : 300 } ,
17
+ { status : 308 } ,
18
+ { location : '/users/id' } ,
19
+ { location : '/users/id' , status : 201 } ,
20
+ { location : '/users/id' , status : 400 } ,
21
+ { location : '/users/id' , status : 500 } ,
22
+ ] ) ( 'returns `false` for invalid Redirect objects' , redirectObject => {
23
+ expect ( isRedirect ( redirectObject ) ) . toBe ( false ) ;
24
+ } ) ;
25
+ } ) ;
Original file line number Diff line number Diff line change 1
1
import { addTracingExtensions } from '@sentry/core' ;
2
2
import { Scope } from '@sentry/node' ;
3
3
import type { Load , ServerLoad } from '@sveltejs/kit' ;
4
- import { error } from '@sveltejs/kit' ;
4
+ import { error , redirect } from '@sveltejs/kit' ;
5
5
import { vi } from 'vitest' ;
6
6
7
7
import { wrapLoadWithSentry , wrapServerLoadWithSentry } from '../../src/server/load' ;
@@ -166,6 +166,18 @@ describe.each([
166
166
} ) ;
167
167
} ) ;
168
168
169
+ it ( "doesn't call captureException for thrown `Redirect`s" , async ( ) => {
170
+ async function load ( _ : Parameters < Load > [ 0 ] ) : Promise < ReturnType < Load > > {
171
+ throw redirect ( 300 , 'other/route' ) ;
172
+ }
173
+
174
+ const wrappedLoad = wrapLoadWithSentry ( load ) ;
175
+ const res = wrappedLoad ( MOCK_LOAD_ARGS ) ;
176
+ await expect ( res ) . rejects . toThrow ( ) ;
177
+
178
+ expect ( mockCaptureException ) . not . toHaveBeenCalled ( ) ;
179
+ } ) ;
180
+
169
181
it ( 'adds an exception mechanism' , async ( ) => {
170
182
const addEventProcessorSpy = vi . spyOn ( mockScope , 'addEventProcessor' ) . mockImplementationOnce ( callback => {
171
183
void callback ( { } , { event_id : 'fake-event-id' } ) ;
You can’t perform that action at this time.
0 commit comments