Skip to content

feat(hono): Auto-instrument Hono via @sentry/server-utils + orchestrion - #24371

Draft
mydea wants to merge 34 commits into
developfrom
feat/hono-server-utils-integration
Draft

mydea wants to merge 34 commits into
developfrom
feat/hono-server-utils-integration

Conversation

@mydea

@mydea mydea commented Sep 14, 2026

Copy link
Copy Markdown
Member

Summary

Moves the Hono instrumentation into @sentry/server-utils and drives it through orchestrion, so Hono is auto-instrumented out of the box by @sentry/node, @sentry/bun, @sentry/deno, and @sentry/cloudflare — no @sentry/hono import and no manual sentry() middleware required.

@sentry/hono keeps working exactly as before: it now re-uses the shared code from @sentry/server-utils instead of owning its own copy.

How it works

  • The runtime-agnostic Hono instrumentation (route/middleware span patches, request/response handlers, transaction naming, context-error capture) moved from @sentry/hono/src/shared to @sentry/server-utils/src/integrations/hono.
  • A new honoIntegration (default in the server SDKs' getErrorIntegrations()) hooks the Hono constructor via an orchestrion diagnostics channel (orchestrion:hono:honoConstructor) and, on every new app, registers the Sentry request/response middleware first and applies the span patches.
  • @sentry/server-utils takes no dependency on hono — not at runtime (the Hono prototype is derived from the live app instance; matched routes are read from the request's own getters instead of hono/route) and not at build/type time (the needed types are vendored in honoTypes.ts).

Per-runtime enablement

Duplicate handling

Request handling is deduplicated per request via the isolation scope, so all of these run the Sentry logic exactly once:

  • a manual sentry() middleware alongside the auto-instrumentation,
  • mounted sub-apps that carry their own auto-registered middleware,
  • internal app.request() dispatches (a new Hono context, same isolation scope).

A user-provided shouldHandleError still wins even when its middleware is deduplicated behind the auto-instrumentation.

Tests

  • Unit (@sentry/server-utils): isMiddleware, defaultShouldHandleError, and the dedup logic in createHonoMiddleware.
  • node-integration-tests (suites/hono): auto-instrumentation with plain @sentry/node (ESM + CJS) — transaction naming, error capture/mechanism, plus regression cases for mounted sub-apps (no stray <anonymous> middleware span) and internal .request() (no span rename / request-data pollution).
  • node-mastra: asserts the http.server span (method/route/url) for Mastra's Hono-served agent endpoint.
  • e2e: new native hono-4 app exercising node/bun/deno/cloudflare via the runtime SDKs directly; the previous @sentry/hono-based app is preserved as hono-4-legacy.
  • e2e runner: added per-variant skip support (getTestMatrix.mjs + run.ts).

Known limitations / follow-ups

  • Deno auto-instrumentation is currently blocked upstream. On Deno's sync Module.registerHooks path, @apm-js-collab/tracing-hooks derives the module type only from result.format, which Deno doesn't report as 'module' for Hono's ESM build — so a CJS require() is injected into an ES module (ReferenceError: require is not defined). Filed as Sync hooks path injects CommonJS require() into ESM modules on Deno → ReferenceError: require is not defined apm-js-collab/tracing-hooks#53. The hono-4 deno variant is marked skip until it lands. (Node works because it uses the async Module.register path, which reports the format correctly.)
  • @sentry/hono remains as-is (thin re-export layer); no deprecation for now.

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Stale Bugbot comment from a previous run.

Comment thread packages/server-utils/src/integrations/hono/applyPatches.ts
Comment thread packages/server-utils/src/integrations/hono/index.ts
Comment thread packages/server-utils/src/integrations/index.ts
Comment thread packages/server-utils/src/integrations/hono/createHonoMiddleware.ts Outdated
@github-actions

github-actions Bot commented Sep 14, 2026

Copy link
Copy Markdown
Contributor

size-limit report 📦

Path Size % Change Change
@sentry/browser 29.09 kB - -
@sentry/browser - with treeshaking flags 27.35 kB - -
@sentry/browser - with treeshaking flags tracing without tracing 27.26 kB - -
@sentry/browser (incl. Tracing) 50.6 kB - -
@sentry/browser (incl. Tracing + Span Streaming) 50.62 kB - -
@sentry/browser (incl. Tracing, Profiling) 53.61 kB - -
@sentry/browser (incl. Tracing, Replay) 90.15 kB - -
@sentry/browser (incl. Tracing, Replay) - with treeshaking flags 79.25 kB - -
@sentry/browser (incl. Tracing, Replay with Canvas) 94.85 kB - -
@sentry/browser (incl. Tracing, Replay, Feedback) 107.83 kB - -
@sentry/browser (incl. Feedback) 46.62 kB - -
@sentry/browser (incl. sendFeedback) 34.15 kB - -
@sentry/browser (incl. FeedbackAsync) 39.26 kB - -
@sentry/browser (incl. Metrics) 30.1 kB - -
@sentry/browser (incl. Logs) 30.35 kB - -
@sentry/browser (incl. Metrics & Logs) 31.02 kB - -
@sentry/react 30.84 kB - -
@sentry/react (incl. Tracing) 52.94 kB - -
@sentry/vue 36.34 kB - -
@sentry/vue (incl. Tracing) 52.91 kB - -
@sentry/svelte 29.11 kB - -
CDN Bundle 30.8 kB - -
CDN Bundle (incl. Tracing) 51.15 kB - -
CDN Bundle (incl. Logs, Metrics) 33.06 kB - -
CDN Bundle (incl. Tracing, Logs, Metrics) 53.14 kB - -
CDN Bundle (incl. Replay, Logs, Metrics) 73.75 kB - -
CDN Bundle (incl. Tracing, Replay) 88.69 kB - -
CDN Bundle (incl. Tracing, Replay, Logs, Metrics) 90.63 kB - -
CDN Bundle (incl. Tracing, Replay, Feedback) 94.73 kB - -
CDN Bundle (incl. Tracing, Replay, Feedback, Logs, Metrics) 96.78 kB - -
CDN Bundle - uncompressed 91.16 kB - -
CDN Bundle (incl. Tracing) - uncompressed 152.66 kB - -
CDN Bundle (incl. Logs, Metrics) - uncompressed 97.73 kB - -
CDN Bundle (incl. Tracing, Logs, Metrics) - uncompressed 158.61 kB - -
CDN Bundle (incl. Replay, Logs, Metrics) - uncompressed 227.14 kB - -
CDN Bundle (incl. Tracing, Replay) - uncompressed 272.23 kB - -
CDN Bundle (incl. Tracing, Replay, Logs, Metrics) - uncompressed 278.17 kB - -
CDN Bundle (incl. Tracing, Replay, Feedback) - uncompressed 285.93 kB - -
CDN Bundle (incl. Tracing, Replay, Feedback, Logs, Metrics) - uncompressed 291.86 kB - -
@sentry/nextjs (client) 55.27 kB - -
@sentry/sveltekit (client) 51.05 kB - -
@sentry/core/server 39.63 kB - -
@sentry/core/browser 13.66 kB - -
@sentry/node 134.07 kB +1.3% +1.72 kB 🔺
@sentry/node/import (ESM hook with diagnostics-channel injection) 82.3 kB +0.33% +270 B 🔺
@sentry/node - without tracing 91.47 kB +1.88% +1.69 kB 🔺
@sentry/node - without channel injection 112.81 kB +1.44% +1.6 kB 🔺
@sentry/aws-serverless 99.72 kB +1.72% +1.69 kB 🔺
@sentry/cloudflare (withSentry) - minified 204.52 kB - -
@sentry/cloudflare (withSentry) 508.73 kB - -

View base workflow run

@mydea
mydea force-pushed the feat/hono-server-utils-integration branch from 7b5f046 to 1e9f2bd Compare September 14, 2026 14:40

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Stale Bugbot comment from a previous run.

Comment thread dev-packages/e2e-tests/test-applications/hono-4/package.json Outdated
Comment thread dev-packages/e2e-tests/test-applications/hono-4/package.json Outdated
Comment thread packages/server-utils/src/integrations/hono/honoIntegration.ts Outdated

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Stale Bugbot comment from a previous run.

Comment thread .github/workflows/build.yml Outdated
Comment thread packages/server-utils/src/integrations/hono/patchAppRequest.ts
@mydea mydea changed the title WIP hono integration feat(hono): Auto-instrument Hono via @sentry/server-utils + orchestrion Sep 15, 2026
@mydea
mydea force-pushed the feat/hono-server-utils-integration branch from 2b018b4 to 5af594d Compare September 15, 2026 07:54

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Stale Bugbot comment from a previous run.

Comment thread dev-packages/e2e-tests/test-applications/hono-4/package.json Outdated
@mydea
mydea force-pushed the feat/hono-server-utils-integration branch from 0541fb0 to 68fb5f5 Compare September 16, 2026 07:46

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Stale Bugbot comment from a previous run.

Comment thread dev-packages/e2e-tests/test-applications/hono-4/package.json Outdated
Comment thread packages/server-utils/src/integrations/hono/wrapMiddlewareSpan.ts

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Stale Bugbot comment from a previous run.

Comment thread packages/server-utils/src/orchestrion/config/index.ts
@mydea
mydea force-pushed the feat/hono-server-utils-integration branch from dcfe28e to 43202bf Compare September 16, 2026 14:44

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Stale Bugbot comment from a previous run.

Comment thread dev-packages/e2e-tests/test-applications/hono-4/package.json
mydea and others added 18 commits September 17, 2026 11:19
Deno's synchronous module loader (`Module.registerHooks`) reports
`format: undefined` for the modules we instrument, so tracing-hooks treated
them as CommonJS and injected `require()` into ESM (`require is not defined`).
Infer the module format from the source for instrumented targets when the
runtime leaves it unset (no-op on Node, which always sets `format`).

The hono-4 e2e app also constructed its sub-apps at module eval before
`Sentry.init()` ran, so `honoIntegration` subscribed to the constructor channel
too late to instrument them (their internal `.request()` calls produced no
span). Move init into a dedicated `instrument.{bun,deno}.ts` imported first,
matching the Node `--import` ordering.

Finally, define the shared middleware as anonymous function expressions: a named
expression whose name equals its binding gets renamed by Bun's bundler
(`middlewareA` -> `middlewareA2`), which leaked into the middleware span name.

deno variant is un-skipped; cloudflare stays skipped pending a workerd-safe
instrumentation path (diagnostics_channel publish/runStores are disallowed at
module scope on workerd).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RaYGdTstWVZKU9X4NjtYV1
…are)

Exposes `honoMiddleware(app)` from `@sentry/node`, `@sentry/bun`, `@sentry/deno`
and `@sentry/cloudflare`: it applies the same Hono span patches and returns the
same request/response middleware the automatic `honoIntegration` uses, but as a
manual, config- and init-free API (`app.use(honoMiddleware(app))`). Sentry must
still be initialized separately.

This is the supported path on Cloudflare Workers, where automatic
constructor-channel instrumentation cannot run: the app is built at module scope,
and on workerd `diagnostics_channel`'s `hasSubscribers` is a method (always
truthy) rather than a boolean getter, so the transform's "no subscribers"
fast-path never triggers and its publish/`runStores` calls execute at module
scope — which workerd forbids, crashing the worker at boot. Accordingly:

