fix(next): split instrumentation gate and quiet edge dev warnings#380
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
Thank you for following the naming conventions! 🙏 |
|
Caution Review failedThe pull request is closed. ℹ️ Recent review info⚙️ Run configurationConfiguration used: Organization UI Review profile: ASSERTIVE Plan: Pro Plus Run ID: 📒 Files selected for processing (2)
📝 WalkthroughWalkthroughSplits 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. ChangesNext.js instrumentation modularization for Edge runtime safety
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
🎯 3 (Moderate) | ⏱️ ~25 minutes Possibly related PRs
Suggested labelsbug 🚥 Pre-merge checks | ✅ 3 | ❌ 2❌ Failed checks (2 warnings)
✅ Passed checks (3 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
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
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. Comment |
There was a problem hiding this comment.
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 | 🟠 MajorAwait
inner.register()to propagate/handle async init failures.In
packages/evlog/src/next/stream.ts(line 83),inner.register()can returnPromise<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
⛔ Files ignored due to path filters (1)
packages/evlog/test/toolkit/__snapshots__/api-surface.test.ts.snapis excluded by!**/*.snap
📒 Files selected for processing (20)
.changeset/next-capture-output-fs-edge.mdapps/docs/content/3.integrate/adapters/self-hosted/01.fs.mdapps/docs/content/3.integrate/frameworks/02.nextjs.mdapps/next-playground/app/api/evlog/ingest/route.tsapps/next-playground/app/api/test/browser-ingest/route.tsapps/next-playground/app/api/test/drain/route.tsapps/next-playground/app/page.tsxapps/next-playground/instrumentation.tsapps/next-playground/lib/evlog.tsexamples/nextjs/lib/evlog.tspackages/evlog/package.jsonpackages/evlog/src/adapters/fs.tspackages/evlog/src/next/instrumentation-create.tspackages/evlog/src/next/instrumentation-gate.tspackages/evlog/src/next/instrumentation.tspackages/evlog/src/next/stream.tspackages/evlog/test/adapters/fs.test.tspackages/evlog/test/next/instrumentation.test.tspackages/evlog/test/next/stream.test.tspackages/evlog/tsdown.config.ts
commit: |
There was a problem hiding this comment.
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 winThe new patch guards make
captureOutputfirst-registration-wins.Lines 149-180 prevent double-wrapping, but the installed wrappers still close over the first
ignorearray andlogApi. Any latercreateInstrumentation(...).register()call with differentcaptureOutputsettings 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 winClear
cachedwhen the loader rejects.Line 98 memoizes
loader()before it resolves. If the dynamic import or a custom loader fails once, every laterregister()/onRequestError()call reuses the same rejected promise, so this instrumentation instance cannot recover in-process. Mirror the retry behavior you added increateInstrumentation.register()and resetcachedin 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
📒 Files selected for processing (5)
apps/docs/content/3.integrate/adapters/self-hosted/01.fs.mdpackages/evlog/src/next/instrumentation-create.tspackages/evlog/src/next/instrumentation-gate.tspackages/evlog/src/next/stream.tspackages/evlog/test/adapters/fs.test.ts
Summary by CodeRabbit
Documentation
New Features
Improvements
Bug Fixes
Other