Skip to content

feat(server-utils): Add @opentelemetry/instrumentation-koa orchestrion integration#22146

Merged
s1gr1d merged 11 commits into
developfrom
sig/rewrite-koa-orchestrion
Jul 16, 2026
Merged

feat(server-utils): Add @opentelemetry/instrumentation-koa orchestrion integration#22146
s1gr1d merged 11 commits into
developfrom
sig/rewrite-koa-orchestrion

Conversation

@s1gr1d

@s1gr1d s1gr1d commented Jul 9, 2026

Copy link
Copy Markdown
Member

Adds koaChannelIntegration in @sentry/server-utils for injecting orchestrion channels into koa.

A subscriber wraps each registered layer in a span-creating proxy.
Span-helpers are ported from the vendored instrumentation, preserving span names (but adapting to new conventions).

Also upgraded @apm-js-collab/tracing-hooks to get this: apm-js-collab/tracing-hooks#45 to be released (lets us actually patch koa - see 1. iteration below).

1. iteration

One thing to know for review: we instrument koa-compose, not use from koa. koa's use lives in koa's main entry (lib/application.js), and transforming a package's main entry forces its top-level require chain through Node's require(esm) bridge, which throws on Node < 24.13.

  1. Orchestrion instruments by rewriting a module's source at load time (via the ESM load hook).
  2. use lives in koa's main entry (lib/application.js), so to instrument it we transform that file. But transforming a main entry pulls its whole top-level require chain into the loader's handling --> and that changes how those requires are loaded (through the ESM→CommonJS translator, not the normal sync require path).
  3. koa is CJS (but support ESM). When importing koa, it loads a shim that loads the CJS code: import 'koa'dist/koa.mjs (a ESM shim) → import '../lib/application.js' (CJS). That CJS entry has a top-level require('is-generator-function').
  4. is-generator-functionrequire('generator-function'), and generator-function points at an .mjs file, which in turn imports ./index.js.
    --> So the top-level require in koa's application.js (CJS) becomes require(esm) of an ESM file importing a CJS file.
  5. On Node < 24.13 (we pin 20.19.5), the loader can't pre-link this dual-package shape into the require(esm) cache, so it throws request for './index.js' is not in cache.

The failing chain:

koa/lib/application.js (CJS)
  └─ require('is-generator-function')        (CJS)
       └─ require('generator-function')      → require(esm) → require.mjs (ESM)
            └─ import './index.js'            (ESM importing CJS)

koa-compose is koa's zero-dependency dispatch engine, so it's safe to transform, and compose(app.middleware) sees the same layers use would. Since @koa/router also calls compose per request, the subscriber uses getActiveSpan() to only wrap at app startup (no active span) and skip the per-request router composition.

Closes #20758

Linear: https://linear.app/getsentry/issue/JS-2409/rewrite-opentelemetryinstrumentation-koa-to-orchestrion

@s1gr1d
s1gr1d requested a review from a team as a code owner July 9, 2026 13:52
@s1gr1d
s1gr1d requested review from JPeer264, andreiborza and mydea and removed request for a team July 9, 2026 13:52
@github-actions

github-actions Bot commented Jul 9, 2026

Copy link
Copy Markdown
Contributor

size-limit report 📦

