Skip to content

fix(next): split instrumentation gate and quiet edge dev warnings#380

Merged
HugoRCD merged 3 commits into
mainfrom
fix/next-instrumentation-edge-dx
Jun 11, 2026
Merged

fix(next): split instrumentation gate and quiet edge dev warnings#380
HugoRCD merged 3 commits into
mainfrom
fix/next-instrumentation-edge-dx

Conversation

@HugoRCD

@HugoRCD HugoRCD commented Jun 11, 2026

Copy link
Copy Markdown
Owner

Summary by CodeRabbit

  • Documentation

    • Clarified Next.js integration, Node vs Edge guidance, FS adapter runtime notes, and expanded captureOutput docs.
  • New Features

    • captureOutput adds stdout/stderr toggles and configurable ignore patterns.
  • Improvements

    • FS adapter warns once and skips writes on Edge.
    • Instrumentation split to keep Node-only setup out of Edge bundles and enable options-based Node initialization.
  • Bug Fixes

    • Default filtering excludes known Edge bundler warnings from captured output.
  • Other

    • Playground: dev-only logging gated and UI text updated to reflect full drain pipeline.

@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 11, 2026 9:11pm
just-use-evlog Ready Ready Preview, Comment Jun 11, 2026 9:11pm

@github-actions

github-actions Bot commented Jun 11, 2026

Copy link
Copy Markdown
Contributor

Thank you for following the naming conventions! 🙏

@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: 53e0d2ba-01dd-4f7e-9fe9-aaad907bcef9

📥 Commits

Reviewing files that changed from the base of the PR and between 7df4ec8 and 05900b5.

📒 Files selected for processing (2)
  • packages/evlog/src/next/instrumentation-create.ts
  • packages/evlog/test/next/instrumentation.test.ts

📝 Walkthrough

Walkthrough

Splits Next.js instrumentation into an Edge-safe gate and a Node-only factory, adds configurable captureOutput (stdout/stderr/ignore) with default Edge-bundler filters, makes the FS adapter warn once and skip writes in Edge, and updates integrations, tests, and docs to match.

Changes

Next.js instrumentation modularization for Edge runtime safety

