Repository navigation
deno: Deno.serve does not record request sessions #25495
Description
denoHttpIntegration records a request session for each incoming node:http request (#22888 turned this on). denoServeIntegration does not record sessions at all. Most Deno apps use Deno.serve, so release health for those apps shows no request sessions and no crash-free rate.
#25492 gives denoServeIntegration the other incoming request options from denoHttpIntegration. It leaves out sessions and sessionFlushingDelayMS, because they need more than an option.
Proposed options
Use the same names, defaults and behavior as denoHttpIntegration.
| Option | Type | Default | Notes |
|---|---|---|---|
sessions |
boolean |
true |
Record a request session for each incoming Deno.serve request. |
sessionFlushingDelayMS |
number |
60000 |
Time until the SDK sends the session aggregate. |
Why this is not a plain option
The shared helper recordRequestSession in @sentry/core/server does most of the work. It marks the request as a session on the isolation scope, counts it in a per-minute bucket when the request ends, and sends the aggregate on a timer or on client.flush(). DenoClient extends ServerRuntimeClient, which already sets the session status to errored or crashed when the SDK captures an exception during the request.
The problem is how the helper knows that a request ended. It takes a node:http response and waits for its close event:
recordRequestSession(client, { requestIsolationScope, response, sessionFlushingDelayMS });
// inside: response.once('close', () => { /* count the session */ });
Deno.serve has no response object with events. The handler returns a web Response, and a streamed body can finish long after the handler returns.
Suggested approach
- Change
recordRequestSessionso it does not need anode:httpresponse. For example, split it into a start step that returns anend()function. Node anddenoHttpIntegrationcallend()from the responsecloseevent, so their behavior does not change. - In
packages/deno/src/wrap-deno-request-handler.ts, start the session for each request whensessionsis on. Callend()when the response finishes:- For a normal response, after the handler returns.
- For a streamed response, when the stream finishes. The
onEndcallback that deno:denoServeIntegrationlacks the incoming request options thatdenoHttpIntegrationhas #25492 added tostreamResponsealready runs at this point. - When the handler throws, after
captureException, so the session counts ascrashed.
- Sessions do not depend on tracing. They must work when the SDK skips the span, for example with
spans: false,ignoreIncomingRequests, a static asset, or tracing turned off. TodaystreamResponsetakes a span, so the stream end needs a code path that works without one.
A fake response object that emits close would also work, but it hides the real contract and is easy to break.
Decisions needed
- OPTIONS and HEAD requests. The wrapper returns early for these methods. Core's
node:httpcode records a session for every request, including these. Match Node unless there is a reason not to. - Changelog. With
sessionsdefaulting totrue,Deno.serveapps start to send session aggregates after an upgrade. This needs a note.
Tests
Add tests to packages/deno/test/deno-serve.test.ts. Use a test transport and call client.flush()to send the aggregate.
- A successful request counts as
exited. - A handled
captureExceptionin the handler counts aserrored. - A thrown error counts as
crashed. sessions: falsesends no session.- A streamed response counts only after the stream finishes.
- Sessions are recorded when tracing is off and when the span is skipped.
Related
Bun.serve(bunServerIntegration) also records no request sessions. The samerecordRequestSessionchange would help there.
Activity
linear-code commentedon Oct 18, 2026
JS-3956
feat(deno): Record request sessions for `Deno.serve`