Skip to content

Portable http.Server integration#20779

Open
isaacs wants to merge 5 commits into
developfrom
isaacschlueter/portable-http-integration-server
Open

Portable http.Server integration#20779
isaacs wants to merge 5 commits into
developfrom
isaacschlueter/portable-http-integration-server

Conversation

@isaacs
Copy link
Copy Markdown
Member

@isaacs isaacs commented May 9, 2026

  • Portable connect integration logic de-OTel-ified and moved into core, for use in bun/deno. (Similar to previous Express integration migration.)
  • Add HTTP server-spans subscription utility to core.
  • Use this utility in node-core and node-core/light.

Before submitting a pull request, please take a look at our
Contributing guidelines and verify:

  • If you've added code that should be tested, please add tests.
  • Ensure your code lints and the test suite passes (yarn lint) & (yarn test).
  • Link an issue if there is one related to your pull request. If no issue is linked, one will be auto-generated and linked.

Closes #issue_link_here

@isaacs isaacs requested a review from a team as a code owner May 9, 2026 18:23
Comment thread packages/core/src/integrations/http/server-subscription.ts Outdated
Copy link
Copy Markdown

@cursor cursor Bot left a comment

Choose a reason for hiding this comment

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

Cursor Bugbot has reviewed your changes and found 2 potential issues.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit f1ac205. Configure here.

Comment thread packages/core/src/integrations/http/server-subscription.ts Outdated
Comment thread packages/core/src/integrations/http/server-span-subscription.ts
@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented May 9, 2026

size-limit report 📦

Path Size % Change Change
@sentry/browser 26.3 kB - -
@sentry/browser - with treeshaking flags 24.78 kB - -
@sentry/browser (incl. Tracing) 44.17 kB - -
@sentry/browser (incl. Tracing + Span Streaming) 46.39 kB - -
@sentry/browser (incl. Tracing, Profiling) 49.14 kB - -
@sentry/browser (incl. Tracing, Replay) 83.82 kB - -
@sentry/browser (incl. Tracing, Replay) - with treeshaking flags 73.28 kB - -
@sentry/browser (incl. Tracing, Replay with Canvas) 88.51 kB - -
@sentry/browser (incl. Tracing, Replay, Feedback) 101.12 kB - -
@sentry/browser (incl. Feedback) 43.44 kB - -
@sentry/browser (incl. sendFeedback) 31.11 kB - -
@sentry/browser (incl. FeedbackAsync) 36.19 kB - -
@sentry/browser (incl. Metrics) 27.6 kB - -
@sentry/browser (incl. Logs) 27.73 kB - -
@sentry/browser (incl. Metrics & Logs) 28.43 kB - -
@sentry/react 28.04 kB - -
@sentry/react (incl. Tracing) 46.4 kB - -
@sentry/vue 31.18 kB - -
@sentry/vue (incl. Tracing) 46.02 kB - -
@sentry/svelte 26.32 kB - -
CDN Bundle 28.91 kB - -
CDN Bundle (incl. Tracing) 46.94 kB - -
CDN Bundle (incl. Logs, Metrics) 30.34 kB - -
CDN Bundle (incl. Tracing, Logs, Metrics) 48.04 kB - -
CDN Bundle (incl. Replay, Logs, Metrics) 69.64 kB - -
CDN Bundle (incl. Tracing, Replay) 84.31 kB - -
CDN Bundle (incl. Tracing, Replay, Logs, Metrics) 85.39 kB - -
CDN Bundle (incl. Tracing, Replay, Feedback) 90.13 kB - -
CDN Bundle (incl. Tracing, Replay, Feedback, Logs, Metrics) 91.19 kB - -
CDN Bundle - uncompressed 84.88 kB - -
CDN Bundle (incl. Tracing) - uncompressed 140.44 kB - -
CDN Bundle (incl. Logs, Metrics) - uncompressed 89.08 kB - -
CDN Bundle (incl. Tracing, Logs, Metrics) - uncompressed 143.9 kB - -
CDN Bundle (incl. Replay, Logs, Metrics) - uncompressed 213.9 kB - -
CDN Bundle (incl. Tracing, Replay) - uncompressed 259.14 kB - -
CDN Bundle (incl. Tracing, Replay, Logs, Metrics) - uncompressed 262.59 kB - -
CDN Bundle (incl. Tracing, Replay, Feedback) - uncompressed 272.84 kB - -
CDN Bundle (incl. Tracing, Replay, Feedback, Logs, Metrics) - uncompressed 276.28 kB - -
@sentry/nextjs (client) 48.9 kB - -
@sentry/sveltekit (client) 44.64 kB - -
@sentry/node-core 60.84 kB +0.61% +365 B 🔺
@sentry/node 165.27 kB -0.15% -234 B 🔽
@sentry/node - without tracing 73.76 kB +0.37% +271 B 🔺
@sentry/aws-serverless 107.99 kB +0.34% +360 B 🔺
@sentry/cloudflare (withSentry) - minified 169.35 kB - -
@sentry/cloudflare (withSentry) 427.5 kB - -

View base workflow run

