| 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 } |
|
|
| |
| |
| |
| |
| |
| export function getDigestForWellKnownError(error: unknown): string | undefined { |
| |
| if (isBailoutToCSRError(error)) return error.digest |
|
|
| |
| if (isNextRouterError(error)) return error.digest |
|
|
| |
| |
| |
| |
| if (isDynamicServerError(error)) return error.digest |
|
|
| |
| 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') { |
| |
| return stringHash(thrownValue).toString() |
| } |
|
|
| |
| if (isAbortError(thrownValue)) return |
|
|
| const digest = getDigestForWellKnownError(thrownValue) |
|
|
| if (digest) { |
| return digest |
| } |
|
|
| if (isReactLargeShellError(thrownValue)) { |
| |
| console.error(thrownValue) |
| return undefined |
| } |
|
|
| let err = getProperError(thrownValue) as DigestedError |
| let silenceLog = false |
|
|
| |
| |
| if (err.digest) { |
| if ( |
| process.env.NODE_ENV === 'production' && |
| reactServerErrors.has(err.digest) |
| ) { |
| |
| |
| |
| err = reactServerErrors.get(err.digest)! |
| |
| |
| silenceLog = true |
| } else { |
| |
| |
| |
| } |
| } else { |
| err.digest = createDigestWithErrorCode( |
| err, |
| |
| stringHash(err.message + (err.stack || '')).toString() |
| ) |
| } |
|
|
| |
| |
| if (!reactServerErrors.has(err.digest)) { |
| reactServerErrors.set(err.digest, err) |
| } |
|
|
| |
| if (shouldFormatError) { |
| formatServerError(err) |
| } |
|
|
| |
| if ( |
| !( |
| isNextExport && |
| err?.message?.includes( |
| 'The specific message is omitted in production builds to avoid leaking sensitive details.' |
| ) |
| ) |
| ) { |
| |
| 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)) { |
| |
| console.error(thrownValue) |
| return undefined |
| } |
|
|
| let isSSRError = true |
|
|
| allCapturedErrors.push(thrownValue) |
|
|
| |
| if (isAbortError(thrownValue)) return |
|
|
| const digest = getDigestForWellKnownError(thrownValue) |
|
|
| if (digest) { |
| return digest |
| } |
|
|
| const err = getProperError(thrownValue) as DigestedError |
|
|
| |
| |
| if (err.digest) { |
| if (reactServerErrors.has(err.digest)) { |
| |
| |
| thrownValue = reactServerErrors.get(err.digest) |
| isSSRError = false |
| } else { |
| |
| |
| } |
| } else { |
| err.digest = createDigestWithErrorCode( |
| err, |
| stringHash( |
| err.message + (errorInfo?.componentStack || err.stack || '') |
| ).toString() |
| ) |
| } |
|
|
| |
| if (shouldFormatError) { |
| formatServerError(err) |
| } |
|
|
| |
| if ( |
| !( |
| isNextExport && |
| err?.message?.includes( |
| 'The specific message is omitted in production builds to avoid leaking sensitive details.' |
| ) |
| ) |
| ) { |
| |
| if (isSSRError) { |
| |
| 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) |
| ) |
| } |
|
|