Skip to content

fix: defer wide-event emit for streaming AI responses#367

Open
HugoRCD wants to merge 3 commits into
mainfrom
fix/streaming-ai-emit-defer
Open

fix: defer wide-event emit for streaming AI responses#367
HugoRCD wants to merge 3 commits into
mainfrom
fix/streaming-ai-emit-defer

Conversation

@HugoRCD

@HugoRCD HugoRCD commented Jun 7, 2026

Copy link
Copy Markdown
Owner

Summary

  • Defer log.emit() until streaming response bodies finish (SSE, AI SDK UI streams, chunked bodies) across Next.js, SvelteKit, Hono, React Router, oRPC, and Nitro/Nuxt integrations.
  • Add a late-merge window for log.set({ ai }) before enrich/drain to cover narrow race windows.
  • Keeps the existing createAILogger(log) API — no Nitro-specific wrapper.

Closes #321 #363

Test plan

  • pnpm run lint && pnpm run test (1390 tests)
  • Regression tests: pnpm --filter evlog exec vitest run test/next/handler.test.ts test/frameworks/sveltekit.test.ts test/nitro/plugin.test.ts test/core/logger-request-logger.test.ts test/shared/streamResponse.test.ts
  • Manual: streaming AI route in Nuxt/Nitro or Next — confirm wide event includes ai and no [evlog] Keys dropped: ai warning

Summary by CodeRabbit

  • New Features

    • Streaming responses now defer wide-event emission until the response body finishes so late AI metadata is included on the same request event.
  • Bug Fixes

    • Prevents post‑emit warnings and dropped AI-only metadata for streaming flows (fixes reported issue).
  • Documentation

    • Clarified guides across frameworks about deferred emission and metadata timing for streaming responses.
  • Tests

    • Added tests for deferred emission, lifecycle handling, and AI metadata merging for streaming scenarios.

@vercel

vercel Bot commented Jun 7, 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 7, 2026 1:53pm
just-use-evlog Ready Ready Preview, Comment Jun 7, 2026 1:53pm

@github-actions

github-actions Bot commented Jun 7, 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 7, 2026
@coderabbitai

coderabbitai Bot commented Jun 7, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: e06bc385-7564-4eda-8914-d955b106b24d

📥 Commits

Reviewing files that changed from the base of the PR and between 643fec2 and 736b712.

⛔ 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 (1)
  • packages/evlog/src/shared/index.ts

📝 Walkthrough

Walkthrough

This PR defers wide-event emission for streaming HTTP responses until the response body completes, adds streaming-detection/lifecycle utilities, tracks pending emitted events to allow AI-only late merges, exposes a drain-start marker, updates middleware/adapters to use finishResponse, and adds tests and docs.

Changes

Streaming emit deferral for AI SDK metadata

