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

@s1gr1ds1gr1d 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 ownerJuly 9, 2026 13:52
@s1gr1d
s1gr1d requested review from JPeer264, andreiborza and mydea and removed request for a teamJuly 9, 2026 13:52
@github-actions

github-actionsBot commented Jul 9, 2026

Copy link
Copy Markdown
Contributor

size-limit report 📦

PathSize% ChangeChange
@sentry/browser27.72 kB--
@sentry/browser - with treeshaking flags26.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 flags75.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/react29.53 kB--
@sentry/react (incl. Tracing)48.79 kB--
@sentry/vue33.15 kB--
@sentry/vue (incl. Tracing)48.52 kB--
@sentry/svelte27.75 kB--
CDN Bundle30.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 - uncompressed89.78 kB--
CDN Bundle (incl. Tracing) - uncompressed146.59 kB--
CDN Bundle (incl. Logs, Metrics) - uncompressed94.48 kB--
CDN Bundle (incl. Tracing, Logs, Metrics) - uncompressed150.56 kB--
CDN Bundle (incl. Replay, Logs, Metrics) - uncompressed219.21 kB--
CDN Bundle (incl. Tracing, Replay) - uncompressed265.79 kB--
CDN Bundle (incl. Tracing, Replay, Logs, Metrics) - uncompressed269.75 kB--
CDN Bundle (incl. Tracing, Replay, Feedback) - uncompressed279.49 kB--
CDN Bundle (incl. Tracing, Replay, Feedback, Logs, Metrics) - uncompressed283.44 kB--
@sentry/nextjs (client)51.35 kB--
@sentry/sveltekit (client)46.97 kB--
@sentry/core/server78.6 kB--
@sentry/core/browser64.95 kB--
@sentry/node-core63.17 kB-0.01%-1 B 🔽
@sentry/node125.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/light51.3 kB--
@sentry/node - without tracing74.68 kB--
@sentry/aws-serverless83.9 kB--
@sentry/cloudflare (withSentry) - minified182.02 kB--
@sentry/cloudflare (withSentry)450.69 kB--

View base workflow run

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

@isaacsisaacs 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.)

* 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
MemberAuthor

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.

@s1gr1ds1gr1dJul 14, 2026

Copy link
Copy Markdown
MemberAuthor

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

Copy link
Copy Markdown
MemberAuthor

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 ownerJuly 15, 2026 12:28
@s1gr1d
s1gr1d requested review from JPeer264 and chargome and removed request for a team and chargomeJuly 15, 2026 12:28
@s1gr1d
s1gr1d requested a review from isaacsJuly 15, 2026 13:12

@isaacsisaacs 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.

* 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, 👍

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

Copy link
Copy Markdown
MemberAuthor

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 developJul 16, 2026
608 of 610 checks passed
@s1gr1d
s1gr1d deleted the sig/rewrite-koa-orchestrion branch July 16, 2026 11:26
Sign up for freeto 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

@s1gr1d@JPeer264@isaacs