fix(dev-overlay): never let browser log forwarding break console.* by stop1love1 · Pull Request #98288 · vercel/next.js · GitHub
Skip to content

fix(dev-overlay): never let browser log forwarding break console.* - #98288

Open
stop1love1 wants to merge 1 commit into
vercel:canaryfrom
stop1love1:fix/forward-logs-console-resilience
Open

fix(dev-overlay): never let browser log forwarding break console.*#98288
stop1love1 wants to merge 1 commit into
vercel:canaryfrom
stop1love1:fix/forward-logs-console-resilience

Conversation

@stop1love1

Copy link
Copy Markdown

What?

Makes browser-to-terminal log forwarding fail-safe so it can never swallow or break a user's console.* call.

Fixes #84864

Why?

patchConsoleMethod runs the forwarding wrapper and then the original console method, with nothing in between:

const wrapperMethod = function (...args) {
  wrapper(methodName, ...args)
  originalMethod.apply(this, args)
}

If wrapper throws, two things go wrong at once: the exception propagates back into the application code that called console.log(...), and originalMethod is never reached — so the log is lost exactly when the user was trying to inspect something unusual. That is the behaviour reported in #84864 ("The original log should be done even if the stringify fails").

preLogSerializationClone is careful about most hostile values — it guards Object.keys, the then probe, array items and property getters — but two paths were still unprotected, and both are reachable from a plain console.log(value):

  • Object.getPrototypeOf(value) — a Proxy can trap getPrototypeOf and throw. The earlier Object.keys guard doesn't catch this, because ownKeys can succeed while getPrototypeOf throws.
  • Object.prototype.toString.call(value) — this reads Symbol.toStringTag, which is an ordinary getter the value controls, so it can throw too.

Since createLogEntry calls preLogSerializationClone outside any try, a throw from either line reaches the wrapper and takes console.* down with it.

Two of the cases originally reported are already handled on canary and I did not change them: the vendored safe-stable-stringify serializes BigInt fine (1n1), and logStringify already catches a throwing toJSON. What was left is the class of values that breaks the clone before stringification, plus the missing safety net around the wrapper itself.

How?

  • patchConsoleMethod: wrap the wrapper(...) call in try/catch so originalMethod.apply always runs and nothing is thrown back at the caller. The failure is swallowed rather than reported, because reporting it through console would recurse into this same wrapper — the same reasoning the existing scheduleLogSend flush already uses.
  • preLogSerializationClone: guard Object.getPrototypeOf and Object.prototype.toString.call, returning the existing [Unable to view] marker, consistent with how the function already degrades for throwing getters and unreadable proxies.

No behaviour changes for values that serialize normally.

Tests

Three cases added to the existing packages/next/src/next-devtools/userspace/app/forward-logs.test.ts:

  • a proxy whose getPrototypeOf throws → [Unable to view]
  • a value whose Symbol.toStringTag getter throws → [Unable to view]
  • patchConsoleMethod with a throwing wrapper → console.log does not throw, and the original method still receives the arguments

All three fail on canary (they throw getPrototypeOf throws / toStringTag throws / wrapper blew up) and pass with this change.

Verified locally on Windows:

  • jest packages/next/src/next-devtools/userspace/app/forward-logs.test.ts — 14/14 pass (11 existing + 3 new)
  • jest packages/next/src/next-devtools/ — 214/214 pass across 15 suites
  • pnpm prettier --check and pnpm lint-eslint on the changed files — clean

test/development/app-dir/browser-log-forwarding/ has 2 pre-existing failures on this machine (warn-level, verbose-level): their inline snapshots expect app/page.tsx but Windows produces app\page.tsx. I confirmed both fail identically on unmodified canary, so they are unrelated to this change and I have not touched them.

`patchConsoleMethod` called the forwarding wrapper before the original
console method, with nothing in between. If the wrapper threw, the user's
`console.log` both threw back into their code and never reached the real
console, so the log was lost precisely when something unusual was being
logged.

`preLogSerializationClone`, which the wrapper calls, still had two paths
that can throw on values the user controls: `Object.getPrototypeOf` (a
proxy can trap it) and `Object.prototype.toString`, which reads the
`Symbol.toStringTag` getter. Guard both so such values degrade to
"[Unable to view]" like the rest of the clone already does, and make the
patched method fail-safe so any future gap can't take `console.*` with it.

fixes vercel#84864
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[bug] Patched console methods stringifies arguments, causing unexpected errors with bigints and unserializable objects

1 participant