Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: log the error instance modified extra location info #71930

Merged
merged 5 commits into from
Oct 29, 2024
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,10 @@ const REACT_ERROR_STACK_BOTTOM_FRAME_REGEX = new RegExp(
`(at ${REACT_ERROR_STACK_BOTTOM_FRAME} )|(${REACT_ERROR_STACK_BOTTOM_FRAME}\\@)`
)

export function getReactStitchedError<T = unknown>(err: T): Error | T {
export function getReactStitchedError<T = unknown>(
err: T,
locationInfo?: string
): Error | T {
if (typeof (React as any).captureOwnerStack !== 'function') {
return err
}
Expand All @@ -26,15 +29,19 @@ export function getReactStitchedError<T = unknown>(err: T): Error | T {
const newError = new Error(originMessage)
// Copy all enumerable properties, e.g. digest
Object.assign(newError, err)
newError.stack = newStack

// Avoid duplicate overriding stack frames
const ownerStack = (React as any).captureOwnerStack()
if (ownerStack && newStack.endsWith(ownerStack) === false) {
newStack += ownerStack
// Override stack
newError.stack = newStack
}

if (locationInfo) {
newStack += `\n\n${locationInfo}`
huozhi marked this conversation as resolved.
Show resolved Hide resolved
}

// Override stack
newError.stack = newStack

return newError
}
43 changes: 20 additions & 23 deletions packages/next/src/client/react-client-callbacks/app-router.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import { handleClientError } from '../components/react-dev-overlay/internal/help
import { isNextRouterError } from '../components/is-next-router-error'
import { isBailoutToCSRError } from '../../shared/lib/lazy-dynamic/bailout-to-csr'
import { reportGlobalError } from './report-global-error'
import isError from '../../lib/is-error'
import { originConsoleError } from '../components/globals/intercept-console-error'

export const onCaughtError: HydrationOptions['onCaughtError'] = (
Expand All @@ -16,9 +15,7 @@ export const onCaughtError: HydrationOptions['onCaughtError'] = (
// Skip certain custom errors which are not expected to be reported on client
if (isBailoutToCSRError(err) || isNextRouterError(err)) return

const stitchedError = getReactStitchedError(err)

if (process.env.NODE_ENV === 'development') {
if (process.env.NODE_ENV !== 'production') {
const errorBoundaryComponent = errorInfo?.errorBoundary?.constructor
const errorBoundaryName =
// read react component displayName
Expand All @@ -36,11 +33,6 @@ export const onCaughtError: HydrationOptions['onCaughtError'] = (
componentThatErroredFrame?.match(/\s+at (\w+)\s+|(\w+)@/) ?? []
const componentThatErroredName = matches[1] || matches[2] || 'Unknown'

// In development mode, pass along the component stack to the error
if (process.env.NODE_ENV === 'development' && errorInfo.componentStack) {
;(stitchedError as any)._componentStack = errorInfo.componentStack
}

// Create error location with errored component and error boundary, to match the behavior of default React onCaughtError handler.
const errorBoundaryMessage = `It was handled by the <${errorBoundaryName}> error boundary.`
const componentErrorMessage = componentThatErroredName
Expand All @@ -49,10 +41,16 @@ export const onCaughtError: HydrationOptions['onCaughtError'] = (

const errorLocation = `${componentErrorMessage} ${errorBoundaryMessage}`

const originErrorStack = isError(err) ? err.stack || '' : ''
const stitchedError = getReactStitchedError(err, errorLocation)
// TODO: change to passing down errorInfo later
// In development mode, pass along the component stack to the error
if (errorInfo.componentStack) {
;(stitchedError as any)._componentStack = errorInfo.componentStack
}

// Log the error
originConsoleError(stitchedError)

// Log the modified error message with stack so without being intercepted again.
originConsoleError(originErrorStack + '\n\n' + errorLocation)
handleClientError(stitchedError, [])
} else {
originConsoleError(err)
Expand All @@ -66,29 +64,28 @@ export const onUncaughtError: HydrationOptions['onUncaughtError'] = (
// Skip certain custom errors which are not expected to be reported on client
if (isBailoutToCSRError(err) || isNextRouterError(err)) return

const stitchedError = getReactStitchedError(err)

if (process.env.NODE_ENV === 'development') {
if (process.env.NODE_ENV !== 'production') {
const componentThatErroredFrame = errorInfo?.componentStack?.split('\n')[1]

// Match chrome or safari stack trace
const matches =
componentThatErroredFrame?.match(/\s+at (\w+)\s+|(\w+)@/) ?? []
const componentThatErroredName = matches[1] || matches[2] || 'Unknown'

// In development mode, pass along the component stack to the error
if (process.env.NODE_ENV === 'development' && errorInfo.componentStack) {
;(stitchedError as any)._componentStack = errorInfo.componentStack
}

// Create error location with errored component and error boundary, to match the behavior of default React onCaughtError handler.
const errorLocation = componentThatErroredName
? `The above error occurred in the <${componentThatErroredName}> component.`
: `The above error occurred in one of your components.`

const errStack = (stitchedError as any).stack || ''
// Log the modified error message with stack so without being intercepted again.
originConsoleError(errStack + '\n\n' + errorLocation)
const stitchedError = getReactStitchedError(err, errorLocation)
// TODO: change to passing down errorInfo later
// In development mode, pass along the component stack to the error
if (errorInfo.componentStack) {
;(stitchedError as any)._componentStack = errorInfo.componentStack
}

// Log and report the error
originConsoleError(stitchedError)
reportGlobalError(stitchedError)
} else {
reportGlobalError(err)
Expand Down
62 changes: 18 additions & 44 deletions test/development/app-dir/owner-stack/owner-stack.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -78,17 +78,11 @@ describe('app-dir - owner-stack', () => {
at useThrowError
at useErrorHook
at Page
at react-stack-bottom-frame
at renderWithHooks
at updateFunctionComponent
at beginWork
at runWithFiberInDEV
at performUnitOfWork
at workLoopSync
at renderRootSync
at performWorkOnRoot
at performWorkOnRootViaSchedulerTask
at MessagePort.performWorkUntilDeadline
at NotFoundBoundary
at DevRootNotFoundBoundary
at Router
at AppRouter
at ServerRoot
The above error occurred in the <Page> component. It was handled by the <ReactDevOverlay> error boundary."
`)
} else {
Expand All @@ -97,17 +91,11 @@ describe('app-dir - owner-stack', () => {
at useThrowError
at useErrorHook
at Page
at react-stack-bottom-frame
at renderWithHooks
at updateFunctionComponent
at beginWork
at runWithFiberInDEV
at performUnitOfWork
at workLoopSync
at renderRootSync
at performWorkOnRoot
at performWorkOnRootViaSchedulerTask
at MessagePort.performWorkUntilDeadline
at NotFoundBoundary
at DevRootNotFoundBoundary
at Router
at AppRouter
at ServerRoot
The above error occurred in the <Page> component. It was handled by the <ReactDevOverlay> error boundary."
`)
}
Expand Down Expand Up @@ -148,17 +136,9 @@ describe('app-dir - owner-stack', () => {
at useThrowError
at useErrorHook
at Thrower
at react-stack-bottom-frame
at renderWithHooks
at updateFunctionComponent
at beginWork
at runWithFiberInDEV
at performUnitOfWork
at workLoopSync
at renderRootSync
at performWorkOnRoot
at performWorkOnRootViaSchedulerTask
at MessagePort.performWorkUntilDeadline
at Inner
at Page
at ClientPageRoot
The above error occurred in the <Thrower> component. It was handled by the <MyErrorBoundary> error boundary."
`)
})
Expand Down Expand Up @@ -191,17 +171,11 @@ describe('app-dir - owner-stack', () => {
at useThrowError
at useErrorHook
at Page
at react-stack-bottom-frame
at renderWithHooks
at updateFunctionComponent
at beginWork
at runWithFiberInDEV
at performUnitOfWork
at workLoopSync
at renderRootSync
at performWorkOnRoot
at performWorkOnRootViaSchedulerTask
at MessagePort.performWorkUntilDeadline
at NotFoundBoundary
at DevRootNotFoundBoundary
at Router
at AppRouter
at ServerRoot
The above error occurred in the <Page> component. It was handled by the <ReactDevOverlay> error boundary."
`)
})
Expand Down
Loading