Skip to content

Conversation

@timneutkens
Copy link
Member

@timneutkens timneutkens commented Jan 9, 2026

What?

Fixed console.trace to output a clean stack trace that shows the actual call site and parent frames, instead of polluting the output with internal Next.js console patching frames.

Why?

Before this fix, calling console.trace('Test') in a Next.js project would output:

Trace: Test
    at console.trace (/node-environment-extensions/console-file.js:21:44)
    at /node-environment-extensions/console-exit.js:22:95
    at AsyncLocalStorage.exit (node:async_hooks:354:14)
    at console.trace (/node-environment-extensions/console-exit.js:22:71)
    at console.trace (/node-environment-extensions/console-dim.external.js:208:47)
    at Object.<anonymous> (/path/to/next.config.js:1:9)  <-- actual call site buried

The internal wrapper frames from console-file, console-exit, and console-dim.external were polluting the stack trace, making it difficult to debug.

How?

In console-dim.external.tsx (the outermost wrapper):

  1. Added getCleanStackTrace() function that captures the stack trace early and filters out any frames containing /node-environment-extensions/
  2. Modified the console patching to capture the clean stack for console.trace before entering the wrapper chain
  3. Output the formatted trace via console.log (which still goes through the wrapper chain for proper file logging) instead of delegating to the original console.trace

After this fix:

Trace: Test
    at Object.<anonymous> (/path/to/next.config.js:1:9)
    at Module._compile (node:internal/modules/cjs/loader:1529:14)
    at Module._extensions..js (node:internal/modules/cjs/loader:1613:10)
    ...

Test Plan

Added unit test should output console.trace with a clean stack trace without internal wrapper frames to console-dim.external.test.ts that verifies:

  • Stack trace does NOT contain /node-environment-extensions/ frames
  • Output has the correct "Trace:" prefix
  • Output contains the provided message
  • Output contains actual stack frames

@nextjs-bot nextjs-bot added created-by: Turbopack team PRs by the Turbopack team. type: next labels Jan 9, 2026
Copy link
Member Author

This stack of pull requests is managed by Graphite. Learn more about stacking.

@nextjs-bot
Copy link
Collaborator

Failing test suites

Commit: 4e97364 | About building and testing Next.js

pnpm test-dev test/e2e/app-dir/interception-dynamic-segment/interception-dynamic-segment.test.ts (job)

  • interception-dynamic-segment > should work when interception route is paired with a dynamic segment (DD)
  • interception-dynamic-segment > should intercept consistently with back/forward navigation (DD)
  • interception-dynamic-segment > should intercept multiple times from root (DD)
Expand output

● interception-dynamic-segment › should work when interception route is paired with a dynamic segment

expect(received).toContain(expected) // indexOf

Expected substring: "intercepted"
Received string:    "MODAL SLOT:
default"

  70 |
  71 |     await retry(async () => {
> 72 |       expect(await browser.elementById('modal').text()).toContain('intercepted')
     |                                                         ^
  73 |     })
  74 |
  75 |     await browser.refresh()

  at toContain (e2e/app-dir/interception-dynamic-segment/interception-dynamic-segment.test.ts:72:57)
  at retry (lib/next-test-utils.ts:797:14)
  at Object.<anonymous> (e2e/app-dir/interception-dynamic-segment/interception-dynamic-segment.test.ts:71:5)

● interception-dynamic-segment › should intercept consistently with back/forward navigation

expect(received).toContain(expected) // indexOf

Expected substring: "intercepted"
Received string:    "MODAL SLOT:
catch-all"

   95 |
   96 |     await retry(async () => {
>  97 |       expect(await browser.elementById('modal').text()).toContain('intercepted')
      |                                                         ^
   98 |     })
   99 |
  100 |     // Go back to root

  at toContain (e2e/app-dir/interception-dynamic-segment/interception-dynamic-segment.test.ts:97:57)
  at retry (lib/next-test-utils.ts:797:14)
  at Object.<anonymous> (e2e/app-dir/interception-dynamic-segment/interception-dynamic-segment.test.ts:96:5)

● interception-dynamic-segment › should intercept multiple times from root

expect(received).toContain(expected) // indexOf

Expected substring: "intercepted"
Received string:    "MODAL SLOT:
catch-all"

  125 |
  126 |       await retry(async () => {
> 127 |         expect(await browser.elementById('modal').text()).toContain(
      |                                                           ^
  128 |           'intercepted'
  129 |         )
  130 |       })

  at toContain (e2e/app-dir/interception-dynamic-segment/interception-dynamic-segment.test.ts:127:59)
  at retry (lib/next-test-utils.ts:797:14)
  at Object.<anonymous> (e2e/app-dir/interception-dynamic-segment/interception-dynamic-segment.test.ts:126:7)