Layer / File(s) Summary
Edge-safe gate module and module split
packages/evlog/src/next/instrumentation-gate.ts, packages/evlog/src/next/instrumentation.ts
New edge-safe gate exports defineNodeInstrumentation, request/error types, and loader plumbing that conditionally imports the Node-only factory only in Node runtime; main instrumentation.ts becomes a thin re-export barrel.
Node-only instrumentation factory with captureOutput
packages/evlog/src/next/instrumentation-create.ts
New createInstrumentation implements logger init, idempotent register(), onRequestError(), and stdout/stderr patching with CaptureOutputOptions and DEFAULT_CAPTURE_OUTPUT_IGNORE for filtering Edge bundler warnings.
Package.json exports and build configuration
packages/evlog/package.json, packages/evlog/tsdown.config.ts
Adds ./next/instrumentation/create public subpath export with types and adds the create entry to tsdown build mappings.
FS adapter Edge-runtime detection and safety
packages/evlog/src/adapters/fs.ts, packages/evlog/test/adapters/fs.test.ts
FS adapter detects NEXT_RUNTIME === 'edge', emits a single [evlog/fs] warning, and returns null to skip drain setup on Edge; test verifies one warning and no file writes across multiple invocations.
Usage updates in stream, playground, and examples
packages/evlog/src/next/stream.ts, apps/next-playground/instrumentation.ts, apps/next-playground/lib/evlog.ts, examples/nextjs/lib/evlog.ts
Consumers updated to import the create entry, await inner registrations where appropriate, use options-based defineNodeInstrumentation({ service, captureOutput }), and add FS drain to playground batch pipeline.
Test suite updates and new coverage
packages/evlog/test/next/instrumentation.test.ts, packages/evlog/test/next/stream.test.ts
Mocks extended to spy on logger.info/error, tests refactored to await register()/onRequestError(), captureOutput coverage expanded (ignore filters, stdout/stderr toggles, custom ignores), and module-split export assertions added.
Documentation and playground UI updates
.changeset/next-capture-output-fs-edge.md, apps/docs/content/3.integrate/adapters/self-hosted/01.fs.md, apps/docs/content/3.integrate/frameworks/02.nextjs.md, apps/next-playground/app/*
Docs updated to explain the split wiring, new captureOutput options and behavior, and FS adapter Edge constraints; playground routes now gate dev-only logging and UI text updated to reference .evlog/logs/ and the full drain pipeline.

Sequence diagram — instrumentation registration flow:

sequenceDiagram
  participant AppRoot as Next instrumentation root
  participant Gate as defineNodeInstrumentation (edge-safe)
  participant Importer as dynamic import (webpackIgnore)
  participant Factory as createInstrumentation (Node-only)
  AppRoot->>Gate: defineNodeInstrumentation({service,...})
  Gate->>Gate: if NEXT_RUNTIME !== 'nodejs' -> return no-op hooks
  Gate->>Importer: import(create path)
  Importer->>Factory: load createInstrumentation
  AppRoot->>Gate: register() called at startup
  Gate->>Factory: delegate register() when Node
  Factory->>Factory: init logger, setup captureOutput, patch stdout/stderr
Loading

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

  • HugoRCD/evlog#352: Adjusts Next.js instrumentation output-patching logic for process.stdout.write/process.stderr.write wrapper typing.

Suggested labels

bug

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Description check ⚠️ Warning The pull request description is empty, missing required sections like objectives, description of changes, and completion of the provided template checklist. Add a comprehensive description including the problem statement, solution overview, and check off items in the provided template checklist (linked issues and documentation updates).
Docstring Coverage ⚠️ Warning Docstring coverage is 18.18% 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 accurately reflects the main changes: splitting Next.js instrumentation (gate module vs. create module) and adding Edge runtime warnings suppression.
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-instrumentation-edge-dx

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.

@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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
packages/evlog/src/next/stream.ts (1)

81-84: ⚠️ Potential issue | 🟠 Major

Await inner.register() to propagate/handle async init failures.

In packages/evlog/src/next/stream.ts (line 83), inner.register() can return Promise<void> (logger is loaded asynchronously). Since the promise isn’t awaited, register() may resolve early and any rejection can become an unhandled promise rejection.

Suggested fix
   const composedDrain = composeDrains(userDrain, serverDrain)
   const inner = createInstrumentation({ ...rest, drain: composedDrain })
-  inner.register()
+  await inner.register()
 }
🤖 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/stream.ts` around lines 81 - 84, The call to
inner.register() in the block that creates composedDrain and inner (from
composeDrains and createInstrumentation) can return a Promise and currently
isn’t awaited, risking unhandled rejections; change the caller so that
inner.register() is awaited (or its Promise returned) to propagate async init
failures—i.e., make the enclosing function async if needed and await
inner.register() (or return inner.register()) so any rejection bubbles up and is
handled by the caller.
🤖 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 `@apps/docs/content/3.integrate/adapters/self-hosted/01.fs.md`:
- Line 85: Update the sentence that currently reads "Use evlog/memory or a HTTP
adapter for Edge routes." to use correct article; replace "a HTTP adapter" with
"an HTTP adapter" in the content string so the sentence becomes "Use
evlog/memory or an HTTP adapter for Edge routes."

In `@packages/evlog/src/next/instrumentation-create.ts`:
- Around line 36-57: Exported interface InstrumentationOptions is missing a
top-level JSDoc comment; add a descriptive JSDoc block above the
InstrumentationOptions declaration that summarizes its purpose (configuration
for instrumentation/logging), lists or briefly describes what it controls
(enabled, service, env, pretty, silent, sampling, minLevel, stringify, drain,
captureOutput) and any defaults/notes, so the public API has proper
documentation for consumers and satisfies the repo's JSDoc requirement.
- Around line 108-131: The module-level flag registered is set to true before
async initialization (loadLogger()/initLogger()) completes, making register()
permanently a no-op if initialization ever rejects; change register() so
registered is only set to true after loadLogger() and initLogger() succeed
(e.g., await loadLogger()/initLogger() or set registered = true in the .then
success path), ensure any rejection leaves registered false so subsequent calls
can retry, and keep lockLogger() and patchOutput(...) execution inside the
successful path so failures don't mark the module as registered.
- Around line 133-167: Make patchOutput idempotent: detect if
process.stdout.write/process.stderr.write have already been wrapped by this
module (e.g., by tagging the wrapped function with a unique Symbol or setting a
hidden property) and skip wrapping again; when creating the wrapper in
patchOutput (refer to patchOutput, proc.stdout.write, proc.stderr.write,
originalStdoutWrite, originalStderrWrite) check that tag first, and only replace
the write functions if not already tagged, and tag the new wrappers so
subsequent calls to createInstrumentation(...).register() do not double-wrap.

In `@packages/evlog/src/next/instrumentation-gate.ts`:
- Around line 53-60: Add a top-level JSDoc block to the exported type
NodeInstrumentationHooks describing its purpose and the shape of its members;
document the register() method as an async registration hook and the
onRequestError(...) callback (including the error parameter shape with optional
digest and the NextInstrumentationRequest and NextInstrumentationErrorContext
types) so the public API matches adjacent exports and satisfies the project's
JSDoc requirement.

In `@packages/evlog/test/adapters/fs.test.ts`:
- Around line 319-336: The test should ensure cleanup always runs by moving the
environment and spy restore into a finally block: in the 'warns once and skips
writes on edge runtime' test wrap the calls to drain and the expectations in try
{ ... } finally { delete process.env.NEXT_RUNTIME; warnSpy.mockRestore(); },
keep the existing expectations including
expect(mockedAppendFile).not.toHaveBeenCalled(), and leave createFsDrain and
createDrainContext references unchanged so the test still imports
createFsDrainFresh and uses createDrainContext as before.

---

Outside diff comments:
In `@packages/evlog/src/next/stream.ts`:
- Around line 81-84: The call to inner.register() in the block that creates
composedDrain and inner (from composeDrains and createInstrumentation) can
return a Promise and currently isn’t awaited, risking unhandled rejections;
change the caller so that inner.register() is awaited (or its Promise returned)
to propagate async init failures—i.e., make the enclosing function async if
needed and await inner.register() (or return inner.register()) so any rejection
bubbles up and is handled by the caller.
🪄 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: 55eb856b-6d0a-4d5d-a135-2c43e9466487

📥 Commits

Reviewing files that changed from the base of the PR and between 4c51970 and f97fc72.

⛔ Files ignored due to path filters (1)
  • packages/evlog/test/toolkit/__snapshots__/api-surface.test.ts.snap is excluded by !**/*.snap