@isaacs isaacs marked this pull request as draft May 9, 2026 18:56
isaacs added 5 commits May 9, 2026 13:38
Platform-portable Connect tracing integration in `@sentry/core`
(`patchConnectModule`, `setupConnectErrorHandler`), similar to portable
Express integration, and rewire the Node SDK's Connect integration to
call into it through the otel InstrumentationBase class.

Remove OTel-specific span attribute fix-up. Spans created with correct
origin (`auto.http.connect`) and op directly in the middleware.
Add platform-portable building blocks that server SDKs use to instrument
incoming HTTP requests without depending on OTel HTTP instrumentation:

- `getHttpServerSubscriptions`: diagnostics_channel listener for
  `http.server.request.start`, set up isolation scope, request data,
  trace continuation, optional body capture and request-session tracking
- `getHttpServerSpanSubscriptions`: wrapper that creates the root server
  span around the request lifecycle, applying static-asset/status-code
  filtering, `ignoreIncomingRequests` and `onSpanCreated` hooks
- `recordRequestSession`: release-health session aggregation per request
- `patchRequestToCaptureBody`: opt-in incoming request body capture

Add `kind` field on `StartSpanOptions` so OTel-based SDKs can set
SpanKind on the underlying span, and update `headersToDict` to allow
`number`-valued headers to support Node.js types.
Replace inline `instrumentServer` Proxy/emit-wrapping implementation in
node-light's HTTP integration with core's `getHttpServerSubscriptions`,
which does the same work (isolation scope, request data, body capture,
trace continuation, best-effort transaction name).

Centralized implementation lets Bun, Deno, Workers, and the OTel-based
Node SDK share server-side primitives.

Also drop `wrappedEmitFns` and the import of several core utilities that
are now consumed via the subscriptions.
…erIntegration

Replace inline `instrumentServer` Proxy/emit-wrapping in
`httpServerIntegration.ts` with core's `getHttpServerSubscriptions`.

OTel-specific concerns (header propagation, double-wrap context guard,
`_startSpanCallback` dispatch) move into a `wrapServerEmitRequest`
callback that `instrumentServer` invokes inside the per-request
lifecycle.

Re-export `recordRequestSession` from core so existing test continues
to pass.

Duplicated request-isolation/session/body-capture plumbing removed,
logic now lives in `@sentry/core`'s subscription factory.
`patchRequestToCaptureBody` lives in `@sentry/core` now and both the
light SDK and the OTel-based `httpServerIntegration` consume it from
there.
@isaacs isaacs force-pushed the isaacschlueter/portable-http-integration-server branch from f1ac205 to 2954cdc Compare May 9, 2026 20:39
@isaacs isaacs marked this pull request as ready for review May 9, 2026 21:06

// Ensure we also remove callbacks correctly
// eslint-disable-next-line @typescript-eslint/unbound-method
req.off = new Proxy(req.off, {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I think I've never ever used double assignment in JavaScript. That's pretty clever

Copy link
Copy Markdown
Member

@JPeer264 JPeer264 left a comment

Choose a reason for hiding this comment

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

Got couple of comments. Nice splitting.

try {
const chunk = args[0] as Buffer | string;
const bufferifiedChunk = Buffer.from(chunk);
const bufferifiedChunk = Buffer.from(chunk as string);
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

q: Is this type assertion needed?

const callbackMap = new WeakMap();

const maxBodySize = getMaxBodyByteLength(maxIncomingRequestBodySize);
const maxBodySize =
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

m: Is there any reason why the function got moved to the previous behavior? Maybe it wasn't rebased yet when it got implemented.

req: HttpIncomingMessage,
isolationScope: Scope,
maxIncomingRequestBodySize: Exclude<MaxRequestBodySize, 'none'>,
maxIncomingRequestBodySize: 'small' | 'medium' | 'always',
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

m: Here the reusable type got removed any specific reason? Also here, maybe it wasn't rebased yet when it got implemented.

await runner.completed();
});
},
{ failsOnEsm: true },
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Absolutely amazing that this works now 🥇

const name = `${method} ${httpTargetWithoutQueryFragment}`;
const headers = request.headers;
const userAgent = headers['user-agent'];
const ips = headers['x-forwarded-for'];
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

l/m: I know this code has been here in some form already, but is it guaranteed that the headers are always lower case? Given that with HTTP1 you could still send X-Forwarded-For


const INTEGRATION_NAME = 'Http.SentryServerSpans';

export function getHttpServerSpanSubscriptions(options: HttpInstrumentationOptions): HttpServerSubscriptions {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

q: How is this supposed to be used? It is exported but not used - so I wonder how this fits into the bigger picture. It also has no e2e/integration/unit tests, but would it would be nice to have some for this.

I also wonder if it would be added as one of our integrations, as it has Http.SentryServerSpans as integration name, but then it wouldn't match the other exported integration names. I'm slightly confused on this.

const timeout = setTimeout(() => {
DEBUG_BUILD && debug.log('Sending request session aggregate due to flushing schedule');
flushPendingClientAggregates();
}, sessionFlushingDelayMS).unref?.();
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

l: Since this line has been moved, you could change that to safeUnref from core, which takes case of that.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants