Skip to content

fix: dedupe Next captureOutput logs and map dev stacks to source#381

Merged
HugoRCD merged 4 commits into
mainfrom
fix/next-capture-output-stack-enrichment
Jun 12, 2026
Merged

fix: dedupe Next captureOutput logs and map dev stacks to source#381
HugoRCD merged 4 commits into
mainfrom
fix/next-capture-output-stack-enrichment

Conversation

@HugoRCD

@HugoRCD HugoRCD commented Jun 11, 2026

Copy link
Copy Markdown
Owner

Summary by CodeRabbit

Release Notes

  • Bug Fixes

    • Fixed duplicate terminal output when captureOutput is enabled in Next.js.
  • New Features

    • Enhanced error stack traces in development to map back to original TypeScript source files and filter out framework internals.
    • Added silent option to control whether stdout is passed through during output capture.

@vercel

vercel Bot commented Jun 11, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
evlog-docs Ready Ready Preview, Comment, Open in v0 Jun 12, 2026 7:43am
just-use-evlog Ready Ready Preview, Comment Jun 12, 2026 7:43am

@github-actions

github-actions Bot commented Jun 11, 2026

Copy link
Copy Markdown
Contributor

Thank you for following the naming conventions! 🙏

@github-actions github-actions Bot added the bug Something isn't working label Jun 11, 2026
@coderabbitai

coderabbitai Bot commented Jun 11, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Caution

Review failed

The pull request is closed.

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: e6f8d248-41e1-4766-84a8-f7658d533d90

📥 Commits

Reviewing files that changed from the base of the PR and between db2aec7 and 6124f81.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (8)
  • .changeset/fix-capture-output-duplicates.md
  • packages/evlog/src/logger.ts
  • packages/evlog/src/next/instrumentation-create.ts
  • packages/evlog/src/shared/enrich-error-stack-next.node.ts
  • packages/evlog/src/shared/pretty-error.ts
  • packages/evlog/test/core/enrich-error-stack-next.test.ts
  • packages/evlog/test/core/pretty-error.test.ts
  • packages/evlog/test/next/instrumentation.test.ts

📝 Walkthrough

Walkthrough

This PR fixes duplicate terminal output in Next.js captureOutput mode by storing a native stdout reference at patch time, adds a silent flag to control passthrough behavior, source-maps error stacks back to TypeScript via Next internals, and compacts stored stacks to exclude framework paths.

Changes

Output handling and error stack improvements

Layer / File(s) Summary
Next.js dev stack enrichment with source mapping
packages/evlog/src/shared/enrich-error-stack-next.node.ts
New module that rewrites error stacks in Next.js dev by parsing frames, reading sibling .map files, and translating line/column positions back to original TypeScript sources via Next's SourceMapConsumer. Skips framework runtime and .next/ paths; falls back safely if internals are unavailable.
Stack compaction and frame filtering for storage
packages/evlog/src/shared/pretty-error.ts, packages/evlog/test/core/pretty-error.test.ts
Adds compactStackForStorage() export to keep header plus useful app frames while dropping framework/internal/node_modules frames. Introduces isFrameworkRuntimePath() and updates isAppPath() to decode file:// URLs and exclude runtime paths. Adjusts pickPrimaryFrame() to prefer topmost app frame and prevents snippet reading for .next/, .nuxt/, .output/ bundles. Tests validate frame preference and compaction.
Stdout/stderr capture with silent flag for passthrough control
packages/evlog/src/next/instrumentation-create.ts, packages/evlog/test/next/instrumentation.test.ts
Adds silent flag to InstrumentationOptions to control whether patched stdout/stderr skip the original write after logging. Saves bound process.stdout.write to globalThis.__evlogNativeStdoutWrite at patch time. Extends DEFAULT_CAPTURE_OUTPUT_IGNORE with Edge runtime warnings. Tests verify silent: false logs without passthrough and silent: true logs with passthrough.
Logger and handler integration
packages/evlog/src/logger.ts, packages/evlog/src/next/handler.ts, packages/evlog/src/next/enrich-error-stack.ts
Logger exports EvlogProcessOutputGlobal interface and writePrettyStdout() helper to use cached/overridden native stdout for pretty output. Exports compactStackForStorage() for dev-mode stack compaction in emitted events. Handler imports enrichNextErrorStackForDev(), normalizes caught errors, awaits enrichment, and logs. New enrichNextErrorStackForDev() wrapper detects dev runtime and conditionally delegates to Next-specific source-mapping.
Next.js stack enrichment tests
packages/evlog/test/core/enrich-error-stack-next.test.ts
Conditional Vitest suite validates dev-mode frame remapping via sibling .map files, missing-map passthrough, and production no-op behavior. Manages NODE_ENV and temporary directory per test.
Documentation and changeset
.changeset/fix-capture-output-duplicates.md, apps/docs/content/3.integrate/frameworks/02.nextjs.md
Changeset and Next.js docs document the fix, explaining silent flag passthrough behavior, default Edge warning filtering, and source-mapped stack traces.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

  • HugoRCD/evlog#380: Both PRs modify Next.js captureOutput plumbing in instrumentation-create.ts, especially stdout/stderr patching and Edge warning ignore patterns.
  • HugoRCD/evlog#370: Both PRs update logger.ts pretty terminal output flushing logic (flushPrettyLines), directly connecting captureOutput stdout handling with dev pretty output behavior.
🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Description check ⚠️ Warning The pull request description is completely empty with no content provided by the author, violating the required template structure. Add a comprehensive description including linked issue (if applicable), detailed explanation of changes and their rationale, and confirm documentation updates were completed.
Docstring Coverage ⚠️ Warning Docstring coverage is 33.33% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the two main changes: deduplicating Next.js captureOutput logs and mapping development stacks to their original sources.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/next-capture-output-stack-enrichment

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 ESLint

If the error stems from missing dependencies, add them to the package.json file. For unrecoverable errors (e.g., due to private dependencies), disable the tool in the CodeRabbit configuration.

ESLint install timed out. The project may have too many dependencies for the sandbox.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@pkg-pr-new

pkg-pr-new Bot commented Jun 11, 2026

Copy link
Copy Markdown
npm i https://pkg.pr.new/evlog@381
npm i https://pkg.pr.new/@evlog/nuxthub@381

commit: db2aec7

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 6

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In @.changeset/fix-capture-output-duplicates.md:
- Line 5: Update the changelog wording to state that "node: frames are filtered
during stack enrichment, not during snippet preview rendering" and clarify that
the "preview skip logic only excludes paths matching .next, .nuxt, and .output";
reference the existing phrases "stack enrichment", "snippet preview rendering",
and "preview skip logic" when making the change so the sentence replaces the
current ambiguous wording accordingly.

In `@packages/evlog/src/next/enrich-error-stack.ts`:
- Around line 1-13: Add a JSDoc comment for the exported function
enrichNextErrorStackForDev describing its purpose, parameters, and return type;
place it immediately above the function declaration and document that it
enriches stack traces for Next.js dev only (no-op in production), annotate the
options param (pretty?: boolean) and that the function returns a Promise<void>,
and mention any side effects (dynamic import of
../shared/enrich-error-stack-next.node and calling enrichErrorStackFromNextDev).

In `@packages/evlog/src/shared/enrich-error-stack-next.node.ts`:
- Around line 55-61: There are duplicate isFrameworkRuntimePath implementations;
extract the logic into a single shared utility module (e.g.,
shared/framework-paths.ts) that exports isFrameworkRuntimePath and reuse it from
both enrich-error-stack-next.node.ts and pretty-error.ts by replacing their
local implementations with an import; ensure the exported function preserves the
same normalization and checks for 'route-modules/', 'webpack://next/',
'/next/dist/', and '/compiled/next-server/' so both files call the shared
isFrameworkRuntimePath symbol instead of having duplicated code.
- Around line 63-69: In shouldSkipMappedSource remove the redundant node_modules
check: the current normalized.includes('node_modules') already covers
normalized.includes('/node_modules/'), so delete the latter condition and keep
the checks for '/packages/evlog/' and isFrameworkRuntimePath(normalized) to
preserve intent; update the return expression in shouldSkipMappedSource
accordingly.

In `@packages/evlog/src/shared/pretty-error.ts`:
- Around line 255-266: The isFrameworkRuntimePath function is duplicated;
extract it into a shared utility (e.g., create a helper module exporting
isFrameworkRuntimePath) and update both isFrameworkRuntimePath and
isFrameworkRuntimeFrame in this file to import and call the shared function
instead of keeping the local copy; remove the local duplicate and ensure
enrich-error-stack-next.node.ts also imports the same helper to avoid divergence
while keeping the isFrameworkRuntimeFrame (which calls decodeFileUrl and the
shared isFrameworkRuntimePath) behavior unchanged.

In `@packages/evlog/test/core/enrich-error-stack-next.test.ts`:
- Around line 28-30: The test currently silently returns when the source map
fixture is missing (the if (!existsSync(`${chunk}.map`)) { return } branch),
which masks failures; update this to fail loudly by replacing the early return
with an explicit test failure (e.g., throw new Error or use an assertion like
expect(false).toBeTruthy()) that includes the missing fixture name
(`${chunk}.map`) so CI fails and developers see which artifact is absent; keep
the existence check using existsSync and the same `chunk` variable.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: e323bcbf-19ba-4bf5-8305-1cba504fc6dc

📥 Commits

Reviewing files that changed from the base of the PR and between af238a2 and db2aec7.

📒 Files selected for processing (12)
  • .changeset/fix-capture-output-duplicates.md
  • apps/docs/content/3.integrate/frameworks/02.nextjs.md
  • packages/evlog/src/logger.ts
  • packages/evlog/src/next/enrich-error-stack.ts
  • packages/evlog/src/next/handler.ts
  • packages/evlog/src/next/instrumentation-create.ts
  • packages/evlog/src/shared/enrich-error-stack-next.node.ts
  • packages/evlog/src/shared/enrich-error-stack.node.ts
  • packages/evlog/src/shared/pretty-error.ts
  • packages/evlog/test/core/enrich-error-stack-next.test.ts
  • packages/evlog/test/core/pretty-error.test.ts
  • packages/evlog/test/next/instrumentation.test.ts

