File tree Expand file tree Collapse file tree 10 files changed +169
-15
lines changed
dev-packages/e2e-tests/test-applications/nextjs-15
streaming-rsc-error/[param] Expand file tree Collapse file tree 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+
13export async function register ( ) {
24 if ( process . env . NEXT_RUNTIME === 'nodejs' ) {
35 await import ( './sentry.server.config' ) ;
@@ -7,3 +9,5 @@ export async function register() {
79 await import ( './sentry.edge.config' ) ;
810 }
911}
12+
13+ export const onRequestError = Sentry . experimental_captureRequestError ;
Original file line number Diff line number Diff line change 55 "scripts" : {
66 "build" : " next build > .tmp_build_stdout 2> .tmp_build_stderr || (cat .tmp_build_stdout && cat .tmp_build_stderr && exit 1)" ,
77 "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" ,
1010 "test:build" : " pnpm install && npx playwright install && pnpm build" ,
1111 "test:build-canary" : " pnpm install && pnpm add next@rc && pnpm add react@beta && pnpm add react-dom@beta && npx playwright install && pnpm build" ,
1212 "test:build-latest" : " pnpm install && pnpm add next@rc && pnpm add react@beta && pnpm add react-dom@beta && npx playwright install && pnpm build" ,
1717 "@types/node" : " 18.11.17" ,
1818 "@types/react" : " 18.0.26" ,
1919 "@types/react-dom" : " 18.0.9" ,
20- "next" : " 14.3 .0-canary.73 " ,
20+ "next" : " 15.0 .0-canary.63 " ,
2121 "react" : " beta" ,
2222 "react-dom" : " beta" ,
2323 "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 11export { wrapGetStaticPropsWithSentry } from './wrapGetStaticPropsWithSentry' ;
2-
32export { wrapGetInitialPropsWithSentry } from './wrapGetInitialPropsWithSentry' ;
4-
53export { wrapAppGetInitialPropsWithSentry } from './wrapAppGetInitialPropsWithSentry' ;
6-
74export { wrapDocumentGetInitialPropsWithSentry } from './wrapDocumentGetInitialPropsWithSentry' ;
8-
95export { wrapErrorGetInitialPropsWithSentry } from './wrapErrorGetInitialPropsWithSentry' ;
10-
116export { wrapGetServerSidePropsWithSentry } from './wrapGetServerSidePropsWithSentry' ;
12-
137export { wrapServerComponentWithSentry } from './wrapServerComponentWithSentry' ;
14-
158export { wrapRouteHandlerWithSentry } from './wrapRouteHandlerWithSentry' ;
16-
179export { wrapApiHandlerWithSentryVercelCrons } from './wrapApiHandlerWithSentryVercelCrons' ;
18-
1910export { wrapMiddlewareWithSentry } from './wrapMiddlewareWithSentry' ;
20-
2111export { wrapPageComponentWithSentry } from './wrapPageComponentWithSentry' ;
22-
2312export { wrapGenerationFunctionWithSentry } from './wrapGenerationFunctionWithSentry' ;
24-
2513export { 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:
140140 * Wraps a page component with Sentry error instrumentation.
141141 */
142142export 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