From c5abf5fdc5d54aaf02c7b3a7c3acc735edf749fc Mon Sep 17 00:00:00 2001 From: s1gr1d <32902192+s1gr1d@users.noreply.github.com> Date: Thu, 9 Jul 2026 14:14:13 +0200 Subject: [PATCH 1/2] rewrite koa to orchestrion --- .../node-integration-tests/package.json | 2 + .../suites/tracing/koa/instrument.mjs | 9 + .../suites/tracing/koa/scenario.mjs | 36 +++ .../suites/tracing/koa/test.ts | 102 +++++++ packages/deno/src/index.ts | 1 + packages/deno/src/integrations/koa.ts | 28 ++ packages/deno/src/sdk.ts | 3 +- .../src/integrations/tracing-channel/koa.ts | 281 ++++++++++++++++++ .../server-utils/src/orchestrion/channels.ts | 2 + .../src/orchestrion/config/index.ts | 2 + .../src/orchestrion/config/koa.ts | 18 ++ .../server-utils/src/orchestrion/index.ts | 4 + yarn.lock | 166 +++++++++-- 13 files changed, 630 insertions(+), 24 deletions(-) create mode 100644 dev-packages/node-integration-tests/suites/tracing/koa/instrument.mjs create mode 100644 dev-packages/node-integration-tests/suites/tracing/koa/scenario.mjs create mode 100644 dev-packages/node-integration-tests/suites/tracing/koa/test.ts create mode 100644 packages/deno/src/integrations/koa.ts create mode 100644 packages/server-utils/src/integrations/tracing-channel/koa.ts create mode 100644 packages/server-utils/src/orchestrion/config/koa.ts diff --git a/dev-packages/node-integration-tests/package.json b/dev-packages/node-integration-tests/package.json index 9e8a5cd89c74..db9a8a04ea1f 100644 --- a/dev-packages/node-integration-tests/package.json +++ b/dev-packages/node-integration-tests/package.json @@ -40,6 +40,7 @@ "@growthbook/growthbook": "^1.6.5", "@hapi/hapi": "^21.3.10", "@hono/node-server": "^1.19.13", + "@koa/router": "^12.0.1", "@langchain/anthropic": "^0.3.10", "@langchain/core": "^0.3.80", "@langchain/openai": "^0.5.0", @@ -76,6 +77,7 @@ "ioredis-5": "npm:ioredis@^5.11.0", "kafkajs": "2.2.4", "knex": "^2.5.1", + "koa": "^2.15.2", "lru-memoizer": "2.3.0", "mongodb": "^3.7.3", "mongodb-memory-server-global": "^11.0.1", diff --git a/dev-packages/node-integration-tests/suites/tracing/koa/instrument.mjs b/dev-packages/node-integration-tests/suites/tracing/koa/instrument.mjs new file mode 100644 index 000000000000..46a27dd03b74 --- /dev/null +++ b/dev-packages/node-integration-tests/suites/tracing/koa/instrument.mjs @@ -0,0 +1,9 @@ +import * as Sentry from '@sentry/node'; +import { loggingTransport } from '@sentry-internal/node-integration-tests'; + +Sentry.init({ + dsn: 'https://public@dsn.ingest.sentry.io/1337', + release: '1.0', + tracesSampleRate: 1.0, + transport: loggingTransport, +}); diff --git a/dev-packages/node-integration-tests/suites/tracing/koa/scenario.mjs b/dev-packages/node-integration-tests/suites/tracing/koa/scenario.mjs new file mode 100644 index 000000000000..c1c0abc772cb --- /dev/null +++ b/dev-packages/node-integration-tests/suites/tracing/koa/scenario.mjs @@ -0,0 +1,36 @@ +import Router from '@koa/router'; +import * as Sentry from '@sentry/node'; +import { sendPortToRunner } from '@sentry-internal/node-integration-tests'; +import Koa from 'koa'; + +const port = 5698; + +const app = new Koa(); + +// Registered first so it wraps every downstream middleware/route in its try/catch. +Sentry.setupKoaErrorHandler(app); + +// Plain middleware -> produces a `middleware.koa` span named after the function. +app.use(async function simpleMiddleware(ctx, next) { + await next(); +}); + +const router = new Router(); + +router.get('/', ctx => { + ctx.body = 'Hello World!'; +}); + +router.get('/test-param/:id', ctx => { + ctx.body = { id: ctx.params.id }; +}); + +router.get('/error', () => { + throw new Error('Sentry Test Error'); +}); + +app.use(router.routes()).use(router.allowedMethods()); + +app.listen(port, () => { + sendPortToRunner(port); +}); diff --git a/dev-packages/node-integration-tests/suites/tracing/koa/test.ts b/dev-packages/node-integration-tests/suites/tracing/koa/test.ts new file mode 100644 index 000000000000..799fdcc6f0ba --- /dev/null +++ b/dev-packages/node-integration-tests/suites/tracing/koa/test.ts @@ -0,0 +1,102 @@ +import { afterAll, describe, expect } from 'vitest'; +import { isOrchestrionEnabled } from '../../../utils'; +import { cleanupChildProcesses, createEsmAndCjsTests } from '../../../utils/runner'; + +describe('koa auto-instrumentation', () => { + afterAll(async () => { + cleanupChildProcesses(); + }); + + // `createEsmAndCjsTests` auto-runs this suite with orchestrion on CI. The + // orchestrion path keeps span ops/attributes identical to the OTel path; only + // the origin differs to signal the injection mechanism, so we branch on + // `isOrchestrionEnabled()`. + const origin = isOrchestrionEnabled() ? 'auto.http.orchestrion.koa' : 'auto.http.otel.koa'; + + const EXPECTED_ERROR_EVENT = { + exception: { + values: [ + { + type: 'Error', + value: 'Sentry Test Error', + }, + ], + }, + }; + + createEsmAndCjsTests(__dirname, 'scenario.mjs', 'instrument.mjs', (createRunner, test) => { + test('should auto-instrument `koa` router and middleware layers.', async () => { + const runner = createRunner() + .expect({ + transaction: { + transaction: 'GET /', + spans: expect.arrayContaining([ + // Router layer span (from `@koa/router`), carrying the matched route. + expect.objectContaining({ + description: '/', + op: 'router.koa', + origin, + data: expect.objectContaining({ + 'http.route': '/', + 'koa.type': 'router', + 'koa.name': '/', + 'sentry.op': 'router.koa', + 'sentry.origin': origin, + }), + }), + // Plain middleware span. + expect.objectContaining({ + description: 'simpleMiddleware', + op: 'middleware.koa', + origin, + data: expect.objectContaining({ + 'koa.type': 'middleware', + 'koa.name': 'simpleMiddleware', + 'sentry.op': 'middleware.koa', + 'sentry.origin': origin, + }), + }), + ]), + }, + }) + .start(); + runner.makeRequest('get', '/'); + await runner.completed(); + }); + + test('should assign a parameterized transaction name.', async () => { + const runner = createRunner() + .expect({ + transaction: { + transaction: 'GET /test-param/:id', + spans: expect.arrayContaining([ + expect.objectContaining({ + description: '/test-param/:id', + op: 'router.koa', + origin, + data: expect.objectContaining({ + 'http.route': '/test-param/:id', + 'koa.type': 'router', + 'koa.name': '/test-param/:id', + 'sentry.op': 'router.koa', + 'sentry.origin': origin, + }), + }), + ]), + }, + }) + .start(); + runner.makeRequest('get', '/test-param/123'); + await runner.completed(); + }); + + test('should capture errors thrown in routes via the koa error handler.', async () => { + const runner = createRunner() + .expect({ transaction: { transaction: 'GET /error' } }) + .expect({ event: EXPECTED_ERROR_EVENT }) + .start(); + runner.makeRequest('get', '/error', { expectError: true }); + await runner.completed(); + }); + }); +}); diff --git a/packages/deno/src/index.ts b/packages/deno/src/index.ts index e87686c84d51..11bfcce2f360 100644 --- a/packages/deno/src/index.ts +++ b/packages/deno/src/index.ts @@ -114,6 +114,7 @@ export type { DenoRedisIntegrationOptions } from './integrations/redis'; export { denoMysqlIntegration } from './integrations/mysql'; export { denoPostgresIntegration } from './integrations/postgres'; export { denoAmqplibIntegration } from './integrations/amqplib'; +export { denoKoaIntegration } from './integrations/koa'; export { denoContextIntegration } from './integrations/context'; export { globalHandlersIntegration } from './integrations/globalhandlers'; export { normalizePathsIntegration } from './integrations/normalizepaths'; diff --git a/packages/deno/src/integrations/koa.ts b/packages/deno/src/integrations/koa.ts new file mode 100644 index 000000000000..62283226c3ff --- /dev/null +++ b/packages/deno/src/integrations/koa.ts @@ -0,0 +1,28 @@ +import { koaChannelIntegration } from '@sentry/server-utils/orchestrion'; +import type { Integration, IntegrationFn } from '@sentry/core'; +import { defineIntegration, extendIntegration } from '@sentry/core'; +import { setAsyncLocalStorageAsyncContextStrategy } from '../async'; + +const INTEGRATION_NAME = 'DenoKoa' as const; + +/** + * Create spans for `koa` middleware/router layers under Deno. Requires the + * `@sentry/deno/import` loader. Delegates to the shared subscriber in + * `@sentry/server-utils`, adding Deno's `AsyncLocalStorage` context strategy so + * spans nest under the active HTTP server span. + */ +const _denoKoaIntegration = (() => { + const inner = koaChannelIntegration(); + + return extendIntegration(inner, { + name: INTEGRATION_NAME, + setupOnce() { + setAsyncLocalStorageAsyncContextStrategy(); + }, + }); +}) satisfies IntegrationFn; + +export const denoKoaIntegration = defineIntegration(_denoKoaIntegration) as () => Integration & { + name: 'DenoKoa'; + setupOnce: () => void; +}; diff --git a/packages/deno/src/sdk.ts b/packages/deno/src/sdk.ts index f67b545966de..805b2ab077bf 100644 --- a/packages/deno/src/sdk.ts +++ b/packages/deno/src/sdk.ts @@ -24,6 +24,7 @@ import { import { denoServeIntegration } from './integrations/deno-serve'; import { denoHttpIntegration } from './integrations/http'; import { denoAmqplibIntegration } from './integrations/amqplib'; +import { denoKoaIntegration } from './integrations/koa'; import { denoMysqlIntegration } from './integrations/mysql'; import { denoPostgresIntegration } from './integrations/postgres'; import { denoRedisIntegration } from './integrations/redis'; @@ -62,7 +63,7 @@ export function getDefaultIntegrations(_options: Options): Integration[] { // (or in parallel to) loading the SDK, so we only gate on whether the // feature is possible. If they're never loaded, it'll just be a no-op. ...(MODULE_REGISTER_HOOKS_SUPPORTED - ? [denoMysqlIntegration(), denoPostgresIntegration(), denoAmqplibIntegration()] + ? [denoMysqlIntegration(), denoPostgresIntegration(), denoAmqplibIntegration(), denoKoaIntegration()] : []), contextLinesIntegration(), normalizePathsIntegration(), diff --git a/packages/server-utils/src/integrations/tracing-channel/koa.ts b/packages/server-utils/src/integrations/tracing-channel/koa.ts new file mode 100644 index 000000000000..40b4c49b0c05 --- /dev/null +++ b/packages/server-utils/src/integrations/tracing-channel/koa.ts @@ -0,0 +1,281 @@ +import * as diagnosticsChannel from 'node:diagnostics_channel'; +import type { IntegrationFn } from '@sentry/core'; +import { + debug, + defineIntegration, + getActiveSpan, + getDefaultIsolationScope, + getIsolationScope, + getRootSpan, + SEMANTIC_ATTRIBUTE_SENTRY_OP, + SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN, + spanToJSON, + startSpan, +} from '@sentry/core'; +import { HTTP_ROUTE } from '@sentry/conventions/attributes'; +import { DEBUG_BUILD } from '../../debug-build'; +import { CHANNELS } from '../../orchestrion/channels'; + +// Same name as the OTel integration. When enabled, the OTel 'Koa' integration is omitted from the default set. +const INTEGRATION_NAME = 'Koa' as const; + +const ORIGIN = 'auto.http.orchestrion.koa'; + +// koa-specific span attributes emitted by the vendored OTel instrumentation. +// These have no `@sentry/conventions` equivalent, so both the OTel and +// orchestrion paths emit the same set — only the origin differs between them. +// todo(v11) use exported attributes from semantic conventions +const ATTR_KOA_TYPE = 'koa.type'; +const ATTR_KOA_NAME = 'koa.name'; + +const LAYER_TYPE = { + ROUTER: 'router', + MIDDLEWARE: 'middleware', +} as const; +type KoaLayerType = (typeof LAYER_TYPE)[keyof typeof LAYER_TYPE]; + +// Keeps wrapping idempotent — the same middleware instance can be registered on +// multiple routes (mirrors the vendored OTel instrumentation's symbol). +const kLayerPatched: unique symbol = Symbol('sentry.koa.layer-patched'); + +// Core dedupes `setupOnce` by integration name, but the Deno SDK also runs this +// under the name `DenoKoa` (via `extendIntegration`), so guard against a second +// subscription here. +let subscribed = false; + +let ignoreLayersType: KoaLayerType[] = []; + +type Next = () => Promise; + +interface KoaContext { + [key: string]: unknown; + _matchedRoute?: string | RegExp; + _matchedRouteName?: string; + request?: { method?: string }; +} + +interface Layer { + path: string | RegExp; + stack: KoaMiddleware[]; +} + +interface Router { + stack: Layer[]; +} + +type KoaMiddleware = ((context: KoaContext, next: Next) => unknown) & { + router?: Router; + [kLayerPatched]?: boolean; +}; + +type KoaSpanAttributes = Record; + +interface KoaMiddlewareMetadata { + attributes: KoaSpanAttributes; + name: string; +} + +// `arguments[0]` is the live middleware array passed to `compose(middleware)`; +// we mutate its entries in place to swap each layer for a span-creating proxy. +interface KoaComposeContext { + arguments: unknown[]; +} + +export interface KoaChannelIntegrationOptions { + /** Ignore layers of the specified types (`'middleware'` and/or `'router'`). */ + ignoreLayersType?: Array<'middleware' | 'router'>; +} + +const _koaChannelIntegration = ((options: KoaChannelIntegrationOptions = {}) => { + return { + name: INTEGRATION_NAME, + setupOnce() { + // `tracingChannel` is unavailable before Node 18.19 so do nothing in that case. + if (!diagnosticsChannel.tracingChannel || subscribed) { + return; + } + subscribed = true; + ignoreLayersType = (options.ignoreLayersType ?? []) as KoaLayerType[]; + + DEBUG_BUILD && debug.log(`[orchestrion:koa] subscribing to channel "${CHANNELS.KOA_COMPOSE}"`); + + // `subscribe` requires all five lifecycle hooks. We only act on `start`, + // which orchestrion fires synchronously with the live middleware array. + diagnosticsChannel.tracingChannel(CHANNELS.KOA_COMPOSE).subscribe({ + start(rawCtx) { + handleCompose(rawCtx as KoaComposeContext); + }, + end() {}, + asyncStart() {}, + asyncEnd() {}, + error() {}, + }); + }, + }; +}) satisfies IntegrationFn; + +/** + * koa calls `compose(app.middleware)` once at startup (no active span); `@koa/router` + * calls it per request while the `http.server` span is active. We only wrap at startup + * — the route handlers are wrapped in place via the router-stack descent, so skipping + * the per-request call just avoids spanning the router's internal param-setters. + */ +function handleCompose(ctx: KoaComposeContext): void { + if (getActiveSpan()) { + return; + } + const middleware = ctx.arguments[0]; + if (!Array.isArray(middleware)) { + return; + } + middleware.forEach((layer, i) => { + if (typeof layer === 'function') { + middleware[i] = patchUse(layer as KoaMiddleware); + } + }); +} + +/** + * Wrap a registered koa middleware. A `@koa/router` dispatch layer + * (`router.routes()`) exposes a `.router`, in which case we patch each routed + * middleware in its stack (in place) and leave the dispatch itself unwrapped; + * everything else is a plain middleware. + */ +function patchUse(middleware: KoaMiddleware): KoaMiddleware { + return middleware.router ? patchRouterDispatch(middleware) : patchLayer(middleware, false); +} + +/** + * Patches the dispatch function used by `@koa/router`, wrapping each routed + * middleware in the router's stack so routed spans carry their matched path. + */ +function patchRouterDispatch(dispatchLayer: KoaMiddleware): KoaMiddleware { + const router = dispatchLayer.router; + const routesStack = router?.stack ?? []; + for (const pathLayer of routesStack) { + const path = pathLayer.path; + const pathStack = pathLayer.stack; + pathStack.forEach((routedMiddleware, j) => { + pathStack[j] = patchLayer(routedMiddleware, true, path); + }); + } + return dispatchLayer; +} + +/** + * Wraps an individual middleware layer so it opens a span when invoked. No span + * is created when there is no active (parent) span, matching the vendored OTel + * instrumentation's `api.trace.getSpan(api.context.active())` guard. + */ +function patchLayer(middlewareLayer: KoaMiddleware, isRouter: boolean, layerPath?: string | RegExp): KoaMiddleware { + const layerType = isRouter ? LAYER_TYPE.ROUTER : LAYER_TYPE.MIDDLEWARE; + // Skip patching the layer if it's ignored by config or already wrapped. + if (middlewareLayer[kLayerPatched] === true || isLayerIgnored(layerType)) { + return middlewareLayer; + } + + if ( + middlewareLayer.constructor.name === 'GeneratorFunction' || + middlewareLayer.constructor.name === 'AsyncGeneratorFunction' + ) { + return middlewareLayer; + } + + middlewareLayer[kLayerPatched] = true; + + return (context: KoaContext, next: Next) => { + if (!getActiveSpan()) { + return middlewareLayer(context, next); + } + const metadata = getMiddlewareMetadata(context, middlewareLayer, isRouter, layerPath); + + if (context._matchedRoute) { + setHttpServerSpanRouteAttribute(context._matchedRoute.toString()); + } + + const koaName = metadata.attributes[ATTR_KOA_NAME]; + // Somehow, name is sometimes `''` for middleware spans. + // See: https://github.com/open-telemetry/opentelemetry-js-contrib/issues/2220 + const name = typeof koaName === 'string' ? koaName || '< unknown >' : metadata.name; + + return startSpan( + { + name, + op: `${layerType}.koa`, + attributes: { + ...metadata.attributes, + [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: ORIGIN, + }, + }, + () => { + const route = metadata.attributes[HTTP_ROUTE]; + if (getIsolationScope() === getDefaultIsolationScope()) { + DEBUG_BUILD && debug.warn('Isolation scope is default isolation scope - skipping setting transactionName'); + } else if (route) { + const method = context.request?.method?.toUpperCase() || 'GET'; + getIsolationScope().setTransactionName(`${method} ${route}`); + } + return middlewareLayer(context, next); + }, + ); + }; +} + +function getMiddlewareMetadata( + context: KoaContext, + layer: KoaMiddleware, + isRouter: boolean, + layerPath?: string | RegExp, +): KoaMiddlewareMetadata { + if (isRouter) { + return { + attributes: { + [ATTR_KOA_NAME]: layerPath?.toString(), + [ATTR_KOA_TYPE]: LAYER_TYPE.ROUTER, + [HTTP_ROUTE]: layerPath?.toString(), + }, + name: context._matchedRouteName || `router - ${layerPath}`, + }; + } + return { + attributes: { + [ATTR_KOA_NAME]: layer.name || 'middleware', + [ATTR_KOA_TYPE]: LAYER_TYPE.MIDDLEWARE, + }, + name: `middleware - ${layer.name}`, + }; +} + +function isLayerIgnored(type: KoaLayerType): boolean { + return ignoreLayersType.includes(type); +} + +/** + * Set the `http.route` attribute on the root HTTP server span for the current trace. + * + * No-op when there is no active span, no root span, or the root span is not an + * `http.server` span — so this can be called unconditionally without risking + * attribute pollution on non-HTTP root spans. + */ +function setHttpServerSpanRouteAttribute(route: string): void { + const activeSpan = getActiveSpan(); + if (!activeSpan) { + return; + } + const rootSpan = getRootSpan(activeSpan); + if (!rootSpan) { + return; + } + if (spanToJSON(rootSpan).data[SEMANTIC_ATTRIBUTE_SENTRY_OP] !== 'http.server') { + return; + } + rootSpan.setAttribute(HTTP_ROUTE, route); +} + +/** + * EXPERIMENTAL — orchestrion-driven koa integration. Subscribes to the + * `orchestrion:koa-compose:compose` channel injected into `koa-compose` and + * wraps each registered middleware/router layer in a span-creating proxy. + * Requires the orchestrion runtime hook or bundler plugin. + */ +export const koaChannelIntegration = defineIntegration(_koaChannelIntegration); diff --git a/packages/server-utils/src/orchestrion/channels.ts b/packages/server-utils/src/orchestrion/channels.ts index 91964e9a809d..7c37723e6f04 100644 --- a/packages/server-utils/src/orchestrion/channels.ts +++ b/packages/server-utils/src/orchestrion/channels.ts @@ -9,6 +9,7 @@ import { googleGenAiChannels } from './config/google-genai'; import { vercelAiChannels } from './config/vercel-ai'; import { amqplibChannels } from './config/amqplib'; import { hapiChannels } from './config/hapi'; +import { koaChannels } from './config/koa'; import { redisChannels } from './config/redis'; import { expressChannels } from './config/express'; import { graphqlChannels } from './config/graphql'; @@ -38,6 +39,7 @@ export const CHANNELS = { ...vercelAiChannels, ...amqplibChannels, ...hapiChannels, + ...koaChannels, ...redisChannels, ...expressChannels, ...graphqlChannels, diff --git a/packages/server-utils/src/orchestrion/config/index.ts b/packages/server-utils/src/orchestrion/config/index.ts index fe35d317621e..0af002f4955c 100644 --- a/packages/server-utils/src/orchestrion/config/index.ts +++ b/packages/server-utils/src/orchestrion/config/index.ts @@ -10,6 +10,7 @@ import { googleGenAiConfig } from './google-genai'; import { vercelAiConfig } from './vercel-ai'; import { amqplibConfig } from './amqplib'; import { hapiConfig } from './hapi'; +import { koaConfig } from './koa'; import { redisConfig } from './redis'; import { expressConfig } from './express'; import { graphqlConfig } from './graphql'; @@ -25,6 +26,7 @@ export const SENTRY_INSTRUMENTATIONS: InstrumentationConfig[] = [ ...googleGenAiConfig, ...vercelAiConfig, ...hapiConfig, + ...koaConfig, ...amqplibConfig, ...redisConfig, ...expressConfig, diff --git a/packages/server-utils/src/orchestrion/config/koa.ts b/packages/server-utils/src/orchestrion/config/koa.ts new file mode 100644 index 000000000000..e81944facf1b --- /dev/null +++ b/packages/server-utils/src/orchestrion/config/koa.ts @@ -0,0 +1,18 @@ +import type { InstrumentationConfig } from '@apm-js-collab/code-transformer'; + +// We instrument `koa-compose` rather than koa's `use` (in koa's main entry +// `lib/application.js`): transforming a package's main entry forces its top-level +// `require` chain through Node's `require(esm)` bridge, which throws on Node < 24.13. +// `koa-compose` is zero-dependency and its `compose()` is the funnel koa's `Application.callback()` +// uses to build the middleware chain. +export const koaConfig = [ + { + channelName: 'compose', + module: { name: 'koa-compose', versionRange: '>=4.0.0 <5', filePath: 'index.js' }, + functionQuery: { functionName: 'compose', kind: 'Sync' }, + }, +] satisfies InstrumentationConfig[]; + +export const koaChannels = { + KOA_COMPOSE: 'orchestrion:koa-compose:compose', +} as const; diff --git a/packages/server-utils/src/orchestrion/index.ts b/packages/server-utils/src/orchestrion/index.ts index 29cfd4140213..6db81290116b 100644 --- a/packages/server-utils/src/orchestrion/index.ts +++ b/packages/server-utils/src/orchestrion/index.ts @@ -6,6 +6,7 @@ import { graphqlDiagnosticsChannelIntegration, } from '../integrations/tracing-channel/graphql'; import { hapiChannelIntegration } from '../integrations/tracing-channel/hapi'; +import { koaChannelIntegration } from '../integrations/tracing-channel/koa'; import { ioredisChannelIntegration } from '../integrations/tracing-channel/ioredis'; import { lruMemoizerChannelIntegration } from '../integrations/tracing-channel/lru-memoizer'; import { mysqlChannelIntegration } from '../integrations/tracing-channel/mysql'; @@ -22,6 +23,7 @@ export { googleGenAIChannelIntegration, graphqlChannelIntegration, hapiChannelIntegration, + koaChannelIntegration, ioredisChannelIntegration, lruMemoizerChannelIntegration, mysqlChannelIntegration, @@ -31,6 +33,7 @@ export { vercelAiChannelIntegration, expressChannelIntegration, }; +export type { KoaChannelIntegrationOptions } from '../integrations/tracing-channel/koa'; export type { IORedisChannelIntegrationOptions, IORedisResponseHook } from '../integrations/tracing-channel/ioredis'; export type { PostgresJsChannelIntegrationOptions } from '../integrations/tracing-channel/postgres-js'; export { redisChannelIntegration } from '../integrations/tracing-channel/redis'; @@ -64,6 +67,7 @@ export const channelIntegrations = { vercelAiIntegration: vercelAiChannelIntegration, amqplibIntegration: amqplibChannelIntegration, hapiIntegration: hapiChannelIntegration, + koaIntegration: koaChannelIntegration, expressIntegration: expressChannelIntegration, graphqlIntegration: graphqlDiagnosticsChannelIntegration, } as const; diff --git a/yarn.lock b/yarn.lock index 4913e2a20b85..93619eb13651 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5175,6 +5175,17 @@ resolved "https://registry.yarnpkg.com/@js-joda/core/-/core-5.7.0.tgz#526d437b07cbb41e28df34d487cbfccbe730185b" integrity sha512-WBu4ULVVxySLLzK1Ppq+OdfP+adRS4ntmDQT915rzDJ++i95gc2jZkM5B6LWEAwN3lGXpfie3yPABozdD3K3Vg== +"@koa/router@^12.0.1": + version "12.0.2" + resolved "https://registry.yarnpkg.com/@koa/router/-/router-12.0.2.tgz#286d51959ed611255faa944818a112e35567835a" + integrity sha512-sYcHglGKTxGF+hQ6x67xDfkE9o+NhVlRHBqq6gLywaMc6CojK/5vFZByphdonKinYlMLkEkacm+HEse9HzwgTA== + dependencies: + debug "^4.3.4" + http-errors "^2.0.0" + koa-compose "^4.1.0" + methods "^1.1.2" + path-to-regexp "^6.3.0" + "@kwsites/file-exists@^1.1.1": version "1.1.1" resolved "https://registry.yarnpkg.com/@kwsites/file-exists/-/file-exists-1.1.1.tgz#ad1efcac13e1987d8dbaf235ef3be5b0d96faa99" @@ -10582,6 +10593,14 @@ abstract-logging@^2.0.1: resolved "https://registry.yarnpkg.com/abstract-logging/-/abstract-logging-2.0.1.tgz#6b0c371df212db7129b57d2e7fcf282b8bf1c839" integrity sha512-2BjRTZxTPvheOvGbBslFSYOUkr+SjPtOnrLP33f+VIWLzezQpZcqVg7ja3L4dBXmzzgwT+a029jRx5PCi3JuiA== +accepts@^1.3.5, accepts@~1.3.4, accepts@~1.3.8: + version "1.3.8" + resolved "https://registry.yarnpkg.com/accepts/-/accepts-1.3.8.tgz#0bf0be125b67014adcb0b0921e62db7bffe16b2e" + integrity sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw== + dependencies: + mime-types "~2.1.34" + negotiator "0.6.3" + accepts@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/accepts/-/accepts-2.0.0.tgz#bbcf4ba5075467f3f2131eab3cffc73c2f5d7895" @@ -10590,14 +10609,6 @@ accepts@^2.0.0: mime-types "^3.0.0" negotiator "^1.0.0" -accepts@~1.3.4, accepts@~1.3.8: - version "1.3.8" - resolved "https://registry.yarnpkg.com/accepts/-/accepts-1.3.8.tgz#0bf0be125b67014adcb0b0921e62db7bffe16b2e" - integrity sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw== - dependencies: - mime-types "~2.1.34" - negotiator "0.6.3" - acorn-globals@^7.0.0: version "7.0.1" resolved "https://registry.yarnpkg.com/acorn-globals/-/acorn-globals-7.0.1.tgz#0dbf05c44fa7c94332914c02066d5beff62c40c3" @@ -12722,6 +12733,14 @@ cache-base@^1.0.1: union-value "^1.0.0" unset-value "^1.0.0" +cache-content-type@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/cache-content-type/-/cache-content-type-1.0.1.tgz#035cde2b08ee2129f4a8315ea8f00a00dba1453c" + integrity sha512-IKufZ1o4Ut42YUrZSo8+qnMTrFuKkvyoLXUywKz9GJ5BrhOFGhLdkx9sG4KAnVvbY6kEcSFjLQul+DVmBm2bgA== + dependencies: + mime-types "^2.1.18" + ylru "^1.2.0" + calculate-cache-key-for-tree@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/calculate-cache-key-for-tree/-/calculate-cache-key-for-tree-2.0.0.tgz#7ac57f149a4188eacb0a45b210689215d3fef8d6" @@ -13167,6 +13186,11 @@ cluster-key-slot@1.1.2, cluster-key-slot@^1.1.0: resolved "https://registry.yarnpkg.com/cluster-key-slot/-/cluster-key-slot-1.1.2.tgz#88ddaa46906e303b5de30d3153b7d9fe0a0c19ac" integrity sha512-RMr0FhtfXemyinomL4hrWcYJxmX6deFdCxpJzhDttxgO1+bcCnkk+9drydLVDmAMG7NE6aN/fl4F7ucU/90gAA== +co@^4.6.0: + version "4.6.0" + resolved "https://registry.yarnpkg.com/co/-/co-4.6.0.tgz#6ea6bdf3d853ae54ccb8e47bfa0bf3f9031fb184" + integrity sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ== + code-red@^1.0.3: version "1.0.4" resolved "https://registry.yarnpkg.com/code-red/-/code-red-1.0.4.tgz#59ba5c9d1d320a4ef795bc10a28bd42bfebe3e35" @@ -13513,14 +13537,14 @@ content-disposition@^1.0.0: resolved "https://registry.yarnpkg.com/content-disposition/-/content-disposition-1.0.1.tgz#a8b7bbeb2904befdfb6787e5c0c086959f605f9b" integrity sha512-oIXISMynqSqm241k6kcQ5UwttDILMK4BiurCfGEREw6+X9jkkpEe5T9FZaApyLGGOnFuyMWZpdolTXMtvEJ08Q== -content-disposition@~0.5.4: +content-disposition@~0.5.2, content-disposition@~0.5.4: version "0.5.4" resolved "https://registry.yarnpkg.com/content-disposition/-/content-disposition-0.5.4.tgz#8b82b4efac82512a02bb0b1dcec9d2c5e8eb5bfe" integrity sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ== dependencies: safe-buffer "5.2.1" -content-type@^1.0.5, content-type@~1.0.4, content-type@~1.0.5: +content-type@^1.0.4, content-type@^1.0.5, content-type@~1.0.4, content-type@~1.0.5: version "1.0.5" resolved "https://registry.yarnpkg.com/content-type/-/content-type-1.0.5.tgz#8b773162656d1d1086784c8f23a54ce6d73d7918" integrity sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA== @@ -13587,6 +13611,14 @@ cookie@^1.0.1, cookie@^1.0.2, cookie@^1.1.1: resolved "https://registry.yarnpkg.com/cookie/-/cookie-1.1.1.tgz#3bb9bdfc82369db9c2f69c93c9c3ceb310c88b3c" integrity sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ== +cookies@~0.9.0: + version "0.9.1" + resolved "https://registry.yarnpkg.com/cookies/-/cookies-0.9.1.tgz#3ffed6f60bb4fb5f146feeedba50acc418af67e3" + integrity sha512-TG2hpqe4ELx54QER/S3HQ9SRVnQnGBtKUz5bLQWtYAQ+o6GpgMs6sYUvaiJjVxb+UXwhRhAEP3m7LbsIZ77Hmw== + dependencies: + depd "~2.0.0" + keygrip "~1.1.0" + copy-anything@^2.0.1: version "2.0.6" resolved "https://registry.yarnpkg.com/copy-anything/-/copy-anything-2.0.6.tgz#092454ea9584a7b7ad5573062b2a87f5900fc480" @@ -14153,6 +14185,11 @@ deep-equal@^2.0.5: which-collection "^1.0.1" which-typed-array "^1.1.13" +deep-equal@~1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/deep-equal/-/deep-equal-1.0.1.tgz#f5d260292b660e084eff4cdbc9f08ad3247448b5" + integrity sha512-bHtC0iYvWhyaTzvV3CZgPeZQqCOBGyGsVV7v4eevpdkLHfiSrXUdBG+qAuSz4RI70sszvjQ1QSZ98An1yNwpSw== + deep-extend@^0.6.0: version "0.6.0" resolved "https://registry.yarnpkg.com/deep-extend/-/deep-extend-0.6.0.tgz#c4fa7c95404a17a9c3e8ca7e1537312b736330ac" @@ -14320,7 +14357,7 @@ destr@^2.0.3, destr@^2.0.5: resolved "https://registry.yarnpkg.com/destr/-/destr-2.0.5.tgz#7d112ff1b925fb8d2079fac5bdb4a90973b51fdb" integrity sha512-ugFTXCtDZunbzasqBxrK93Ik/DRYsO6S/fedkWEMKqt04xZ4csmnmwGDBAb07QWNaGMAmnTIemsYZCksjATwsA== -destroy@1.2.0, destroy@~1.2.0: +destroy@1.2.0, destroy@^1.0.4, destroy@~1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/destroy/-/destroy-1.2.0.tgz#4803735509ad8be552934c67df614f94e66fa015" integrity sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg== @@ -15333,16 +15370,16 @@ enabled@2.0.x: resolved "https://registry.yarnpkg.com/enabled/-/enabled-2.0.0.tgz#f9dd92ec2d6f4bbc0d5d1e64e21d61cd4665e7c2" integrity sha512-AKrN98kuwOzMIdAizXGI86UFBoo26CL21UM763y1h/GMSJ4/OHU9k2YlsmBpyScFo/wbLzWQJBMCW4+IO3/+OQ== +encodeurl@^1.0.2, encodeurl@~1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-1.0.2.tgz#ad3ff4c86ec2d029322f5a02c3a9a606c95b3f59" + integrity sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k= + encodeurl@^2.0.0, encodeurl@~2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-2.0.0.tgz#7b8ea898077d7e409d3ac45474ea38eaf0857a58" integrity sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg== -encodeurl@~1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-1.0.2.tgz#ad3ff4c86ec2d029322f5a02c3a9a606c95b3f59" - integrity sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k= - encoding@^0.1.13: version "0.1.13" resolved "https://registry.yarnpkg.com/encoding/-/encoding-0.1.13.tgz#56574afdd791f54a8e9b2785c0582a2d26210fa9" @@ -18638,6 +18675,14 @@ htmlparser2@^6.1.0: domutils "^2.5.2" entities "^2.0.0" +http-assert@^1.3.0: + version "1.5.0" + resolved "https://registry.yarnpkg.com/http-assert/-/http-assert-1.5.0.tgz#c389ccd87ac16ed2dfa6246fd73b926aa00e6b8f" + integrity sha512-uPpH7OKX4H25hBmU6G1jWNaqJGpTXxey+YOUizJUAgu0AjLUeC8D73hTrhvDS5D+GJN1DN1+hhc/eF/wpxtp0w== + dependencies: + deep-equal "~1.0.1" + http-errors "~1.8.0" + http-cache-semantics@^4.1.0, http-cache-semantics@^4.1.1: version "4.2.0" resolved "https://registry.yarnpkg.com/http-cache-semantics/-/http-cache-semantics-4.2.0.tgz#205f4db64f8562b76a4ff9235aa5279839a09dd5" @@ -18648,6 +18693,17 @@ http-deceiver@^1.2.7: resolved "https://registry.yarnpkg.com/http-deceiver/-/http-deceiver-1.2.7.tgz#fa7168944ab9a519d337cb0bec7284dc3e723d87" integrity sha1-+nFolEq5pRnTN8sL7HKE3D5yPYc= +http-errors@^1.6.3, http-errors@~1.8.0: + version "1.8.1" + resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-1.8.1.tgz#7c3f28577cbc8a207388455dbd62295ed07bd68c" + integrity sha512-Kpk9Sm7NmI+RHhnj6OIWDI1d6fIoFAtFt9RLaTMRlg/8w49juAStsrBgp0Dp4OdxdVbRIeKhtCUvoi/RuAhO4g== + dependencies: + depd "~1.1.2" + inherits "2.0.4" + setprototypeof "1.2.0" + statuses ">= 1.5.0 < 2" + toidentifier "1.0.1" + http-errors@^2.0.0, http-errors@^2.0.1, http-errors@~2.0.0, http-errors@~2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-2.0.1.tgz#36d2f65bc909c8790018dd36fb4d93da6caae06b" @@ -20156,6 +20212,13 @@ karma-source-map-support@1.4.0: dependencies: source-map-support "^0.5.5" +keygrip@~1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/keygrip/-/keygrip-1.1.0.tgz#871b1681d5e159c62a445b0c74b615e0917e7226" + integrity sha512-iYSchDJ+liQ8iwbSI2QqsQOvqv58eJCEanyJPJi+Khyu8smkcKSFUCbPwzFcL7YVtZ6eONjqRX/38caJ7QjRAQ== + dependencies: + tsscmp "1.0.6" + kind-of@^3.0.2, kind-of@^3.0.3, kind-of@^3.2.0: version "3.2.2" resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-3.2.2.tgz#31ea21a734bab9bbb0f32466d893aea51e4a3c64" @@ -20220,6 +20283,48 @@ knitwork@^1.2.0, knitwork@^1.3.0: resolved "https://registry.yarnpkg.com/knitwork/-/knitwork-1.3.0.tgz#4a0d0b0d45378cac909ee1117481392522bd08a4" integrity sha512-4LqMNoONzR43B1W0ek0fhXMsDNW/zxa1NdFAVMY+k28pgZLovR4G3PB5MrpTxCy1QaZCqNoiaKPr5w5qZHfSNw== +koa-compose@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/koa-compose/-/koa-compose-4.1.0.tgz#507306b9371901db41121c812e923d0d67d3e877" + integrity sha512-8ODW8TrDuMYvXRwra/Kh7/rJo9BtOfPc6qO8eAfC80CnCvSjSl0bkRM24X6/XBBEyj0v1nRUQ1LyOy3dbqOWXw== + +koa-convert@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/koa-convert/-/koa-convert-2.0.0.tgz#86a0c44d81d40551bae22fee6709904573eea4f5" + integrity sha512-asOvN6bFlSnxewce2e/DK3p4tltyfC4VM7ZwuTuepI7dEQVcvpyFuBcEARu1+Hxg8DIwytce2n7jrZtRlPrARA== + dependencies: + co "^4.6.0" + koa-compose "^4.1.0" + +koa@^2.15.2: + version "2.16.4" + resolved "https://registry.yarnpkg.com/koa/-/koa-2.16.4.tgz#303b996f5c3f2a3bb771c7db5e4303ee05f2265f" + integrity sha512-3An0GCLDSR34tsCO4H8Tef8Pp2ngtaZDAZnsWJYelqXUK5wyiHvGItgK/xcSkmHLSTn1Jcho1mRQs2ehRzvKKw== + dependencies: + accepts "^1.3.5" + cache-content-type "^1.0.0" + content-disposition "~0.5.2" + content-type "^1.0.4" + cookies "~0.9.0" + debug "^4.3.2" + delegates "^1.0.0" + depd "^2.0.0" + destroy "^1.0.4" + encodeurl "^1.0.2" + escape-html "^1.0.3" + fresh "~0.5.2" + http-assert "^1.3.0" + http-errors "^1.6.3" + is-generator-function "^1.0.7" + koa-compose "^4.1.0" + koa-convert "^2.0.0" + on-finished "^2.3.0" + only "~0.0.2" + parseurl "^1.3.2" + statuses "^1.5.0" + type-is "^1.6.16" + vary "^1.1.2" + kubernetes-types@^1.30.0: version "1.30.0" resolved "https://registry.yarnpkg.com/kubernetes-types/-/kubernetes-types-1.30.0.tgz#f686cacb08ffc5f7e89254899c2153c723420116" @@ -21315,7 +21420,7 @@ meriyah@^6.1.4: resolved "https://registry.yarnpkg.com/meriyah/-/meriyah-6.1.4.tgz#2d49a8934fbcd9205c20564579c3560d9b1e077b" integrity sha512-Sz8FzjzI0kN13GK/6MVEsVzMZEPvOhnmmI1lU5+/1cGOiK3QUahntrNNtdVeihrO7t9JpoH75iMNXg6R6uWflQ== -methods@~1.1.2: +methods@^1.1.2, methods@~1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/methods/-/methods-1.1.2.tgz#5529a4d67654134edcc5266656835b0f851afcee" integrity sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4= @@ -23333,7 +23438,7 @@ on-exit-leak-free@^2.1.0: resolved "https://registry.yarnpkg.com/on-exit-leak-free/-/on-exit-leak-free-2.1.2.tgz#fed195c9ebddb7d9e4c3842f93f281ac8dadd3b8" integrity sha512-0eJJY6hXLGf1udHwfNftBqH+g73EU4B504nZeKpz1sYRKafAghwxEJunB2O7rDZkL4PGfsMVnTXZ2EjibbqcsA== -on-finished@^2.4.1, on-finished@~2.4.1: +on-finished@^2.3.0, on-finished@^2.4.1, on-finished@~2.4.1: version "2.4.1" resolved "https://registry.yarnpkg.com/on-finished/-/on-finished-2.4.1.tgz#58c8c44116e54845ad57f14ab10b03533184ac3f" integrity sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg== @@ -23401,6 +23506,11 @@ oniguruma-to-es@^2.2.0: regex "^5.1.1" regex-recursion "^5.1.1" +only@~0.0.2: + version "0.0.2" + resolved "https://registry.yarnpkg.com/only/-/only-0.0.2.tgz#2afde84d03e50b9a8edc444e30610a70295edfb4" + integrity sha512-Fvw+Jemq5fjjyWz6CpKx6w9s7xxqo3+JCyM0WXWeCSOboZ8ABkyvP8ID4CZuChA/wxSx+XSJmdOm8rGVyJ1hdQ== + open@8.4.0: version "8.4.0" resolved "https://registry.yarnpkg.com/open/-/open-8.4.0.tgz#345321ae18f8138f82565a910fdc6b39e8c244f8" @@ -23944,7 +24054,7 @@ parse5@^7.0.0, parse5@^7.1.2: dependencies: entities "^4.4.0" -parseurl@^1.3.3, parseurl@~1.3.2, parseurl@~1.3.3: +parseurl@^1.3.2, parseurl@^1.3.3, parseurl@~1.3.2, parseurl@~1.3.3: version "1.3.3" resolved "https://registry.yarnpkg.com/parseurl/-/parseurl-1.3.3.tgz#9da19e7bee8d12dff0513ed5b76957793bc2e8d4" integrity sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ== @@ -24045,7 +24155,7 @@ path-to-regexp@3.3.0: resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-3.3.0.tgz#f7f31d32e8518c2660862b644414b6d5c63a611b" integrity sha512-qyCH421YQPS2WFDxDjftfc1ZR5WKQzVzqsp4n9M2kQhVOo/ByahFoUNJfl58kOcEGfQ//7weFTDhm+ss8Ecxgw== -path-to-regexp@6.3.0, path-to-regexp@^6.2.1: +path-to-regexp@6.3.0, path-to-regexp@^6.2.1, path-to-regexp@^6.3.0: version "6.3.0" resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-6.3.0.tgz#2b6a26a337737a8e1416f9272ed0766b1c0389f4" integrity sha512-Yhpw4T9C6hPpgPeA28us07OJeqZ5EzQTkbfwuhsUg0c237RomFoETJgmp2sa3F/41gfLE6G5cqcYwznmeEeOlQ== @@ -27878,7 +27988,7 @@ static-extend@^0.1.1: define-property "^0.2.5" object-copy "^0.1.0" -"statuses@>= 1.4.0 < 2", statuses@~1.5.0: +"statuses@>= 1.4.0 < 2", "statuses@>= 1.5.0 < 2", statuses@^1.5.0, statuses@~1.5.0: version "1.5.0" resolved "https://registry.yarnpkg.com/statuses/-/statuses-1.5.0.tgz#161c7dac177659fd9811f43771fa99381478628c" integrity sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow= @@ -28886,7 +28996,7 @@ toad-cache@^3.7.0: resolved "https://registry.yarnpkg.com/toad-cache/-/toad-cache-3.7.1.tgz#33441aab508e15a35fb5292c61ee3322c0853822" integrity sha512-5DXWzE4Vz7xNHsv+xQ+MGfJYyC78Aok3tEr0MNwHoRf7vZnga1mQXZ4/Nsodld4VR6Wd+VhfmqnNrsRJyYPfrQ== -toidentifier@~1.0.1: +toidentifier@1.0.1, toidentifier@~1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/toidentifier/-/toidentifier-1.0.1.tgz#3be34321a88a820ed1bd80dfaa33e479fbb8dd35" integrity sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA== @@ -29083,6 +29193,11 @@ tslib@^1.8.1, tslib@^1.9.0: resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00" integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg== +tsscmp@1.0.6: + version "1.0.6" + resolved "https://registry.yarnpkg.com/tsscmp/-/tsscmp-1.0.6.tgz#85b99583ac3589ec4bfef825b5000aa911d605eb" + integrity sha512-LxhtAkPDTkVCMQjt2h6eBVY28KCjikZqZfMcC15YBeNjkgUpdCfBu5HoiOTDu86v6smE8yOjyEktJ8hlbANHQA== + tsutils@^3.21.0: version "3.21.0" resolved "https://registry.yarnpkg.com/tsutils/-/tsutils-3.21.0.tgz#b48717d394cea6c1e096983eed58e9d61715b623" @@ -29161,7 +29276,7 @@ type-fest@^5.0.0: dependencies: tagged-tag "^1.0.0" -type-is@^1.6.18, type-is@~1.6.18: +type-is@^1.6.16, type-is@^1.6.18, type-is@~1.6.18: version "1.6.18" resolved "https://registry.yarnpkg.com/type-is/-/type-is-1.6.18.tgz#4e552cd05df09467dcbc4ef739de89f2cf37c131" integrity sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g== @@ -31212,6 +31327,11 @@ yauzl@^3.2.0: buffer-crc32 "~0.2.3" pend "~1.2.0" +ylru@^1.2.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/ylru/-/ylru-1.4.0.tgz#0cf0aa57e9c24f8a2cbde0cc1ca2c9592ac4e0f6" + integrity sha512-2OQsPNEmBCvXuFlIni/a+Rn+R2pHW9INm0BxXJ4hVDA8TirqMj+J/Rp9ItLatT/5pZqWwefVrTQcHpixsxnVlA== + yn@3.1.1: version "3.1.1" resolved "https://registry.yarnpkg.com/yn/-/yn-3.1.1.tgz#1e87401a09d767c1d5eab26a6e4c185182d2eb50" From 36ac08c977167522108765d2407d172f0b0f7e57 Mon Sep 17 00:00:00 2001 From: s1gr1d <32902192+s1gr1d@users.noreply.github.com> Date: Thu, 9 Jul 2026 14:24:12 +0200 Subject: [PATCH 2/2] update attributes --- .../suites/tracing/koa/test.ts | 1 + .../integrations/tracing/koa/vendored/utils.ts | 7 ++++--- .../src/integrations/tracing-channel/koa.ts | 15 ++++++++------- 3 files changed, 13 insertions(+), 10 deletions(-) diff --git a/dev-packages/node-integration-tests/suites/tracing/koa/test.ts b/dev-packages/node-integration-tests/suites/tracing/koa/test.ts index 799fdcc6f0ba..10d4d93afc68 100644 --- a/dev-packages/node-integration-tests/suites/tracing/koa/test.ts +++ b/dev-packages/node-integration-tests/suites/tracing/koa/test.ts @@ -52,6 +52,7 @@ describe('koa auto-instrumentation', () => { data: expect.objectContaining({ 'koa.type': 'middleware', 'koa.name': 'simpleMiddleware', + 'code.function.name': 'simpleMiddleware', 'sentry.op': 'middleware.koa', 'sentry.origin': origin, }), diff --git a/packages/node/src/integrations/tracing/koa/vendored/utils.ts b/packages/node/src/integrations/tracing/koa/vendored/utils.ts index 1c9d9a8067a9..070aa7eb965d 100644 --- a/packages/node/src/integrations/tracing/koa/vendored/utils.ts +++ b/packages/node/src/integrations/tracing/koa/vendored/utils.ts @@ -11,7 +11,7 @@ import { KoaLayerType, type KoaInstrumentationConfig } from './types'; import type { KoaContext, KoaMiddleware } from './internal-types'; import { AttributeNames } from './enums/AttributeNames'; import type { Attributes } from '@opentelemetry/api'; -import { HTTP_ROUTE } from '@sentry/conventions/attributes'; +import { CODE_FUNCTION_NAME, HTTP_ROUTE } from '@sentry/conventions/attributes'; export const getMiddlewareMetadata = ( context: KoaContext, @@ -25,7 +25,7 @@ export const getMiddlewareMetadata = ( if (isRouter) { return { attributes: { - [AttributeNames.KOA_NAME]: layerPath?.toString(), + [AttributeNames.KOA_NAME]: layerPath?.toString(), // TODO(v11): remove, replaced by http.route [AttributeNames.KOA_TYPE]: KoaLayerType.ROUTER, [HTTP_ROUTE]: layerPath?.toString(), }, @@ -34,8 +34,9 @@ export const getMiddlewareMetadata = ( } else { return { attributes: { - [AttributeNames.KOA_NAME]: layer.name ?? 'middleware', + [AttributeNames.KOA_NAME]: layer.name ?? 'middleware', // TODO(v11): remove, replaced by code.function.name [AttributeNames.KOA_TYPE]: KoaLayerType.MIDDLEWARE, + [CODE_FUNCTION_NAME]: layer.name ?? 'middleware', }, name: `middleware - ${layer.name}`, }; diff --git a/packages/server-utils/src/integrations/tracing-channel/koa.ts b/packages/server-utils/src/integrations/tracing-channel/koa.ts index 40b4c49b0c05..f131f98edae7 100644 --- a/packages/server-utils/src/integrations/tracing-channel/koa.ts +++ b/packages/server-utils/src/integrations/tracing-channel/koa.ts @@ -12,7 +12,7 @@ import { spanToJSON, startSpan, } from '@sentry/core'; -import { HTTP_ROUTE } from '@sentry/conventions/attributes'; +import { CODE_FUNCTION_NAME, HTTP_ROUTE } from '@sentry/conventions/attributes'; import { DEBUG_BUILD } from '../../debug-build'; import { CHANNELS } from '../../orchestrion/channels'; @@ -21,11 +21,11 @@ const INTEGRATION_NAME = 'Koa' as const; const ORIGIN = 'auto.http.orchestrion.koa'; -// koa-specific span attributes emitted by the vendored OTel instrumentation. -// These have no `@sentry/conventions` equivalent, so both the OTel and -// orchestrion paths emit the same set — only the origin differs between them. -// todo(v11) use exported attributes from semantic conventions +// `koa.type` (a layer's role) has no `@sentry/conventions` equivalent, so it stays +// the canonical attribute — kept in sync with the OTel koa integration so spans are +// identical across both code paths. const ATTR_KOA_TYPE = 'koa.type'; +// TODO(v11): remove this attribute. const ATTR_KOA_NAME = 'koa.name'; const LAYER_TYPE = { @@ -230,7 +230,7 @@ function getMiddlewareMetadata( if (isRouter) { return { attributes: { - [ATTR_KOA_NAME]: layerPath?.toString(), + [ATTR_KOA_NAME]: layerPath?.toString(), // TODO(v11): remove, replaced by http.route [ATTR_KOA_TYPE]: LAYER_TYPE.ROUTER, [HTTP_ROUTE]: layerPath?.toString(), }, @@ -239,8 +239,9 @@ function getMiddlewareMetadata( } return { attributes: { - [ATTR_KOA_NAME]: layer.name || 'middleware', + [ATTR_KOA_NAME]: layer.name || 'middleware', // TODO(v11): remove, replaced by code.function.name [ATTR_KOA_TYPE]: LAYER_TYPE.MIDDLEWARE, + [CODE_FUNCTION_NAME]: layer.name || 'middleware', }, name: `middleware - ${layer.name}`, };