- the Cloudflare vite plugin now excludes `hono` from the orchestrion transform
  (new `excludeModules` option), so `new Hono()` stays untransformed and the
  worker boots; users instrument via `honoMiddleware` instead;
- the Cloudflare-only eager constructor-channel arm is removed from
  `honoIntegration` (it relied on the same disallowed module-scope publishing).

Node/Bun/Deno keep the automatic constructor-channel instrumentation unchanged.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RaYGdTstWVZKU9X4NjtYV1
…eno format workaround

`@apm-js-collab/tracing-hooks` 0.13.1 fixes the runtime hook to infer a module's
type from its source when the host loader leaves `format` unset (its new
`unlabeledModuleType`). That is exactly what our register.ts workaround did for
Deno's synchronous loader, which reported `format: undefined` and caused
`ReferenceError: require is not defined` when a CJS wrapper was injected into an
ES module.

With the fix upstream, remove `loadWithInferredFormat`/`isInstrumentedTarget`
and register the plain `{ resolve, load }` hooks again. (code-transformer and
code-transformer-bundler-plugins are already at their latest, 0.18.1 / 0.7.4.)

Verified: the hono-4 Deno e2e no longer crashes and instruments correctly. The
one remaining Deno failure (`http.request.header.*` as a string vs array) is an
unrelated pre-existing issue from the header-array change (#24231) not yet
covering Deno's `winterCGHeadersToDict` path; fixed on develop.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RaYGdTstWVZKU9X4NjtYV1
…le-cloud-serverless

These SDKs already re-export the server-side integration surface (incl.
`honoIntegration`); add `honoMiddleware` alongside so users of those SDKs can
manually instrument a Hono app where automatic constructor-channel
instrumentation can't run.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RaYGdTstWVZKU9X4NjtYV1
Bun runs on JavaScriptCore, which rejects orchestrion's default constructor
instrumentation for DERIVED classes (e.g. `class Hono extends HonoBase`): it
reads `this` in a `finally` that does not lexically enclose the nested
`super()` call, and JSC statically rejects that with

  ReferenceError: 'super()' must be called in derived constructor before
  accessing |this|

...crashing `new Hono()` at boot. V8 (Node/Deno/workerd) accepts it via a
runtime this-binding check.

Add a `traceSync` override, wired into the Bun bundler plugin only, that runs
the built-in transform and then—for derived constructors only—relocates the
`__apm$ctx.self ??= this` capture out of the `finally` into `__apm$traced`
(after `__apm$wrapped(...)` returns, a scope that encloses `super()`). Every
other node is left byte-identical, and only Bun (JSC) gets the override, so
cloudflare/vite, esbuild/webpack and the Node/Deno runtime keep the default
output. Guarded: if the transformer's generated shape changes, it no-ops and
warns rather than silently reintroducing the crash.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RaYGdTstWVZKU9X4NjtYV1
@mydea
mydea force-pushed the feat/hono-server-utils-integration branch from 43202bf to b4a5995 Compare September 17, 2026 09:24

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Stale Bugbot comment from a previous run.

Comment thread packages/server-utils/src/integrations/hono/honoIntegration.ts
Comment thread packages/server-utils/src/integrations/hono/honoIntegration.ts

@cursor cursor Bot left a comment

Copy link
Copy Markdown

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 1 potential issue.

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 b823241. Configure here.

return /^https?:\/\//.test(input) ? new URL(input).pathname : input;
// `app.request()` accepts absolute URLs as well as relative paths. Parse both
// against a dummy base so the query string is stripped from the span name.
return new URL(input, 'http://sentry-internal').pathname;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Pathname parse can abort requests

Low Severity

extractPathname now runs every string through new URL against a dummy base, including relative Hono paths. That constructor throws on some inputs Hono accepts (such as // or http:), and the Proxy calls it before Reflect.apply, so instrumentation can abort app.request. Protocol-relative strings are also parsed as absolute URLs, so the span name can drop the host segment Hono actually requests.

Additional Locations (1)
Fix in Cursor Fix in Web

Triggered by project rule: PR Review Guidelines for Cursor Bot

Reviewed by Cursor Bugbot for commit b823241. Configure here.

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.

1 participant