📒 Files selected for processing (20)
  • .changeset/next-capture-output-fs-edge.md
  • apps/docs/content/3.integrate/adapters/self-hosted/01.fs.md
  • apps/docs/content/3.integrate/frameworks/02.nextjs.md
  • apps/next-playground/app/api/evlog/ingest/route.ts
  • apps/next-playground/app/api/test/browser-ingest/route.ts
  • apps/next-playground/app/api/test/drain/route.ts
  • apps/next-playground/app/page.tsx
  • apps/next-playground/instrumentation.ts
  • apps/next-playground/lib/evlog.ts
  • examples/nextjs/lib/evlog.ts
  • packages/evlog/package.json
  • packages/evlog/src/adapters/fs.ts
  • packages/evlog/src/next/instrumentation-create.ts
  • packages/evlog/src/next/instrumentation-gate.ts
  • packages/evlog/src/next/instrumentation.ts
  • packages/evlog/src/next/stream.ts
  • packages/evlog/test/adapters/fs.test.ts
  • packages/evlog/test/next/instrumentation.test.ts
  • packages/evlog/test/next/stream.test.ts
  • packages/evlog/tsdown.config.ts

Comment thread apps/docs/content/3.integrate/adapters/self-hosted/01.fs.md Outdated
Comment thread packages/evlog/src/next/instrumentation-create.ts
Comment thread packages/evlog/src/next/instrumentation-create.ts
Comment thread packages/evlog/src/next/instrumentation-create.ts Outdated
Comment thread packages/evlog/src/next/instrumentation-gate.ts
Comment thread packages/evlog/test/adapters/fs.test.ts
@pkg-pr-new

pkg-pr-new Bot commented Jun 11, 2026

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

commit: 7df4ec8

@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.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
packages/evlog/src/next/instrumentation-create.ts (1)

149-180: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

The new patch guards make captureOutput first-registration-wins.