if (methodName === 'trace' && traceStack !== undefined) {
const label = args.length > 0 ? `Trace: ${args.join(' ')}` : 'Trace'
const traceOutput = `${label}\n${traceStack}`
// Use console.log with the dimmed trace output
Copy link
Contributor

@vercel vercel bot Jan 9, 2026

Choose a reason for hiding this comment

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

Double dimming of console.trace output when dim=true: trace output is converted to dimmed ANSI codes, then passed to the patched console.log which applies the dimming conversion again, resulting in malformed output

Fix on Vercel

// For console.trace, output the label + clean stack using console.log
// This goes through the wrapper chain for proper file logging
const label = args.length > 0 ? `Trace: ${args.join(' ')}` : 'Trace'
return console.log(`${label}\n${traceStack}`) as ReturnType<F>
Copy link
Contributor

@vercel vercel bot Jan 9, 2026

Choose a reason for hiding this comment

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

Console.trace output loses async storage context when calling console.log(), breaking proper file logging and dimming behavior

Fix on Vercel

@nextjs-bot
Copy link
Collaborator

nextjs-bot commented Jan 9, 2026

Stats from current PR

✅ No significant changes detected

📊 All Metrics
📖 Metrics Glossary

Dev Server Metrics:

  • Listen = TCP port starts accepting connections
  • First Request = HTTP server returns successful response
  • Cold = Fresh build (no cache)
  • Warm = With cached build artifacts

Build Metrics:

  • Fresh = Clean build (no .next directory)
  • Cached = With existing .next directory

Change Thresholds:

  • Time: Changes < 50ms AND < 10%, OR < 2% are insignificant
  • Size: Changes < 1KB AND < 1% are insignificant
  • All other changes are flagged to catch regressions

⚡ Dev Server

Metric Canary PR Change Trend
Cold (Listen) 456ms 455ms ▁▁▁▁▁
Cold (First Request) 896ms 890ms ▇▁█▁█
Warm (Listen) 456ms 456ms ▁▁▁▁▁
Warm (First Request) 356ms 362ms ▂▄▅▅▁
📦 Dev Server (Webpack) (Legacy)

📦 Dev Server (Webpack)

Metric Canary PR Change Trend
Cold (Listen) 507ms 507ms ▁▁▅▅▁
Cold (First Request) 2.171s 2.182s ▁▄▁█▅
Warm (Listen) 508ms 508ms ▅▅▅▅▅
Warm (First Request) 2.193s 2.197s ▁▅▁█▅

⚡ Production Builds

Metric Canary PR Change Trend
Fresh Build 4.424s 4.467s ▂▂▁▅▁
Cached Build 4.495s 4.443s ▁▂▁▃▁
📦 Production Builds (Webpack) (Legacy)

📦 Production Builds (Webpack)

Metric Canary PR Change Trend
Fresh Build 16.289s 16.316s ▁▂▁█▂
Cached Build 16.444s 16.406s ▂▂▁█▁
node_modules Size 457 MB 457 MB █████
📦 Bundle Sizes

Bundle Sizes

⚡ Turbopack

Client

Main Bundles: **430 kB** → **430 kB** ✅ -4 B

82 files with content-based hashes (individual files not comparable between builds)

Server

Middleware
Canary PR Change
middleware-b..fest.js gzip 793 B 796 B
Total 793 B 796 B ⚠️ +3 B
Build Details
Build Manifests
Canary PR Change
_buildManifest.js gzip 449 B 450 B
Total 449 B 450 B ⚠️ +1 B

📦 Webpack

Client

Main Bundles
Canary PR Change
2086.HASH.js gzip 169 B N/A -
2161-HASH.js gzip 5.41 kB N/A -
2747-HASH.js gzip 4.48 kB N/A -
4322-HASH.js gzip 52.7 kB N/A -
ec793fe8-HASH.js gzip 62.3 kB N/A -
framework-HASH.js gzip 59.8 kB 59.8 kB
main-app-HASH.js gzip 251 B 254 B 🔴 +3 B (+1%)
main-HASH.js gzip 38.6 kB 39 kB
webpack-HASH.js gzip 1.68 kB 1.68 kB
1596.HASH.js gzip N/A 169 B -
2658-HASH.js gzip N/A 52.5 kB -
6349-HASH.js gzip N/A 4.46 kB -
7019-HASH.js gzip N/A 5.43 kB -
b17a3386-HASH.js gzip N/A 62.3 kB -
Total 225 kB 225 kB ⚠️ +157 B
Polyfills
Canary PR Change
polyfills-HASH.js gzip 39.4 kB 39.4 kB
Total 39.4 kB 39.4 kB
Pages
Canary PR Change
_app-HASH.js gzip 194 B 193 B
_error-HASH.js gzip 182 B 182 B
css-HASH.js gzip 336 B 335 B
dynamic-HASH.js gzip 1.8 kB 1.8 kB
edge-ssr-HASH.js gzip 256 B 256 B
head-HASH.js gzip 352 B 349 B
hooks-HASH.js gzip 385 B 384 B
image-HASH.js gzip 580 B 580 B
index-HASH.js gzip 259 B 258 B
link-HASH.js gzip 2.5 kB 2.51 kB
routerDirect..HASH.js gzip 319 B 317 B
script-HASH.js gzip 385 B 387 B
withRouter-HASH.js gzip 316 B 315 B
1afbb74e6ecf..834.css gzip 106 B 106 B
Total 7.97 kB 7.96 kB ✅ -8 B

Server

Edge SSR
Canary PR Change
edge-ssr.js gzip 125 kB 125 kB
page.js gzip 239 kB 239 kB
Total 364 kB 364 kB ⚠️ +274 B
Middleware
Canary PR Change
middleware-b..fest.js gzip 654 B 655 B
middleware-r..fest.js gzip 155 B 156 B
middleware.js gzip 32.7 kB 33.1 kB 🔴 +403 B (+1%)
edge-runtime..pack.js gzip 842 B 842 B
Total 34.3 kB 34.7 kB ⚠️ +405 B
Build Details
Build Manifests
Canary PR Change
_buildManifest.js gzip 738 B 738 B
Total 738 B 738 B
Build Cache
Canary PR Change
0.pack gzip 3.61 MB 3.61 MB 🔴 +4.68 kB (+0%)
index.pack gzip 99 kB 100 kB 🔴 +1.2 kB (+1%)
index.pack.old gzip 98.9 kB 98.1 kB
Total 3.81 MB 3.81 MB ⚠️ +5.02 kB

🔄 Shared (bundler-independent)

Runtimes
Canary PR Change
app-page-exp...dev.js gzip 303 kB 303 kB
app-page-exp..prod.js gzip 157 kB 157 kB
app-page-tur...dev.js gzip 302 kB 302 kB
app-page-tur..prod.js gzip 157 kB 157 kB
app-page-tur...dev.js gzip 299 kB 299 kB
app-page-tur..prod.js gzip 155 kB 155 kB
app-page.run...dev.js gzip 299 kB 299 kB
app-page.run..prod.js gzip 155 kB 155 kB
app-route-ex...dev.js gzip 68.2 kB 68.2 kB
app-route-ex..prod.js gzip 46.9 kB 46.9 kB
app-route-tu...dev.js gzip 68.2 kB 68.2 kB
app-route-tu..prod.js gzip 46.9 kB 46.9 kB
app-route-tu...dev.js gzip 67.8 kB 67.8 kB
app-route-tu..prod.js gzip 46.7 kB 46.7 kB
app-route.ru...dev.js gzip 67.8 kB 67.8 kB
app-route.ru..prod.js gzip 46.7 kB 46.7 kB
dist_client_...dev.js gzip 324 B 324 B
dist_client_...dev.js gzip 326 B 326 B
dist_client_...dev.js gzip 318 B 318 B
dist_client_...dev.js gzip 317 B 317 B
pages-api-tu...dev.js gzip 41.1 kB 41.1 kB
pages-api-tu..prod.js gzip 31.2 kB 31.2 kB
pages-api.ru...dev.js gzip 41.1 kB 41.1 kB
pages-api.ru..prod.js gzip 31.2 kB 31.2 kB
pages-turbo....dev.js gzip 50.8 kB 50.8 kB
pages-turbo...prod.js gzip 38.2 kB 38.2 kB
pages.runtim...dev.js gzip 50.8 kB 50.8 kB
pages.runtim..prod.js gzip 38.2 kB 38.2 kB
server.runti..prod.js gzip 62 kB 62 kB
Total 2.67 MB 2.67 MB ✅ -2 B

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

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants