Skip to content

Commit

Permalink
Sourcemap errors logged in Edge runtime (#73438)
Browse files Browse the repository at this point in the history
  • Loading branch information
eps1lon authored Dec 3, 2024
1 parent 87c2c52 commit e5336c2
Show file tree
Hide file tree
Showing 4 changed files with 81 additions and 29 deletions.
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
import { patchErrorInspect } from '../patch-error-inspect'
import { patchErrorInspectNodeJS } from '../patch-error-inspect'

patchErrorInspect()
patchErrorInspectNodeJS(globalThis.Error)
89 changes: 65 additions & 24 deletions packages/next/src/server/patch-error-inspect.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,9 +42,6 @@ type SourceMapCache = Map<
{ map: SyncSourceMapConsumer; payload: ModernSourceMapPayload }
>

// TODO: Implement for Edge runtime
const inspectSymbol = Symbol.for('nodejs.util.inspect.custom')

function frameToString(frame: StackFrame): string {
let sourceLocation = frame.lineNumber !== null ? `:${frame.lineNumber}` : ''
if (frame.column !== null && sourceLocation !== '') {
Expand Down Expand Up @@ -287,36 +284,46 @@ function parseAndSourceMap(error: Error): string {
)
}

export function patchErrorInspect() {
Error.prepareStackTrace = prepareUnsourcemappedStackTrace
function sourceMapError(this: void, error: Error): Error {
// Create a new Error object with the source mapping applied and then use native
// Node.js formatting on the result.
const newError =
error.cause !== undefined
? // Setting an undefined `cause` would print `[cause]: undefined`
new Error(error.message, { cause: error.cause })
: new Error(error.message)

// TODO: Ensure `class MyError extends Error {}` prints `MyError` as the name
newError.stack = parseAndSourceMap(error)

for (const key in error) {
if (!Object.prototype.hasOwnProperty.call(newError, key)) {
// @ts-expect-error -- We're copying all enumerable properties.
// So they definitely exist on `this` and obviously have no type on `newError` (yet)
newError[key] = error[key]
}
}

return newError
}

export function patchErrorInspectNodeJS(
errorConstructor: ErrorConstructor
): void {
const inspectSymbol = Symbol.for('nodejs.util.inspect.custom')

errorConstructor.prepareStackTrace = prepareUnsourcemappedStackTrace

// @ts-expect-error -- TODO upstream types
// eslint-disable-next-line no-extend-native -- We're not extending but overriding.
Error.prototype[inspectSymbol] = function (
errorConstructor.prototype[inspectSymbol] = function (
depth: number,
inspectOptions: util.InspectOptions,
inspect: typeof util.inspect
): string {
// avoid false-positive dynamic i/o warnings e.g. due to usage of `Math.random` in `source-map`.
return workUnitAsyncStorage.exit(() => {
// Create a new Error object with the source mapping applied and then use native
// Node.js formatting on the result.
const newError =
this.cause !== undefined
? // Setting an undefined `cause` would print `[cause]: undefined`
new Error(this.message, { cause: this.cause })
: new Error(this.message)

// TODO: Ensure `class MyError extends Error {}` prints `MyError` as the name
newError.stack = parseAndSourceMap(this)

for (const key in this) {
if (!Object.prototype.hasOwnProperty.call(newError, key)) {
// @ts-expect-error -- We're copying all enumerable properties.
// So they definitely exist on `this` and obviously have no type on `newError` (yet)
newError[key] = this[key]
}
}
const newError = sourceMapError(this)

const originalCustomInspect = (newError as any)[inspectSymbol]
// Prevent infinite recursion.
Expand All @@ -340,3 +347,37 @@ export function patchErrorInspect() {
})
}
}

export function patchErrorInspectEdgeLite(
errorConstructor: ErrorConstructor
): void {
const inspectSymbol = Symbol.for('edge-runtime.inspect.custom')

errorConstructor.prepareStackTrace = prepareUnsourcemappedStackTrace

// @ts-expect-error -- TODO upstream types
// eslint-disable-next-line no-extend-native -- We're not extending but overriding.
errorConstructor.prototype[inspectSymbol] = function ({
format,
}: {
format: (...args: unknown[]) => string
}): string {
// avoid false-positive dynamic i/o warnings e.g. due to usage of `Math.random` in `source-map`.
return workUnitAsyncStorage.exit(() => {
const newError = sourceMapError(this)

const originalCustomInspect = (newError as any)[inspectSymbol]
// Prevent infinite recursion.
Object.defineProperty(newError, inspectSymbol, {
value: undefined,
enumerable: false,
writable: true,
})
try {
return format(newError)
} finally {
;(newError as any)[inspectSymbol] = originalCustomInspect
}
})
}
}
3 changes: 3 additions & 0 deletions packages/next/src/server/web/sandbox/context.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import UtilImplementation from 'node:util'
import AsyncHooksImplementation from 'node:async_hooks'
import { intervalsManager, timeoutsManager } from './resource-managers'
import { createLocalRequestContext } from '../../after/builtin-request-context'
import { patchErrorInspectEdgeLite } from '../../patch-error-inspect'

interface ModuleContext {
runtime: EdgeRuntime
Expand Down Expand Up @@ -478,6 +479,8 @@ Learn More: https://nextjs.org/docs/messages/edge-dynamic-code-evaluation`),
decorateUnhandledRejection
)

patchErrorInspectEdgeLite(runtime.context.Error)

return {
runtime,
paths: new Map<string, string>(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,10 +30,18 @@ describe('app-dir - server source maps edge runtime', () => {
isTurbopack
? '\nError: Boom' +
// TODO(veil): Should be sourcemapped
'\n at logError (/'
'\n at logError (.next'
: '\nError: Boom' +
// TODO(veil): Should be sourcemapped
'\n at logError (webpack'
'\n at logError (app/rsc-error-log/page.js:2:16)' +
'\n at logError (app/rsc-error-log/page.js:6:2)' +
'\n 1 | function logError() {' +
"\n> 2 | console.error(new Error('Boom'))" +
'\n | ^' +
'\n 3 | }' +
'\n 4 |' +
'\n 5 | export default function Page() { {' +
'\n ' +
'\n}'
)
} else {
// TODO: Test `next build` with `--enable-source-maps`.
Expand Down

0 comments on commit e5336c2

Please sign in to comment.