"evlog": patch
---

Fix duplicate terminal output when Next.js `captureOutput` is enabled: pretty-print writes use the native stdout handle registered at patch time, passthrough is skipped unless `silent: true`, and evlog-formatted terminal lines are ignored by capture. Next.js dev stacks are source-mapped to original TypeScript (like Nitro) via a Next-only enricher that does not bundle nitropack/youch; stored stacks are compacted and useless `.next`/`node:` snippet previews are skipped. The primary `at` line now points at your route/handler file instead of Next `route-modules` internals.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Tighten the stack/snippet wording.

node: frames are filtered during stack enrichment, not snippet preview rendering; the preview skip logic only excludes .next, .nuxt, and .output paths.

🧰 Tools
🪛 markdownlint-cli2 (0.22.1)

[warning] 5-5: First line in a file should be a top-level heading

(MD041, first-line-heading, first-line-h1)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.changeset/fix-capture-output-duplicates.md at line 5, Update the changelog
wording to state that "node: frames are filtered during stack enrichment, not
during snippet preview rendering" and clarify that the "preview skip logic only
excludes paths matching .next, .nuxt, and .output"; reference the existing
phrases "stack enrichment", "snippet preview rendering", and "preview skip
logic" when making the change so the sentence replaces the current ambiguous
wording accordingly.

Comment on lines +1 to +13
/**
* Source-map stack enrichment for Next.js dev — isolated from Nitro to avoid bundling nitropack/youch.
*/
export async function enrichNextErrorStackForDev(
error: Error,
options: { pretty?: boolean } = {},
): Promise<void> {
if (process.env.NODE_ENV === 'production') return
if (options.pretty === false) return

const { enrichErrorStackFromNextDev } = await import('../shared/enrich-error-stack-next.node')
enrichErrorStackFromNextDev(error)
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Missing JSDoc on public API.

Per coding guidelines, public APIs in packages/evlog/src/**/*.{ts,tsx} require JSDoc comments. The exported enrichNextErrorStackForDev function lacks documentation.

📝 Suggested JSDoc
-/**
- * Source-map stack enrichment for Next.js dev — isolated from Nitro to avoid bundling nitropack/youch.
- */
+/**
+ * Rewrite `error.stack` with source-mapped frames in Next.js dev.
+ * Isolated from Nitro to avoid bundling nitropack/youch.
+ *
+ * `@param` error - The error whose stack should be enriched.
+ * `@param` options - Configuration options.
+ * `@param` options.pretty - When false, skip enrichment. Defaults to true in dev.
+ */
 export async function enrichNextErrorStackForDev(
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/evlog/src/next/enrich-error-stack.ts` around lines 1 - 13, Add a
JSDoc comment for the exported function enrichNextErrorStackForDev describing
its purpose, parameters, and return type; place it immediately above the
function declaration and document that it enriches stack traces for Next.js dev
only (no-op in production), annotate the options param (pretty?: boolean) and
that the function returns a Promise<void>, and mention any side effects (dynamic
import of ../shared/enrich-error-stack-next.node and calling
enrichErrorStackFromNextDev).

Source: Coding guidelines

Comment thread packages/evlog/src/shared/enrich-error-stack-next.node.ts Outdated
Comment thread packages/evlog/src/shared/enrich-error-stack-next.node.ts
Comment thread packages/evlog/src/shared/pretty-error.ts Outdated
Comment thread packages/evlog/test/core/enrich-error-stack-next.test.ts Outdated
claude and others added 3 commits June 11, 2026 22:02
- Revert shared/enrich-error-stack.node.ts to main: the Next branch and
  createRequire refactor were dead code (file is only imported by the
  Nitro plugins, where NEXT_RUNTIME is never set)
- Only compact stored stacks in dev: compactStackForStorage filtered out
  .output/.next/node_modules frames, which would have reduced production
  stacks to the message line in drains; it now also returns the stack
  unchanged when no app frame survives
- Drop the evlog-format regex from DEFAULT_CAPTURE_OUTPUT_IGNORE: the
  native stdout bypass already prevents recapture, and the regex silently
  swallowed app lines starting with INFO [ etc.
- Deduplicate isFrameworkRuntimePath and EvlogProcessOutputGlobal, inline
  computeErrorName
- Replace the enricher test that depended on a local playground build
  artifact with a synthetic chunk + sourcemap fixture that runs in CI

https://claude.ai/code/session_01M4hK5tKJCvXKZDBpa93G15
…into fix/next-capture-output-stack-enrichment
@HugoRCD HugoRCD merged commit 6f21121 into main Jun 12, 2026
14 of 16 checks passed
@HugoRCD HugoRCD deleted the fix/next-capture-output-stack-enrichment branch June 12, 2026 07:43
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants