File tree 10 files changed +169
-15
lines changed
dev-packages/e2e-tests/test-applications/nextjs-15
streaming-rsc-error/[param]
10 files changed +169
-15
lines changed Original file line number Diff line number Diff line change
1
+ import { Suspense } from 'react' ;
2
+
3
+ export const dynamic = 'force-dynamic' ;
4
+
5
+ export default async function Page ( ) {
6
+ return (
7
+ < Suspense fallback = { < p > Loading...</ p > } >
8
+ { /* @ts -ignore */ }
9
+ < Crash /> ;
10
+ </ Suspense >
11
+ ) ;
12
+ }
13
+
14
+ async function Crash ( ) {
15
+ throw new Error ( 'I am technically uncatchable' ) ;
16
+ return < p > unreachable</ p > ;
17
+ }
Original file line number Diff line number Diff line change
1
+ 'use client' ;
2
+
3
+ import { use } from 'react' ;
4
+
5
+ export function RenderPromise ( { stringPromise } : { stringPromise : Promise < string > } ) {
6
+ const s = use ( stringPromise ) ;
7
+ return < > { s } </ > ;
8
+ }
Original file line number Diff line number Diff line change
1
+ import { Suspense } from 'react' ;
2
+ import { RenderPromise } from './client-page' ;
3
+
4
+ export const dynamic = 'force-dynamic' ;
5
+
6
+ export default async function Page ( ) {
7
+ const crashingPromise = new Promise < string > ( ( _ , reject ) => {
8
+ setTimeout ( ( ) => {
9
+ reject ( new Error ( 'I am a data streaming error' ) ) ;
10
+ } , 100 ) ;
11
+ } ) ;
12
+
13
+ return (
14
+ < Suspense fallback = { < p > Loading...</ p > } >
15
+ < RenderPromise stringPromise = { crashingPromise } /> ;
16
+ </ Suspense >
17
+ ) ;
18
+ }
Original file line number Diff line number Diff line change
1
+ import * as Sentry from '@sentry/nextjs' ;
2
+
1
3
export async function register ( ) {
2
4
if ( process . env . NEXT_RUNTIME === 'nodejs' ) {
3
5
await import ( './sentry.server.config' ) ;
@@ -7,3 +9,5 @@ export async function register() {
7
9
await import ( './sentry.edge.config' ) ;
8
10
}
9
11
}
12
+
13
+ export const onRequestError = Sentry . experimental_captureRequestError ;
Original file line number Diff line number Diff line change 5
5
"scripts" : {
6
6
"build" : " next build > .tmp_build_stdout 2> .tmp_build_stderr || (cat .tmp_build_stdout && cat .tmp_build_stderr && exit 1)" ,
7
7
"clean" : " npx rimraf node_modules pnpm-lock.yaml" ,
8
- "test:prod" : " TEST_ENV=production playwright test" ,
9
- "test:dev" : " TEST_ENV=development playwright test" ,
8
+ "test:prod" : " TEST_ENV=production __NEXT_EXPERIMENTAL_INSTRUMENTATION=1 playwright test" ,
9
+ "test:dev" : " TEST_ENV=development __NEXT_EXPERIMENTAL_INSTRUMENTATION=1 playwright test" ,
10
10
"test:build" : " pnpm install && npx playwright install && pnpm build" ,
11
11
"test:build-canary" : " pnpm install && pnpm add next@rc && pnpm add react@beta && pnpm add react-dom@beta && npx playwright install && pnpm build" ,
12
12
"test:build-latest" : " pnpm install && pnpm add next@rc && pnpm add react@beta && pnpm add react-dom@beta && npx playwright install && pnpm build" ,
17
17
"@types/node" : " 18.11.17" ,
18
18
"@types/react" : " 18.0.26" ,
19
19
"@types/react-dom" : " 18.0.9" ,
20
- "next" : " 14.3 .0-canary.73 " ,
20
+ "next" : " 15.0 .0-canary.63 " ,
21
21
"react" : " beta" ,
22
22
"react-dom" : " beta" ,
23
23
"typescript" : " 4.9.5"
Original file line number Diff line number Diff line change
1
+ import { expect , test } from '@playwright/test' ;
2
+ import { waitForError , waitForTransaction } from '@sentry-internal/test-utils' ;
3
+
4
+ test ( 'Should capture errors from nested server components when `Sentry.captureRequestError` is added to the `onRequestError` hook' , async ( {
5
+ page,
6
+ } ) => {
7
+ const errorEventPromise = waitForError ( 'nextjs-15' , errorEvent => {
8
+ return ! ! errorEvent ?. exception ?. values ?. some ( value => value . value === 'I am technically uncatchable' ) ;
9
+ } ) ;
10
+
11
+ const serverTransactionPromise = waitForTransaction ( 'nextjs-15' , async transactionEvent => {
12
+ return transactionEvent ?. transaction === 'GET /nested-rsc-error/[param]' ;
13
+ } ) ;
14
+
15
+ await page . goto ( `/nested-rsc-error/123` ) ;
16
+ const errorEvent = await errorEventPromise ;
17
+ const serverTransactionEvent = await serverTransactionPromise ;
18
+
19
+ // error event is part of the transaction
20
+ expect ( errorEvent . contexts ?. trace ?. trace_id ) . toBe ( serverTransactionEvent . contexts ?. trace ?. trace_id ) ;
21
+
22
+ expect ( errorEvent . request ) . toMatchObject ( {
23
+ headers : expect . any ( Object ) ,
24
+ method : 'GET' ,
25
+ } ) ;
26
+
27
+ expect ( errorEvent . contexts ?. nextjs ) . toEqual ( {
28
+ route_type : 'render' ,
29
+ router_kind : 'App Router' ,
30
+ router_path : '/nested-rsc-error/[param]' ,
31
+ request_path : '/nested-rsc-error/123' ,
32
+ } ) ;
33
+ } ) ;
Original file line number Diff line number Diff line change
1
+ import { expect , test } from '@playwright/test' ;
2
+ import { waitForError , waitForTransaction } from '@sentry-internal/test-utils' ;
3
+
4
+ test ( 'Should capture errors for crashing streaming promises in server components when `Sentry.captureRequestError` is added to the `onRequestError` hook' , async ( {
5
+ page,
6
+ } ) => {
7
+ const errorEventPromise = waitForError ( 'nextjs-15' , errorEvent => {
8
+ return ! ! errorEvent ?. exception ?. values ?. some ( value => value . value === 'I am a data streaming error' ) ;
9
+ } ) ;
10
+
11
+ const serverTransactionPromise = waitForTransaction ( 'nextjs-15' , async transactionEvent => {
12
+ return transactionEvent ?. transaction === 'GET /streaming-rsc-error/[param]' ;
13
+ } ) ;
14
+
15
+ await page . goto ( `/streaming-rsc-error/123` ) ;
16
+ const errorEvent = await errorEventPromise ;
17
+ const serverTransactionEvent = await serverTransactionPromise ;
18
+
19
+ // error event is part of the transaction
20
+ expect ( errorEvent . contexts ?. trace ?. trace_id ) . toBe ( serverTransactionEvent . contexts ?. trace ?. trace_id ) ;
21
+
22
+ expect ( errorEvent . request ) . toMatchObject ( {
23
+ headers : expect . any ( Object ) ,
24
+ method : 'GET' ,
25
+ } ) ;
26
+
27
+ expect ( errorEvent . contexts ?. nextjs ) . toEqual ( {
28
+ route_type : 'render' ,
29
+ router_kind : 'App Router' ,
30
+ router_path : '/streaming-rsc-error/[param]' ,
31
+ request_path : '/streaming-rsc-error/123' ,
32
+ } ) ;
33
+ } ) ;
Original file line number Diff line number Diff line change
1
+ import { captureException , withScope } from '@sentry/core' ;
2
+
3
+ type RequestInfo = {
4
+ url : string ;
5
+ method : string ;
6
+ headers : Record < string , string | string [ ] | undefined > ;
7
+ } ;
8
+
9
+ type ErrorContext = {
10
+ routerKind : string ; // 'Pages Router' | 'App Router'
11
+ routePath : string ;
12
+ routeType : string ; // 'render' | 'route' | 'middleware'
13
+ } ;
14
+
15
+ /**
16
+ * Reports errors for the Next.js `onRequestError` instrumentation hook.
17
+ *
18
+ * Notice: This function is experimental and not intended for production use. Breaking changes may be done to this funtion in any release.
19
+ *
20
+ * @experimental
21
+ */
22
+ export function experimental_captureRequestError (
23
+ error : unknown ,
24
+ request : RequestInfo ,
25
+ errorContext : ErrorContext ,
26
+ ) : void {
27
+ withScope ( scope => {
28
+ scope . setSDKProcessingMetadata ( {
29
+ request : {
30
+ headers : request . headers ,
31
+ method : request . method ,
32
+ } ,
33
+ } ) ;
34
+
35
+ scope . setContext ( 'nextjs' , {
36
+ request_path : request . url ,
37
+ router_kind : errorContext . routerKind ,
38
+ router_path : errorContext . routePath ,
39
+ route_type : errorContext . routeType ,
40
+ } ) ;
41
+
42
+ scope . setTransactionName ( errorContext . routePath ) ;
43
+
44
+ captureException ( error , {
45
+ mechanism : {
46
+ handled : false ,
47
+ } ,
48
+ } ) ;
49
+ } ) ;
50
+ }
Original file line number Diff line number Diff line change 1
1
export { wrapGetStaticPropsWithSentry } from './wrapGetStaticPropsWithSentry' ;
2
-
3
2
export { wrapGetInitialPropsWithSentry } from './wrapGetInitialPropsWithSentry' ;
4
-
5
3
export { wrapAppGetInitialPropsWithSentry } from './wrapAppGetInitialPropsWithSentry' ;
6
-
7
4
export { wrapDocumentGetInitialPropsWithSentry } from './wrapDocumentGetInitialPropsWithSentry' ;
8
-
9
5
export { wrapErrorGetInitialPropsWithSentry } from './wrapErrorGetInitialPropsWithSentry' ;
10
-
11
6
export { wrapGetServerSidePropsWithSentry } from './wrapGetServerSidePropsWithSentry' ;
12
-
13
7
export { wrapServerComponentWithSentry } from './wrapServerComponentWithSentry' ;
14
-
15
8
export { wrapRouteHandlerWithSentry } from './wrapRouteHandlerWithSentry' ;
16
-
17
9
export { wrapApiHandlerWithSentryVercelCrons } from './wrapApiHandlerWithSentryVercelCrons' ;
18
-
19
10
export { wrapMiddlewareWithSentry } from './wrapMiddlewareWithSentry' ;
20
-
21
11
export { wrapPageComponentWithSentry } from './wrapPageComponentWithSentry' ;
22
-
23
12
export { wrapGenerationFunctionWithSentry } from './wrapGenerationFunctionWithSentry' ;
24
-
25
13
export { withServerActionInstrumentation } from './withServerActionInstrumentation' ;
14
+ export { experimental_captureRequestError } from './captureRequestError' ;
Original file line number Diff line number Diff line change @@ -140,3 +140,5 @@ export declare function wrapApiHandlerWithSentryVercelCrons<F extends (...args:
140
140
* Wraps a page component with Sentry error instrumentation.
141
141
*/
142
142
export declare function wrapPageComponentWithSentry < C > ( WrappingTarget : C ) : C ;
143
+
144
+ export { experimental_captureRequestError } from './common/captureRequestError' ;
You can’t perform that action at this time.
0 commit comments