Lines 149-180 prevent double-wrapping, but the installed wrappers still close over the first ignore array and logApi. Any later createInstrumentation(...).register() call with different captureOutput settings silently keeps using the earlier filters/sink, so behavior becomes order-dependent across repeated registrations and tests.

Patch once, but keep the active capture config in shared module state so later registrations can update it without re-wrapping.

🤖 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/instrumentation-create.ts` around lines 149 - 180,
The current stdout/stderr wrappers (guarded by stdoutPatched/stderrPatched)
close over the first-registration's ignore array and logApi, causing
first-registration-wins for captureOutput; change this by extracting the active
capture configuration (e.g., currentIgnore and currentLogApi) into module-level
mutable state and make the installed wrappers call shouldIgnoreCapturedOutput
against currentIgnore and forward to currentLogApi at call time; update that
module-level state from createInstrumentation(...).register() (or wherever
captureOutput is applied) so subsequent registrations update the active
filters/sink without re-wrapping, leaving stdoutPatched/stderrPatched checks to
prevent duplicate installs.
♻️ Duplicate comments (1)
packages/evlog/src/next/instrumentation-gate.ts (1)

97-99: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Clear cached when the loader rejects.

Line 98 memoizes loader() before it resolves. If the dynamic import or a custom loader fails once, every later register() / onRequestError() call reuses the same rejected promise, so this instrumentation instance cannot recover in-process. Mirror the retry behavior you added in createInstrumentation.register() and reset cached in a failure path.

💡 Minimal fix
 function load(): Promise<NodeInstrumentationModule> {
-  cached ??= loader()
+  cached ??= loader().catch((error) => {
+    cached = undefined
+    throw error
+  })
   return cached
 }
🤖 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/instrumentation-gate.ts` around lines 97 - 99, The
load() function currently memoizes the promise returned by loader() into cached
so a failed dynamic import leaves cached as a rejected promise; update load() to
attach a rejection handler to the loader() promise that clears cached (set
cached = undefined) and rethrows the error so subsequent calls retry the loader;
keep the existing cached ??= loader() assignment but replace it with cached ??=
loader().catch(err => { cached = undefined; throw err; }) (mirrors the retry
behavior in createInstrumentation.register()) so the instrumentation can recover
after a transient failure.
🤖 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.

Outside diff comments:
In `@packages/evlog/src/next/instrumentation-create.ts`:
- Around line 149-180: The current stdout/stderr wrappers (guarded by
stdoutPatched/stderrPatched) close over the first-registration's ignore array
and logApi, causing first-registration-wins for captureOutput; change this by
extracting the active capture configuration (e.g., currentIgnore and
currentLogApi) into module-level mutable state and make the installed wrappers
call shouldIgnoreCapturedOutput against currentIgnore and forward to
currentLogApi at call time; update that module-level state from
createInstrumentation(...).register() (or wherever captureOutput is applied) so
subsequent registrations update the active filters/sink without re-wrapping,
leaving stdoutPatched/stderrPatched checks to prevent duplicate installs.

---

Duplicate comments:
In `@packages/evlog/src/next/instrumentation-gate.ts`:
- Around line 97-99: The load() function currently memoizes the promise returned
by loader() into cached so a failed dynamic import leaves cached as a rejected
promise; update load() to attach a rejection handler to the loader() promise
that clears cached (set cached = undefined) and rethrows the error so subsequent
calls retry the loader; keep the existing cached ??= loader() assignment but
replace it with cached ??= loader().catch(err => { cached = undefined; throw
err; }) (mirrors the retry behavior in createInstrumentation.register()) so the
instrumentation can recover after a transient failure.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: e6f7edc2-ea6d-466f-8591-3ee174b0172b

📥 Commits

Reviewing files that changed from the base of the PR and between f97fc72 and 7df4ec8.

📒 Files selected for processing (5)
  • apps/docs/content/3.integrate/adapters/self-hosted/01.fs.md
  • packages/evlog/src/next/instrumentation-create.ts
  • packages/evlog/src/next/instrumentation-gate.ts
  • packages/evlog/src/next/stream.ts
  • packages/evlog/test/adapters/fs.test.ts

@HugoRCD HugoRCD merged commit af238a2 into main Jun 11, 2026
12 of 15 checks passed
@HugoRCD HugoRCD deleted the fix/next-instrumentation-edge-dx branch June 11, 2026 21:09
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.

1 participant