Layer / File(s) Summary
Streaming response detection and lifecycle binding
packages/evlog/src/shared/streamResponse.ts, packages/evlog/src/shared/index.ts
Implements StreamCompleteMeta, isStreamingResponse(), shouldDeferEmitForResponse(), stream observation (createObservedBody()), and bindStreamingResponseLifecycle() to wrap streaming Responses and invoke completion callbacks.
Logger pending-drain tracking and AI-only merge
packages/evlog/src/logger.ts
Tracks a pending wide event and drain-start state; merges AI-only set() updates into an emitted-but-not-drained event and exports markWideEventDrainStarted() for frameworks to signal drain onset.
Middleware finishResponse API
packages/evlog/src/shared/middleware.ts
Adds finishResponse(response, opts?) and refactors finish logic into performFinish; defers finish for streaming responses using lifecycle binding and marks drain start before drain.
Next.js streaming-aware request emission
packages/evlog/src/next/handler.ts, packages/evlog/test/next/handler.test.ts
Extracts tail-sampling/emit+drain into emitRequestEvent. Detects streaming Responses, wraps with bindStreamingResponseLifecycle, defers emission until completion metadata, and tests SSE deferred emission and AI merge.
Nitro (v2 & v3) streaming-aware response emission
packages/evlog/src/nitro/plugin.ts, packages/evlog/src/nitro-v3/plugin.ts, packages/evlog/test/nitro/plugin.test.ts
Introduces emitSuccessResponse helpers and defers emission for streaming responses by binding lifecycle callbacks; logs streaming errors and preserves response when wrapping; adds tests for deferred emit behavior.
SvelteKit, Hono, React Router, oRPC streaming support
packages/evlog/src/sveltekit/index.ts, packages/evlog/src/hono/index.ts, packages/evlog/src/react-router/index.ts, packages/evlog/src/orpc/index.ts, packages/evlog/test/frameworks/sveltekit.test.ts
Adapters adopt finishResponse: SvelteKit/React Router return finishResponse(response); Hono conditionally uses finishResponse for streaming responses; oRPC awaits finishResponse for matched responses. Adds SvelteKit test for streaming defer behavior.
Streaming response unit tests and docs
packages/evlog/test/core/logger-request-logger.test.ts, packages/evlog/test/shared/streamResponse.test.ts, packages/evlog/test/helpers/stream.ts, .changeset/streaming-ai-emit-defer.md, apps/docs/content/*
Adds tests for stream detection, lifecycle callbacks, error handling, locked-body edge cases, and logger pending-drain AI merge. Updates docs and the changeset to describe deferred emission and list affected integrations, closing issue #321.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

  • HugoRCD/evlog#344: Introduces the oRPC integration that this PR extends with streaming-aware finishResponse handling.

Suggested labels

bug

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 50.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the main change: deferring wide-event emission for streaming AI responses, which directly addresses the core issue.
Description check ✅ Passed The PR description covers the summary of changes, test plan, and linked issues, providing sufficient context for understanding the fix.
Linked Issues check ✅ Passed The PR successfully addresses issue #321 by deferring wide-event emit for streaming responses and adding a late-merge window for log.set({ ai }), ensuring AI metadata is included without warnings.
Out of Scope Changes check ✅ Passed All changes are directly related to deferring wide-event emission and supporting late AI metadata merging across multiple framework integrations and core logger logic.

✏️ 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/streaming-ai-emit-defer

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: 8

🤖 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/streaming-ai-emit-defer.md:
- Line 5: Split the dense single-sentence changeset into 2–3 clearer sentences:
first state the main change ("Defer wide-event emit for streaming HTTP responses
... until the response body finishes") as one sentence, then add a second
sentence describing the effect on createAILogger metadata and prevention of
post-emit warnings, and a short third sentence listing the affected integrations
(Next.js withEvlog, SvelteKit, Hono, React Router, oRPC, Nitro/Nuxt) and the
merge behavior for late `ai` fields; preserve all original technical details and
wording but break into natural sentence boundaries for readability.

In `@apps/docs/content/2.learn/2.wide-events.md`:
- Around line 191-192: Update the sentence about "supported framework
integrations" to explicitly list the frameworks that defer wide-event emit so
readers know which integrations behave this way (reference the
createAILogger(log) metadata behavior and the wide-event emit mechanism); if you
prefer to keep it short, add a parenthetical pointer saying "see AI SDK usage
guide for full list" and/or mirror the same framework list used in the AI SDK
usage guide so wording is consistent with that document.

In `@apps/docs/content/3.integrate/frameworks/04.nitro.md`:
- Line 128: The note about post-emit warnings is potentially misleading for AI
SDK streaming — update the sentence referencing post-emit warnings to clarify
scope: explain that Nitro uses useLogger(event) (event-bound scope) so
log.fork() is unavailable, and that evlog defers wide-event emit for AI SDK
streaming so createAILogger(log) metadata remains attached until the response
body finishes; then either remove the blanket "Post-emit warnings still
apply..." clause or change it to a brief conditional statement saying warnings
only occur if code calls set() after the wide event has actually emitted (e.g.,
in non-streaming or background code paths), referencing useLogger(event),
log.fork(), createAILogger(log), evlog, wide-event emit and set() so readers can
locate the related concepts.

In `@packages/evlog/src/logger.ts`:
- Around line 891-894: The pending merge window allows AI data to be merged
after a drain even when deferDrain is false: update the logic around
pendingWideEvent/pendingDrainState so non-deferred loggers are not left with
drainStarted:false after immediate drain. Specifically, in emitWideEvent and
where pendingDrainState is initialized (symbols: pendingWideEvent,
pendingDrainState, emitWideEvent, deferDrain, and the set method that accepts {
ai }), either only create a pendingDrainState entry when deferDrain is true, or
set drainStarted:true immediately when deferDrain is false so subsequent set({
ai }) calls cannot merge into an already-drained event; apply the same rule
wherever pendingDrainState is populated to ensure AI data is not lost.

In `@packages/evlog/src/next/handler.ts`:
- Around line 232-241: The logger context is set to result.status before
streaming completes, so when a streaming response errors the final status
(meta.status) diverges; inside the bindStreamingResponseLifecycle completion
callback (the async meta => { ... } passed to bindStreamingResponseLifecycle)
update the logger context with the final status (use logger.set({ status:
meta.status ?? result.status })) before calling emitRequestEvent and logging
meta.error so the logger reflects the actual final response status; adjust the
block around bindStreamingResponseLifecycle, emitRequestEvent, and logger.set
accordingly.

In `@packages/evlog/src/shared/streamResponse.ts`:
- Around line 1-4: Add a JSDoc block above the exported StreamCompleteMeta
interface that documents the interface as a public API: describe that
StreamCompleteMeta represents the final metadata for a stream completion,
explain the status field as the HTTP-like numeric status code (number) and the
optional error field as an Error object present when the stream failed, and
include any relevant tags (e.g., `@export` or `@public`) per project convention;
place the comment immediately above the StreamCompleteMeta declaration to
satisfy the coding guideline for public APIs.

In `@packages/evlog/test/next/handler.test.ts`:
- Around line 7-24: Extract the duplicated createDeferredStream helper (and its
TextEncoder instance) into a shared helper module (e.g., helpers/stream) that
exports createDeferredStream, implement the safer close wrapper that throws if
close is uninitialized, then remove the local createDeferredStream definitions
in the three tests and import the shared createDeferredStream instead (update
usages in packages/evlog/test/shared/streamResponse.test.ts,
packages/evlog/test/next/handler.test.ts, and
packages/evlog/test/nitro/plugin.test.ts to use the exported function).

In `@packages/evlog/test/nitro/plugin.test.ts`:
- Around line 979-996: The createDeferredStream helper (and its encoder) is
duplicated across tests; extract it into a shared helper module (e.g.,
helpers/stream.ts) that exports createDeferredStream, keeping the same behavior
but replacing the inline close wrapper with a safe check that throws if close is
uninitialized, then update the tests (including the instance in plugin.test.ts)
to import createDeferredStream from the new module and remove the local
definition; reference symbols: createDeferredStream and encoder.
🪄 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: 43fc0668-d010-4d39-b0df-52cdc6ea24c8

📥 Commits

Reviewing files that changed from the base of the PR and between 7c046d0 and 5c0fd1f.

⛔ 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 (21)
  • .changeset/streaming-ai-emit-defer.md
  • apps/docs/content/2.learn/2.wide-events.md
  • apps/docs/content/3.integrate/frameworks/02.nextjs.md
  • apps/docs/content/3.integrate/frameworks/04.nitro.md
  • apps/docs/content/4.use-cases/2.ai-sdk/02.usage.md
  • packages/evlog/src/hono/index.ts
  • packages/evlog/src/logger.ts
  • packages/evlog/src/next/handler.ts
  • packages/evlog/src/nitro-v3/plugin.ts
  • packages/evlog/src/nitro/plugin.ts
  • packages/evlog/src/orpc/index.ts
  • packages/evlog/src/react-router/index.ts
  • packages/evlog/src/shared/index.ts
  • packages/evlog/src/shared/middleware.ts
  • packages/evlog/src/shared/streamResponse.ts
  • packages/evlog/src/sveltekit/index.ts
  • packages/evlog/test/core/logger-request-logger.test.ts
  • packages/evlog/test/frameworks/sveltekit.test.ts
  • packages/evlog/test/next/handler.test.ts
  • packages/evlog/test/nitro/plugin.test.ts
  • packages/evlog/test/shared/streamResponse.test.ts

Comment thread .changeset/streaming-ai-emit-defer.md Outdated
Comment thread apps/docs/content/2.learn/2.wide-events.md Outdated
Comment thread apps/docs/content/3.integrate/frameworks/04.nitro.md Outdated
Comment thread packages/evlog/src/logger.ts
Comment thread packages/evlog/src/next/handler.ts
Comment thread packages/evlog/src/shared/streamResponse.ts
Comment thread packages/evlog/test/next/handler.test.ts Outdated
Comment thread packages/evlog/test/nitro/plugin.test.ts Outdated
Co-authored-by: Hussain Arslan <m.hussain.arslan@gmail.com>

@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/shared/streamResponse.ts (1)

12-17: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Add JSDoc comment to exported function.

Per coding guidelines, all public APIs require JSDoc comments. This function is exported but lacks documentation.

📝 Suggested JSDoc
+/**
+ * Whether a {`@link` Response} carries a non-null body stream.
+ */
 export function isStreamingResponse(response: Response): boolean {
   return response.body !== null
 }
