next.js / packages /next /src /server /app-render /create-error-handler.tsx
AbdulElahGwaith's picture
Upload folder using huggingface_hub
b91e262 verified
import type { ErrorInfo } from 'react'
import stringHash from 'next/dist/compiled/string-hash'
import { formatServerError } from '../../lib/format-server-error'
import { SpanStatusCode, getTracer } from '../lib/trace/tracer'
import { isAbortError } from '../pipe-readable'
import { isBailoutToCSRError } from '../../shared/lib/lazy-dynamic/bailout-to-csr'
import { isDynamicServerError } from '../../client/components/hooks-server-context'
import { isNextRouterError } from '../../client/components/is-next-router-error'
import { isPrerenderInterruptedError } from './dynamic-rendering'
import { getProperError } from '../../lib/is-error'
import { createDigestWithErrorCode } from '../../lib/error-telemetry-utils'
import { isReactLargeShellError } from './react-large-shell-error'
declare global {
var __next_log_error__: undefined | ((err: unknown) => void)
}
type RSCErrorHandler = (err: unknown) => string | undefined
type SSRErrorHandler = (
err: unknown,
errorInfo?: ErrorInfo
) => string | undefined
export type DigestedError = Error & { digest: string; environmentName?: string }
/**
* Returns a digest for well-known Next.js errors, otherwise `undefined`. If a
* digest is returned this also means that the error does not need to be
* reported.
*/
export function getDigestForWellKnownError(error: unknown): string | undefined {
// If we're bailing out to CSR, we don't need to log the error.
if (isBailoutToCSRError(error)) return error.digest
// If this is a navigation error, we don't need to log the error.
if (isNextRouterError(error)) return error.digest
// If this error occurs, we know that we should be stopping the static
// render. This is only thrown in static generation when PPR is not enabled,
// which causes the whole page to be marked as dynamic. We don't need to
// tell the user about this error, as it's not actionable.
if (isDynamicServerError(error)) return error.digest
// If this is a prerender interrupted error, we don't need to log the error.
if (isPrerenderInterruptedError(error)) return error.digest
return undefined
}
export function createReactServerErrorHandler(
shouldFormatError: boolean,
isNextExport: boolean,
reactServerErrors: Map<string, DigestedError>,
onReactServerRenderError: (err: DigestedError, silenceLog: boolean) => void,
spanToRecordOn?: any
): RSCErrorHandler {
return (thrownValue: unknown) => {
if (typeof thrownValue === 'string') {
// TODO-APP: look at using webcrypto instead. Requires a promise to be awaited.
return stringHash(thrownValue).toString()
}
// If the response was closed, we don't need to log the error.
if (isAbortError(thrownValue)) return
const digest = getDigestForWellKnownError(thrownValue)
if (digest) {
return digest
}
if (isReactLargeShellError(thrownValue)) {
// TODO: Aggregate
console.error(thrownValue)
return undefined
}
let err = getProperError(thrownValue) as DigestedError
let silenceLog = false
// If the error already has a digest, respect the original digest,
// so it won't get re-generated into another new error.
if (err.digest) {
if (
process.env.NODE_ENV === 'production' &&
reactServerErrors.has(err.digest)
) {
// This error is likely an obfuscated error from another react-server
// environment (e.g. 'use cache'). We recover the original error here
// for reporting purposes.
err = reactServerErrors.get(err.digest)!
// We don't log it again though, as it was already logged in the
// original environment.
silenceLog = true
} else {
// Either we're in development (where we want to keep the transported
// error with environmentName), or the error is not in reactServerErrors
// but has a digest from other means. Keep the error as-is.
}
} else {
err.digest = createDigestWithErrorCode(
err,
// TODO-APP: look at using webcrypto instead. Requires a promise to be awaited.
stringHash(err.message + (err.stack || '')).toString()
)
}
// @TODO by putting this here and not at the top it is possible that
// we don't error the build in places we actually expect to
if (!reactServerErrors.has(err.digest)) {
reactServerErrors.set(err.digest, err)
}
// Format server errors in development to add more helpful error messages
if (shouldFormatError) {
formatServerError(err)
}
// Don't log the suppressed error during export
if (
!(
isNextExport &&
err?.message?.includes(
'The specific message is omitted in production builds to avoid leaking sensitive details.'
)
)
) {
// Record exception on the provided span if available, otherwise try active span.
const span = spanToRecordOn ?? getTracer().getActiveScopeSpan()
if (span) {
span.recordException(err)
span.setAttribute('error.type', err.name)
span.setStatus({
code: SpanStatusCode.ERROR,
message: err.message,
})
}
onReactServerRenderError(err, silenceLog)
}
return err.digest
}
}
export function createHTMLErrorHandler(
shouldFormatError: boolean,
isNextExport: boolean,
reactServerErrors: Map<string, DigestedError>,
allCapturedErrors: Array<unknown>,
onHTMLRenderSSRError: (err: DigestedError, errorInfo?: ErrorInfo) => void,
spanToRecordOn?: any
): SSRErrorHandler {
return (thrownValue: unknown, errorInfo?: ErrorInfo) => {
if (isReactLargeShellError(thrownValue)) {
// TODO: Aggregate
console.error(thrownValue)
return undefined
}
let isSSRError = true
allCapturedErrors.push(thrownValue)
// If the response was closed, we don't need to log the error.
if (isAbortError(thrownValue)) return
const digest = getDigestForWellKnownError(thrownValue)
if (digest) {
return digest
}
const err = getProperError(thrownValue) as DigestedError
// If the error already has a digest, respect the original digest,
// so it won't get re-generated into another new error.
if (err.digest) {
if (reactServerErrors.has(err.digest)) {
// This error is likely an obfuscated error from react-server.
// We recover the original error here.
thrownValue = reactServerErrors.get(err.digest)
isSSRError = false
} else {
// The error is not from react-server but has a digest
// from other means so we don't need to produce a new one
}
} else {
err.digest = createDigestWithErrorCode(
err,
stringHash(
err.message + (errorInfo?.componentStack || err.stack || '')
).toString()
)
}
// Format server errors in development to add more helpful error messages
if (shouldFormatError) {
formatServerError(err)
}
// Don't log the suppressed error during export
if (
!(
isNextExport &&
err?.message?.includes(
'The specific message is omitted in production builds to avoid leaking sensitive details.'
)
)
) {
// HTML errors contain RSC errors as well, filter them out before reporting
if (isSSRError) {
// Record exception on the provided span if available, otherwise try active span.
const span = spanToRecordOn ?? getTracer().getActiveScopeSpan()
if (span) {
span.recordException(err)
span.setAttribute('error.type', err.name)
span.setStatus({
code: SpanStatusCode.ERROR,
message: err.message,
})
}
onHTMLRenderSSRError(err, errorInfo)
}
}
return err.digest
}
}
export function isUserLandError(err: any): boolean {
return (
!isAbortError(err) && !isBailoutToCSRError(err) && !isNextRouterError(err)
)
}