Path Size % Change Change
@sentry/browser 27.72 kB - -
@sentry/browser - with treeshaking flags 26.17 kB - -
@sentry/browser (incl. Tracing) 46.55 kB - -
@sentry/browser (incl. Tracing + Span Streaming) 48.33 kB - -
@sentry/browser (incl. Tracing, Profiling) 51.32 kB - -
@sentry/browser (incl. Tracing, Replay) 85.78 kB - -
@sentry/browser (incl. Tracing, Replay) - with treeshaking flags 75.42 kB - -
@sentry/browser (incl. Tracing, Replay with Canvas) 90.5 kB - -
@sentry/browser (incl. Tracing, Replay, Feedback) 103.15 kB - -
@sentry/browser (incl. Feedback) 44.9 kB - -
@sentry/browser (incl. sendFeedback) 32.52 kB - -
@sentry/browser (incl. FeedbackAsync) 37.65 kB - -
@sentry/browser (incl. Metrics) 28.81 kB - -
@sentry/browser (incl. Logs) 29.05 kB - -
@sentry/browser (incl. Metrics & Logs) 29.73 kB - -
@sentry/react 29.53 kB - -
@sentry/react (incl. Tracing) 48.79 kB - -
@sentry/vue 33.15 kB - -
@sentry/vue (incl. Tracing) 48.52 kB - -
@sentry/svelte 27.75 kB - -
CDN Bundle 30.12 kB - -
CDN Bundle (incl. Tracing) 48.51 kB - -
CDN Bundle (incl. Logs, Metrics) 31.7 kB - -
CDN Bundle (incl. Tracing, Logs, Metrics) 49.81 kB - -
CDN Bundle (incl. Replay, Logs, Metrics) 70.94 kB - -
CDN Bundle (incl. Tracing, Replay) 86 kB - -
CDN Bundle (incl. Tracing, Replay, Logs, Metrics) 87.31 kB - -
CDN Bundle (incl. Tracing, Replay, Feedback) 91.8 kB - -
CDN Bundle (incl. Tracing, Replay, Feedback, Logs, Metrics) 93.06 kB - -
CDN Bundle - uncompressed 89.78 kB - -
CDN Bundle (incl. Tracing) - uncompressed 146.59 kB - -
CDN Bundle (incl. Logs, Metrics) - uncompressed 94.48 kB - -
CDN Bundle (incl. Tracing, Logs, Metrics) - uncompressed 150.56 kB - -
CDN Bundle (incl. Replay, Logs, Metrics) - uncompressed 219.21 kB - -
CDN Bundle (incl. Tracing, Replay) - uncompressed 265.79 kB - -
CDN Bundle (incl. Tracing, Replay, Logs, Metrics) - uncompressed 269.75 kB - -
CDN Bundle (incl. Tracing, Replay, Feedback) - uncompressed 279.49 kB - -
CDN Bundle (incl. Tracing, Replay, Feedback, Logs, Metrics) - uncompressed 283.44 kB - -
@sentry/nextjs (client) 51.35 kB - -
@sentry/sveltekit (client) 46.97 kB - -
@sentry/core/server 78.6 kB - -
@sentry/core/browser 64.95 kB - -
@sentry/node-core 63.17 kB -0.01% -1 B 🔽
@sentry/node 125.44 kB +0.02% +24 B 🔺
@sentry/node (incl. diagnostics channel injection) 141.14 kB +0.35% +489 B 🔺
@sentry/node/import (ESM hook with diagnostics-channel injection) 70.03 kB +0.01% +2 B 🔺
@sentry/node/light 51.3 kB - -
@sentry/node - without tracing 74.68 kB - -
@sentry/aws-serverless 83.9 kB - -
@sentry/cloudflare (withSentry) - minified 182.02 kB - -
@sentry/cloudflare (withSentry) 450.69 kB - -

View base workflow run

@isaacs
isaacs self-requested a review July 10, 2026 18:19

@isaacs isaacs left a comment

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.

This looks good!

The main question is if the update to orchestrion can allow us to instrument koa's main export properly. Apart from that just some mechanical stuff and a few bikeshed nits. (Wire Deno's options through, and update test snapshots so the CI stops complaining.)

