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

capture console issues as console errors #72468

Merged
merged 2 commits into from
Nov 11, 2024
Merged
Show file tree
Hide file tree
Changes from all 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
@@ -1,6 +1,5 @@
import isError from '../../../lib/is-error'
import { isNextRouterError } from '../is-next-router-error'
import { captureStackTrace } from '../react-dev-overlay/internal/helpers/capture-stack-trace'
import { handleClientError } from '../react-dev-overlay/internal/helpers/use-error-handler'

export const originConsoleError = window.console.error
Expand All @@ -13,13 +12,12 @@ export function patchConsoleError() {
}
window.console.error = function error(...args: any[]) {
let maybeError: unknown
let isReplayedError = false

if (process.env.NODE_ENV !== 'production') {
const replayedError = matchReplayedError(...args)
if (replayedError) {
isReplayedError = true
maybeError = replayedError
} else if (isError(args[0])) {
maybeError = args[0]
Comment on lines +19 to +20
Copy link
Member

@eps1lon eps1lon Nov 11, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So this doesn't currently handle when errors are in different positions e.g. console.error('Failed to render', error)? We need to track this shortcoming in Linear.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yea it's a short solution for error capturing but having the correct trace. I marked the follow up work in NDX-459

} else {
// See https://github.com/facebook/react/blob/d50323eb845c5fde0d720cae888bf35dedd05506/packages/react-reconciler/src/ReactFiberErrorLogger.js#L78
maybeError = args[1]
Expand All @@ -30,16 +28,12 @@ export function patchConsoleError() {

if (!isNextRouterError(maybeError)) {
if (process.env.NODE_ENV !== 'production') {
// Create an origin stack that pointing to the origin location of the error
if (!isReplayedError && isError(maybeError)) {
captureStackTrace(maybeError)
}

handleClientError(
// replayed errors have their own complex format string that should be used,
// but if we pass the error directly, `handleClientError` will ignore it
maybeError,
args
args,
true
)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,10 @@ import {
} from '../helpers/hydration-error-info'
import { NodejsInspectorCopyButton } from '../components/nodejs-inspector'
import { CopyButton } from '../components/copy-button'
import { isUnhandledConsoleOrRejection } from '../helpers/console-error'
import {
getUnhandledErrorType,
isUnhandledConsoleOrRejection,
} from '../helpers/console-error'

export type SupportedErrorEvent = {
id: number
Expand Down Expand Up @@ -62,12 +65,19 @@ function ErrorDescription({
hydrationWarning: string | null
}) {
const isUnhandledOrReplayError = isUnhandledConsoleOrRejection(error)
const unhandledErrorType = isUnhandledOrReplayError
? getUnhandledErrorType(error)
: null
const isConsoleErrorStringMessage = unhandledErrorType === 'string'
// If the error is:
// - hydration warning
// - captured console error or unhandled rejection
// skip displaying the error name
const title =
isUnhandledOrReplayError || hydrationWarning ? '' : error.name + ': '
(isUnhandledOrReplayError && isConsoleErrorStringMessage) ||
hydrationWarning
? ''
: error.name + ': '

// If it's replayed error, display the environment name
const environmentName =
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,15 +1,29 @@
// To distinguish from React error.digest, we use a different symbol here to determine if the error is from console.error or unhandled promise rejection.
const digestSym = Symbol.for('next.console.error.digest')
const consoleTypeSym = Symbol.for('next.console.error.type')

// Represent non Error shape unhandled promise rejections or console.error errors.
// Those errors will be captured and displayed in Error Overlay.
type UnhandledError = Error & { digest: 'NEXT_UNHANDLED_ERROR' }
type UnhandledError = Error & {
[digestSym]: 'NEXT_UNHANDLED_ERROR'
[consoleTypeSym]: 'string' | 'error'
}

export function createUnhandledError(message: string): UnhandledError {
const error = new Error(message) as UnhandledError
error.digest = 'NEXT_UNHANDLED_ERROR'
export function createUnhandledError(message: string | Error): UnhandledError {
const error = (
typeof message === 'string' ? new Error(message) : message
) as UnhandledError
error[digestSym] = 'NEXT_UNHANDLED_ERROR'
error[consoleTypeSym] = typeof message === 'string' ? 'string' : 'error'
return error
}

export const isUnhandledConsoleOrRejection = (
error: any
): error is UnhandledError => {
return error && error.digest === 'NEXT_UNHANDLED_ERROR'
return error && error[digestSym] === 'NEXT_UNHANDLED_ERROR'
}

export const getUnhandledErrorType = (error: UnhandledError) => {
return error[consoleTypeSym]
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,16 +17,20 @@ const rejectionHandlers: Array<ErrorHandler> = []

export function handleClientError(
originError: unknown,
consoleErrorArgs: any[]
consoleErrorArgs: any[],
capturedFromConsole: boolean = false
) {
let error: Error
if (!originError || !isError(originError)) {
// If it's not an error, format the args into an error
const formattedErrorMessage = formatConsoleArgs(consoleErrorArgs)
error = getReactStitchedError(createUnhandledError(formattedErrorMessage))
error = createUnhandledError(formattedErrorMessage)
} else {
error = originError
error = capturedFromConsole
? createUnhandledError(originError)
: originError
}
error = getReactStitchedError(error)

storeHydrationErrorStateFromConsoleArgs(...consoleErrorArgs)
attachHydrationErrorState(error)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
'use client'

export default function Page() {
return (
<button
onClick={() => {
console.error('trigger an console <%s>', 'error')
}}
>
click to error
</button>
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
'use client'

export default function Page() {
for (let i = 0; i < 3; i++) {
console.error('trigger an console.error in loop of render')
}
return <p>render</p>
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
'use client'

export default function Page() {
console.error('trigger an console.error in render')
return <p>render</p>
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
export default function Root({ children }) {
return (
<html>
<body>{children}</body>
</html>
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export default function Page() {
console.error(new Error('boom'))
return <p>rsc</p>
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
'use client'

export default function Page() {
console.error(new Error('page error'))
return <p>ssr</p>
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
'use client'

export default function Page() {
console.error(
'ssr console error:' + (typeof window === 'undefined' ? 'server' : 'client')
)
return <p>ssr</p>
}
Loading
Loading