🤖 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/shared/streamResponse.ts` around lines 12 - 17, The
exported function isStreamingResponse lacks a JSDoc block; add a JSDoc comment
above the isStreamingResponse function describing what it does, the parameter
(response: Response) and its meaning, and the boolean return value (true when
the Response has a non-null streaming body), following project JSDoc style
(brief description, `@param`, `@returns`). Ensure the comment is placed directly
above the export so tooling and docs pick it up.

Source: Coding guidelines

🤖 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/shared/streamResponse.ts`:
- Around line 12-17: The exported function isStreamingResponse lacks a JSDoc
block; add a JSDoc comment above the isStreamingResponse function describing
what it does, the parameter (response: Response) and its meaning, and the
boolean return value (true when the Response has a non-null streaming body),
following project JSDoc style (brief description, `@param`, `@returns`). Ensure the
comment is placed directly above the export so tooling and docs pick it up.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 6ff37a97-c365-4ed4-a8cd-98141231b041

📥 Commits

Reviewing files that changed from the base of the PR and between 5c0fd1f and 643fec2.

📒 Files selected for processing (11)
  • .changeset/streaming-ai-emit-defer.md
  • apps/docs/content/2.learn/2.wide-events.md
  • apps/docs/content/3.integrate/frameworks/04.nitro.md
  • packages/evlog/src/logger.ts
  • packages/evlog/src/next/handler.ts
  • packages/evlog/src/shared/streamResponse.ts
  • packages/evlog/test/core/logger-request-logger.test.ts
  • packages/evlog/test/helpers/stream.ts
  • packages/evlog/test/next/handler.test.ts
  • packages/evlog/test/nitro/plugin.test.ts
  • packages/evlog/test/shared/streamResponse.test.ts

Co-authored-by: Hussain Arslan <m.hussain.arslan@gmail.com>
@pkg-pr-new

pkg-pr-new Bot commented Jun 7, 2026

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

commit: 736b712

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.

[bug] "[evlog] log.set() called after the wide event was emitted — Keys dropped: ai

1 participant