Comment thread packages/deno/src/integrations/koa.ts Outdated
* spans nest under the active HTTP server span.
*/
const _denoKoaIntegration = (() => {
const inner = koaChannelIntegration();

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: This loses the ignoreLayersType option. It should take the options and thread them through, like how we do in packages/deno/src/integrations/postgres.ts

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.

This is still not threading the options through, but tbh, maybe it doesn't matter (re discussion this morning in platform team weekly). This is still more tracing integration than Koa has in Deno today, and once #22306 gets implemented, we might rip this out anyway. So, bottom line, 👍

export const koaConfig = [
{
channelName: 'compose',
module: { name: 'koa-compose', versionRange: '>=4.0.0 <5', filePath: 'index.js' },

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.

The rationale in the PR description makes sense, and this is a good workaround to address it.

But I believe that the require(esm) issue will be addressed by @apm-js-collab/tracing-hooks's next release, once we land apm-js-collab/tracing-hooks#45

Is that accurate? If so, it might be worth backing this out, and patching the main export directly, if only to keep the versions aligned with the OTel instrumentation.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Yes, this PR would fix this! I think it's worth waiting for it and then patching the main export (it's more accurate)

// subscription here.
let subscribed = false;

let ignoreLayersType: KoaLayerType[] = [];

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.

nit: This is functionally fine, because of the subscriber/integration deduping. But we've done this sort of "integration-local" variables within a closure in eg pg/redis/ioredis.

I don't have a strong opinion about which is better, but it might be good to settle on one pattern. The closure approach seems like it might be slightly safer, because it's future-proofed against potentially relaxing the deduplication constraint, but it seems unlikely we'd ever do that, so this is very much 100% a bikeshed nit.

@s1gr1d s1gr1d Jul 14, 2026

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

yeah makes sense to settle on one pattern here (the closure approach).

};
}

function getMiddlewareMetadata(

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.

Rather than duplicate all the stuff from the vendored utils.ts, it'd be good to extract those out to a shared helpers.ts first, just to have one less thing to keep in sync.

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.

Hm, on further thought, I guess that somewhat is tricky because the utils.ts file is in a completely different package (sentry/node vs sentry/server-utils) 🤔

I'm not sure if it makes sense to have the node SDK's vendored otel koa instrumentation pull from server-utils to get its reusables. Feels like a stretch. Maybe this concern can be addressed with a comment just mentioning that the two files should be kept in sync as long as we're using the otel koa in node. They're unlikely to change very much anyway, I suppose.

@s1gr1d

s1gr1d commented Jul 14, 2026

Copy link
Copy Markdown
Member Author

I will come back to this PR once apm-js-collab/tracing-hooks#45 is released - this gives us the possibility to patch koa like we did before (not just koa-compose).

@JPeer264

Copy link
Copy Markdown
Member

@s1gr1d please readd me as reviewer once this is ready to review again 🥳

@JPeer264
JPeer264 removed their request for review July 14, 2026 09:29
s1gr1d added 4 commits July 15, 2026 13:43
# Conflicts:
#	packages/deno/src/index.ts
#	packages/server-utils/src/orchestrion/channels.ts
#	packages/server-utils/src/orchestrion/config/index.ts
#	yarn.lock
@s1gr1d
s1gr1d requested a review from a team as a code owner July 15, 2026 12:28
@s1gr1d
s1gr1d requested review from JPeer264 and chargome and removed request for a team and chargome July 15, 2026 12:28
@s1gr1d
s1gr1d requested a review from isaacs July 15, 2026 13:12

@isaacs isaacs left a comment

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.

LGTM! A few comments still, but nothing blocking imo.

channelName: 'use',
module: { name: 'koa', versionRange: '>=2.0.0 <4', filePath: 'lib/application.js' },
functionQuery: { className: 'Application', methodName: 'use', kind: 'Sync' },
},

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.

So much simpler! love it.

Comment thread packages/deno/src/integrations/koa.ts Outdated
* spans nest under the active HTTP server span.
*/
const _denoKoaIntegration = (() => {
const inner = koaChannelIntegration();

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.

This is still not threading the options through, but tbh, maybe it doesn't matter (re discussion this morning in platform team weekly). This is still more tracing integration than Koa has in Deno today, and once #22306 gets implemented, we might rip this out anyway. So, bottom line, 👍

Comment thread packages/server-utils/src/integrations/tracing-channel/koa.ts
s1gr1d added 3 commits July 16, 2026 10:57
# Conflicts:
#	packages/deno/src/index.ts
#	packages/server-utils/package.json
#	yarn.lock
@s1gr1d

s1gr1d commented Jul 16, 2026

Copy link
Copy Markdown
Member Author

This is still not threading the options through

That was an oversight! I added it before merging this.

@s1gr1d
s1gr1d enabled auto-merge (squash) July 16, 2026 09:59
@s1gr1d
s1gr1d merged commit da1645c into develop Jul 16, 2026
608 of 610 checks passed
@s1gr1d
s1gr1d deleted the sig/rewrite-koa-orchestrion branch July 16, 2026 11:26
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.

Rewrite @opentelemetry/instrumentation-koa to orchestrion

3 participants