From 678fbe9031dce2b781c423a8d39ba7092ea63aa5 Mon Sep 17 00:00:00 2001 From: Nicolas Hrubec Date: Tue, 19 May 2026 09:12:27 +0200 Subject: [PATCH 1/4] ref(nestjs): Vendor nestjs-core instrumentation Closes https://github.com/getsentry/sentry-javascript/issues/20515 Co-Authored-By: Claude Opus 4.6 (1M context) --- .oxlintrc.base.json | 3 +- packages/nestjs/package.json | 1 - packages/nestjs/src/integrations/nest.ts | 2 +- .../vendored/enums/AttributeNames.ts | 31 +++ .../integrations/vendored/enums/NestType.ts | 26 ++ .../src/integrations/vendored/enums/index.ts | 23 ++ .../integrations/vendored/instrumentation.ts | 255 ++++++++++++++++++ .../src/integrations/vendored/semconv.ts | 50 ++++ yarn.lock | 8 - 9 files changed, 388 insertions(+), 11 deletions(-) create mode 100644 packages/nestjs/src/integrations/vendored/enums/AttributeNames.ts create mode 100644 packages/nestjs/src/integrations/vendored/enums/NestType.ts create mode 100644 packages/nestjs/src/integrations/vendored/enums/index.ts create mode 100644 packages/nestjs/src/integrations/vendored/instrumentation.ts create mode 100644 packages/nestjs/src/integrations/vendored/semconv.ts diff --git a/.oxlintrc.base.json b/.oxlintrc.base.json index 7a6834cec9f7..fe0b59853aeb 100644 --- a/.oxlintrc.base.json +++ b/.oxlintrc.base.json @@ -146,7 +146,8 @@ "**/integrations/tracing/genericPool/vendored/**/*.ts", "**/integrations/fs/vendored/**/*.ts", "**/integrations/tracing/knex/vendored/**/*.ts", - "**/integrations/tracing/mongo/vendored/**/*.ts" + "**/integrations/tracing/mongo/vendored/**/*.ts", + "**/nestjs/src/integrations/vendored/**/*.ts" ], "rules": { "typescript/no-explicit-any": "off" diff --git a/packages/nestjs/package.json b/packages/nestjs/package.json index 432acc2b89ed..b996a8c56b28 100644 --- a/packages/nestjs/package.json +++ b/packages/nestjs/package.json @@ -47,7 +47,6 @@ "@opentelemetry/api": "^1.9.1", "@opentelemetry/core": "^2.6.1", "@opentelemetry/instrumentation": "^0.214.0", - "@opentelemetry/instrumentation-nestjs-core": "0.60.0", "@opentelemetry/semantic-conventions": "^1.40.0", "@sentry/core": "10.53.1", "@sentry/node": "10.53.1" diff --git a/packages/nestjs/src/integrations/nest.ts b/packages/nestjs/src/integrations/nest.ts index 330c76f319cb..ec351f66348b 100644 --- a/packages/nestjs/src/integrations/nest.ts +++ b/packages/nestjs/src/integrations/nest.ts @@ -1,4 +1,4 @@ -import { NestInstrumentation as NestInstrumentationCore } from '@opentelemetry/instrumentation-nestjs-core'; +import { NestInstrumentation as NestInstrumentationCore } from './vendored/instrumentation'; import { defineIntegration } from '@sentry/core'; import { generateInstrumentOnce } from '@sentry/node'; import { SentryNestBullMQInstrumentation } from './sentry-nest-bullmq-instrumentation'; diff --git a/packages/nestjs/src/integrations/vendored/enums/AttributeNames.ts b/packages/nestjs/src/integrations/vendored/enums/AttributeNames.ts new file mode 100644 index 000000000000..c9b0b04a839e --- /dev/null +++ b/packages/nestjs/src/integrations/vendored/enums/AttributeNames.ts @@ -0,0 +1,31 @@ +/* + * Copyright The OpenTelemetry Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * NOTICE from the Sentry authors: + * - Vendored from: https://github.com/open-telemetry/opentelemetry-js-contrib/tree/15ef7506553f631ea4181391e0c5725a56f0d082/packages/instrumentation-nestjs-core + * - Upstream version: @opentelemetry/instrumentation-nestjs-core@0.64.0 + */ +/* eslint-disable */ + +export enum AttributeNames { + VERSION = 'nestjs.version', + TYPE = 'nestjs.type', + MODULE = 'nestjs.module', + CONTROLLER = 'nestjs.controller', + CALLBACK = 'nestjs.callback', + PIPES = 'nestjs.pipes', + INTERCEPTORS = 'nestjs.interceptors', + GUARDS = 'nestjs.guards', +} diff --git a/packages/nestjs/src/integrations/vendored/enums/NestType.ts b/packages/nestjs/src/integrations/vendored/enums/NestType.ts new file mode 100644 index 000000000000..1a1dc69c8e4c --- /dev/null +++ b/packages/nestjs/src/integrations/vendored/enums/NestType.ts @@ -0,0 +1,26 @@ +/* + * Copyright The OpenTelemetry Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * NOTICE from the Sentry authors: + * - Vendored from: https://github.com/open-telemetry/opentelemetry-js-contrib/tree/15ef7506553f631ea4181391e0c5725a56f0d082/packages/instrumentation-nestjs-core + * - Upstream version: @opentelemetry/instrumentation-nestjs-core@0.64.0 + */ +/* eslint-disable */ + +export enum NestType { + APP_CREATION = 'app_creation', + REQUEST_CONTEXT = 'request_context', + REQUEST_HANDLER = 'handler', +} diff --git a/packages/nestjs/src/integrations/vendored/enums/index.ts b/packages/nestjs/src/integrations/vendored/enums/index.ts new file mode 100644 index 000000000000..18970ae86ba2 --- /dev/null +++ b/packages/nestjs/src/integrations/vendored/enums/index.ts @@ -0,0 +1,23 @@ +/* + * Copyright The OpenTelemetry Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * NOTICE from the Sentry authors: + * - Vendored from: https://github.com/open-telemetry/opentelemetry-js-contrib/tree/15ef7506553f631ea4181391e0c5725a56f0d082/packages/instrumentation-nestjs-core + * - Upstream version: @opentelemetry/instrumentation-nestjs-core@0.64.0 + */ +/* eslint-disable */ + +export { AttributeNames } from './AttributeNames'; +export { NestType } from './NestType'; diff --git a/packages/nestjs/src/integrations/vendored/instrumentation.ts b/packages/nestjs/src/integrations/vendored/instrumentation.ts new file mode 100644 index 000000000000..72314db64f40 --- /dev/null +++ b/packages/nestjs/src/integrations/vendored/instrumentation.ts @@ -0,0 +1,255 @@ +/* + * Copyright The OpenTelemetry Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * NOTICE from the Sentry authors: + * - Vendored from: https://github.com/open-telemetry/opentelemetry-js-contrib/tree/15ef7506553f631ea4181391e0c5725a56f0d082/packages/instrumentation-nestjs-core + * - Upstream version: @opentelemetry/instrumentation-nestjs-core@0.64.0 + * - Some types vendored from @nestjs/core and @nestjs/common with simplifications + */ +/* eslint-disable */ + +import * as api from '@opentelemetry/api'; +import { + InstrumentationBase, + InstrumentationConfig, + InstrumentationNodeModuleDefinition, + InstrumentationNodeModuleFile, + isWrapped, + SemconvStability, + semconvStabilityFromStr, +} from '@opentelemetry/instrumentation'; +import { ATTR_HTTP_REQUEST_METHOD, ATTR_HTTP_ROUTE, ATTR_URL_FULL } from '@opentelemetry/semantic-conventions'; +import { SDK_VERSION } from '@sentry/core'; +import { ATTR_HTTP_METHOD, ATTR_HTTP_URL } from './semconv'; +import { AttributeNames, NestType } from './enums'; + +const PACKAGE_NAME = '@sentry/instrumentation-nestjs-core'; + +// Simplified types inlined from @nestjs/core and @nestjs/common to avoid requiring these packages. +// Controller: exact copy from @nestjs/common/interfaces/controllers/controller.interface.d.ts +type Controller = object; + +// NestFactory: simplified from @nestjs/core/nest-factory.d.ts — only the create method is used. +// Declared as a const (matching the original export shape) so `typeof NestFactory` works. +declare const NestFactory: { + create(...args: any[]): Promise; +}; + +// RouterExecutionContext: simplified from @nestjs/core/router/router-execution-context.d.ts — only the create method is used. +interface RouterExecutionContext { + create(instance: Controller, callback: (...args: any[]) => unknown, ...args: any[]): any; +} + +// Reflect metadata API (provided by reflect-metadata at runtime, used by NestJS) +declare namespace Reflect { + function getMetadataKeys(target: any): any[]; + function getMetadata(metadataKey: any, target: any): any; + function defineMetadata(metadataKey: any, metadataValue: any, target: any): void; +} + +const supportedVersions = ['>=4.0.0 <12']; + +export class NestInstrumentation extends InstrumentationBase { + static readonly COMPONENT = '@nestjs/core'; + static readonly COMMON_ATTRIBUTES = { + component: NestInstrumentation.COMPONENT, + }; + + private _semconvStability: SemconvStability; + + constructor(config: InstrumentationConfig = {}) { + super(PACKAGE_NAME, SDK_VERSION, config); + this._semconvStability = semconvStabilityFromStr('http', process.env.OTEL_SEMCONV_STABILITY_OPT_IN); + } + + init() { + const module = new InstrumentationNodeModuleDefinition(NestInstrumentation.COMPONENT, supportedVersions); + + module.files.push( + this.getNestFactoryFileInstrumentation(supportedVersions), + this.getRouterExecutionContextFileInstrumentation(supportedVersions), + ); + + return module; + } + + getNestFactoryFileInstrumentation(versions: string[]) { + return new InstrumentationNodeModuleFile( + '@nestjs/core/nest-factory.js', + versions, + (NestFactoryStatic: any, moduleVersion?: string) => { + this.ensureWrapped( + NestFactoryStatic.NestFactoryStatic.prototype, + 'create', + createWrapNestFactoryCreate(this.tracer, moduleVersion), + ); + return NestFactoryStatic; + }, + (NestFactoryStatic: any) => { + this._unwrap(NestFactoryStatic.NestFactoryStatic.prototype, 'create'); + }, + ); + } + + getRouterExecutionContextFileInstrumentation(versions: string[]) { + return new InstrumentationNodeModuleFile( + '@nestjs/core/router/router-execution-context.js', + versions, + (RouterExecutionContext: any, moduleVersion?: string) => { + this.ensureWrapped( + RouterExecutionContext.RouterExecutionContext.prototype, + 'create', + createWrapCreateHandler(this.tracer, moduleVersion, this._semconvStability), + ); + return RouterExecutionContext; + }, + (RouterExecutionContext: any) => { + this._unwrap(RouterExecutionContext.RouterExecutionContext.prototype, 'create'); + }, + ); + } + + private ensureWrapped(obj: any, methodName: string, wrapper: (original: any) => any) { + if (isWrapped(obj[methodName])) { + this._unwrap(obj, methodName); + } + this._wrap(obj, methodName, wrapper); + } +} + +function createWrapNestFactoryCreate(tracer: api.Tracer, moduleVersion?: string) { + return function wrapCreate(original: typeof NestFactory.create) { + return function createWithTrace( + this: typeof NestFactory, + nestModule: any, + /* serverOrOptions */ + ) { + const span = tracer.startSpan('Create Nest App', { + attributes: { + ...NestInstrumentation.COMMON_ATTRIBUTES, + [AttributeNames.TYPE]: NestType.APP_CREATION, + [AttributeNames.VERSION]: moduleVersion, + [AttributeNames.MODULE]: nestModule.name, + }, + }); + const spanContext = api.trace.setSpan(api.context.active(), span); + + return api.context.with(spanContext, async () => { + try { + return await original.apply(this, arguments as any); + } catch (e: any) { + throw addError(span, e); + } finally { + span.end(); + } + }); + }; + }; +} + +function createWrapCreateHandler( + tracer: api.Tracer, + moduleVersion: string | undefined, + semconvStability: SemconvStability, +) { + return function wrapCreateHandler(original: RouterExecutionContext['create']) { + return function createHandlerWithTrace( + this: RouterExecutionContext, + instance: Controller, + callback: (...args: any[]) => unknown, + ) { + arguments[1] = createWrapHandler(tracer, moduleVersion, callback); + const handler = original.apply(this, arguments as any); + const callbackName = callback.name; + const instanceName = + instance.constructor && instance.constructor.name ? instance.constructor.name : 'UnnamedInstance'; + const spanName = callbackName ? `${instanceName}.${callbackName}` : instanceName; + + return function (this: any, req: any, res: any, next: (...args: any[]) => unknown) { + const attributes: api.Attributes = { + ...NestInstrumentation.COMMON_ATTRIBUTES, + [AttributeNames.VERSION]: moduleVersion, + [AttributeNames.TYPE]: NestType.REQUEST_CONTEXT, + [ATTR_HTTP_ROUTE]: req.route?.path || req.routeOptions?.url || req.routerPath, + [AttributeNames.CONTROLLER]: instanceName, + [AttributeNames.CALLBACK]: callbackName, + }; + if (semconvStability & SemconvStability.OLD) { + attributes[ATTR_HTTP_METHOD] = req.method; + attributes[ATTR_HTTP_URL] = req.originalUrl || req.url; + } + if (semconvStability & SemconvStability.STABLE) { + attributes[ATTR_HTTP_REQUEST_METHOD] = req.method; + attributes[ATTR_URL_FULL] = req.originalUrl || req.url; + } + const span = tracer.startSpan(spanName, { attributes }); + const spanContext = api.trace.setSpan(api.context.active(), span); + + return api.context.with(spanContext, async () => { + try { + return await handler.apply(this, arguments as any); + } catch (e: any) { + throw addError(span, e); + } finally { + span.end(); + } + }); + }; + }; + }; +} + +function createWrapHandler(tracer: api.Tracer, moduleVersion: string | undefined, handler: Function) { + const spanName = handler.name || 'anonymous nest handler'; + const options = { + attributes: { + ...NestInstrumentation.COMMON_ATTRIBUTES, + [AttributeNames.VERSION]: moduleVersion, + [AttributeNames.TYPE]: NestType.REQUEST_HANDLER, + [AttributeNames.CALLBACK]: handler.name, + }, + }; + const wrappedHandler = function (this: RouterExecutionContext) { + const span = tracer.startSpan(spanName, options); + const spanContext = api.trace.setSpan(api.context.active(), span); + + return api.context.with(spanContext, async () => { + try { + return await handler.apply(this, arguments); + } catch (e: any) { + throw addError(span, e); + } finally { + span.end(); + } + }); + }; + + if (handler.name) { + Object.defineProperty(wrappedHandler, 'name', { value: handler.name }); + } + + // Get the current metadata and set onto the wrapper to ensure other decorators ( ie: NestJS EventPattern / RolesGuard ) + // won't be affected by the use of this instrumentation + Reflect.getMetadataKeys(handler).forEach(metadataKey => { + Reflect.defineMetadata(metadataKey, Reflect.getMetadata(metadataKey, handler), wrappedHandler); + }); + return wrappedHandler; +} + +const addError = (span: api.Span, error: Error) => { + span.recordException(error); + span.setStatus({ code: api.SpanStatusCode.ERROR, message: error.message }); + return error; +}; diff --git a/packages/nestjs/src/integrations/vendored/semconv.ts b/packages/nestjs/src/integrations/vendored/semconv.ts new file mode 100644 index 000000000000..52d15d084d0b --- /dev/null +++ b/packages/nestjs/src/integrations/vendored/semconv.ts @@ -0,0 +1,50 @@ +/* + * Copyright The OpenTelemetry Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * NOTICE from the Sentry authors: + * - Vendored from: https://github.com/open-telemetry/opentelemetry-js-contrib/tree/15ef7506553f631ea4181391e0c5725a56f0d082/packages/instrumentation-nestjs-core + * - Upstream version: @opentelemetry/instrumentation-nestjs-core@0.64.0 + */ +/* eslint-disable */ + +/* + * This file contains a copy of unstable semantic convention definitions + * used by this package. + * @see https://github.com/open-telemetry/opentelemetry-js/tree/main/semantic-conventions#unstable-semconv + */ + +/** + * Deprecated, use `http.request.method` instead. + * + * @example GET + * @example POST + * @example HEAD + * + * @experimental This attribute is experimental and is subject to breaking changes in minor releases of `@opentelemetry/semantic-conventions`. + * + * @deprecated Replaced by `http.request.method`. + */ +export const ATTR_HTTP_METHOD = 'http.method' as const; + +/** + * Deprecated, use `url.full` instead. + * + * @example https://www.foo.bar/search?q=OpenTelemetry#SemConv + * + * @experimental This attribute is experimental and is subject to breaking changes in minor releases of `@opentelemetry/semantic-conventions`. + * + * @deprecated Replaced by `url.full`. + */ +export const ATTR_HTTP_URL = 'http.url' as const; diff --git a/yarn.lock b/yarn.lock index 6bf617d827c9..6f3c48f2818b 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6156,14 +6156,6 @@ "@opentelemetry/semantic-conventions" "^1.33.0" "@types/mysql" "2.15.27" -"@opentelemetry/instrumentation-nestjs-core@0.60.0": - version "0.60.0" - resolved "https://registry.yarnpkg.com/@opentelemetry/instrumentation-nestjs-core/-/instrumentation-nestjs-core-0.60.0.tgz#60a34a8a3af7e3ab4cd7e46c783c99ff2430f2fb" - integrity sha512-BZqFAoD+frnwjpb0/T4kEEQMhl2YykZch4n2MMLKAVTzTehTBBV2hZxvFF629ipS+WOGBKjCjz1dycU9QNIckQ== - dependencies: - "@opentelemetry/instrumentation" "^0.214.0" - "@opentelemetry/semantic-conventions" "^1.30.0" - "@opentelemetry/instrumentation-pg@0.66.0": version "0.66.0" resolved "https://registry.yarnpkg.com/@opentelemetry/instrumentation-pg/-/instrumentation-pg-0.66.0.tgz#78d16b50dc4c5d851015823611a46243d63a88fb" From 999fef819ec4ccb5b6bb677a3fcdcd7e71d83616 Mon Sep 17 00:00:00 2001 From: Nicolas Hrubec Date: Tue, 19 May 2026 11:36:37 +0200 Subject: [PATCH 2/4] ref: Remove verbose inline type comments Co-Authored-By: Claude Opus 4.6 (1M context) --- .../nestjs/src/integrations/vendored/instrumentation.ts | 6 ------ 1 file changed, 6 deletions(-) diff --git a/packages/nestjs/src/integrations/vendored/instrumentation.ts b/packages/nestjs/src/integrations/vendored/instrumentation.ts index 72314db64f40..1dc6f08809fd 100644 --- a/packages/nestjs/src/integrations/vendored/instrumentation.ts +++ b/packages/nestjs/src/integrations/vendored/instrumentation.ts @@ -37,22 +37,16 @@ import { AttributeNames, NestType } from './enums'; const PACKAGE_NAME = '@sentry/instrumentation-nestjs-core'; -// Simplified types inlined from @nestjs/core and @nestjs/common to avoid requiring these packages. -// Controller: exact copy from @nestjs/common/interfaces/controllers/controller.interface.d.ts type Controller = object; -// NestFactory: simplified from @nestjs/core/nest-factory.d.ts — only the create method is used. -// Declared as a const (matching the original export shape) so `typeof NestFactory` works. declare const NestFactory: { create(...args: any[]): Promise; }; -// RouterExecutionContext: simplified from @nestjs/core/router/router-execution-context.d.ts — only the create method is used. interface RouterExecutionContext { create(instance: Controller, callback: (...args: any[]) => unknown, ...args: any[]): any; } -// Reflect metadata API (provided by reflect-metadata at runtime, used by NestJS) declare namespace Reflect { function getMetadataKeys(target: any): any[]; function getMetadata(metadataKey: any, target: any): any; From 8f81a74dc1fead767982d958feb085876042b5af Mon Sep 17 00:00:00 2001 From: Nicolas Hrubec Date: Tue, 19 May 2026 15:17:24 +0200 Subject: [PATCH 3/4] chore: Add vendor-otel skill for OTel instrumentation vendoring Co-Authored-By: Claude Opus 4.6 (1M context) --- .agents/skills/vendor-otel/SKILL.md | 168 ++++++++++++++++++++++++++++ 1 file changed, 168 insertions(+) create mode 100644 .agents/skills/vendor-otel/SKILL.md diff --git a/.agents/skills/vendor-otel/SKILL.md b/.agents/skills/vendor-otel/SKILL.md new file mode 100644 index 000000000000..a83710bc2a24 --- /dev/null +++ b/.agents/skills/vendor-otel/SKILL.md @@ -0,0 +1,168 @@ +--- +name: vendor-otel +description: Vendor an OpenTelemetry instrumentation package into the Sentry JavaScript SDK. Use when vendoring, inlining, or copying an @opentelemetry/instrumentation-* package (or similar like @prisma/instrumentation, @fastify/otel) into the SDK source. Also use when the user says "vendor", "inline instrumentation", or references the Vendor OpenTelemetry Instrumentation project. +--- + +# Vendor OTel Instrumentation + +Copy upstream OTel instrumentation TypeScript source into a `vendored/` directory, remove the npm dependency, and ensure builds and tests pass. No logic changes — the vendored code must behave identically to the original. + +## 1. Research + +### Find upstream source +```bash +gh api "repos/open-telemetry/opentelemetry-js-contrib/git/trees/main?recursive=1" --jq '.tree[].path' | grep "instrumentation-/src/.*\.ts$" +``` + +### Check versions +- Pinned version: `grep "instrumentation-" packages/node/package.json` +- Latest tag: `gh api repos/open-telemetry/opentelemetry-js-contrib/git/refs/tags --jq '.[].ref' | grep "instrumentation-"` +- Get commit SHA: `gh api repos/open-telemetry/opentelemetry-js-contrib/git/refs/tags/instrumentation--v --jq '.object.sha'` + +### Verify no breaking changes between pinned and latest +**This is critical.** All OTel instrumentations are pre-v1 so any bump could break things. Diff ALL source files: +```bash +diff <(gh api ".../src/?ref=instrumentation--v" --jq '.content' | base64 -d) \ + <(gh api ".../src/?ref=instrumentation--v" --jq '.content' | base64 -d) +``` +In practice most bumps have zero code changes (only license headers). If there ARE changes, evaluate safety and report to the user before proceeding. + +### Check external type imports +```bash +grep "import.*from '" | grep -v "@opentelemetry\|'\./\|@sentry\|'util'\|'path'\|'fs'\|'http'\|'events'" +``` +External type imports need special handling (see section 4). + +### Check test coverage +- Integration tests: `dev-packages/node-integration-tests/suites/tracing//` +- E2E tests: `dev-packages/e2e-tests/test-applications/node-/` +- Unit tests: `packages/node/test/integrations/tracing/.test.ts` + +Report any gaps to the user (e.g., "no integration test exists for this instrumentation" or "tests exist but don't cover X functionality"). + +## 2. Directory Structure + +Move the integration file into a directory: +- `packages/node/src/integrations/tracing/.ts` → `packages/node/src/integrations/tracing//index.ts` +- For non-tracing integrations (like `fs.ts`): `packages/node/src/integrations//index.ts` +- For non-node packages (aws-serverless, nestjs): follow their existing structure +- Create `vendored/` subdirectory for upstream files + +Import paths in barrel exports (`index.ts`, `tracing/index.ts`) resolve to the directory's `index.ts` automatically — usually no changes needed. + +## 3. Vendoring Source Files + +Fetch original TypeScript from the OTel contrib repo (NOT compiled JS from node_modules): +```bash +gh api "repos/open-telemetry/opentelemetry-js-contrib/contents/?ref=" --jq '.content' | base64 -d +``` + +**Be careful with header stripping** — `sed '1,Nd'` to remove the SPDX header can accidentally strip import lines. Always verify all imports are present after stripping. + +Each vendored file gets this header: +``` +/* + * Copyright The OpenTelemetry Authors + * ...full Apache 2.0 license text... + * + * NOTICE from the Sentry authors: + * - Vendored from: https://github.com/open-telemetry/opentelemetry-js-contrib/tree//packages/instrumentation- + * - Upstream version: @opentelemetry/instrumentation-@ + */ +/* eslint-disable */ +``` + +Add additional bullets to the NOTICE only when applicable: +- `* - Minor TypeScript strictness adjustments for this repository's compiler settings` — when TS changes were made +- `* - Some types vendored from with simplifications` — when types were inlined + +Standard replacements in the main instrumentation file: +- Remove `import { PACKAGE_NAME, PACKAGE_VERSION } from './version'` and the `/** @knipignore */` comment above it +- Add `import { SDK_VERSION } from '@sentry/core'` +- Add `const PACKAGE_NAME = '@sentry/instrumentation-';` +- Replace `PACKAGE_VERSION` with `SDK_VERSION` in the `super()` call + +Don't forget barrel exports (`enums/index.ts`, etc.) if the upstream has them. + +## 4. External Type Handling + +Types from external packages can leak into `.d.ts` output and break consumers. + +**Decision tree:** +1. Check if types appear in public class signatures. TypeScript strips private method signatures from `.d.ts`, so private-only usage won't leak. +2. Check if the upstream `types.ts` already vendors some types inline. +3. If types leak or are needed for compilation, **inline simplified types**: + - Put in a separate `-types.ts` file in vendored/ + - Keep as close to originals as possible — same generic parameters, field names, types + - Only simplify when the full type tree is too deep + - Add `[key: string]: any` index signatures for permissiveness + - Check if package ships own types or uses DefinitelyTyped + +4. After building, verify no leaks: `grep "from ''" packages/node/build/types/...` + +## 5. TypeScript Strictness Adjustments + +The Sentry repo has `strict: true` and `noUncheckedIndexedAccess: true`. Common fixes: +- `` angle-bracket assertions → `as any` (sucrase/esbuild doesn't support angle-bracket syntax) +- Implicit `any` in `.then()`, `.catch()`, `.forEach()`, `.map()` callbacks → add `: any` +- Array indexing `T | undefined` → add `!` non-null assertion or default values +- `ConstructorParameters` constraint failures → replace with `any[]` + +Add the TS strictness bullet to the header comment when changes are made. + +## 6. Package.json and Lint Config + +- Remove the dependency from the relevant `package.json` +- If vendored code imports `@opentelemetry/core` and the package didn't previously have it as a direct dependency, **add it** — rollup auto-externalizes based on `dependencies`, without it the import resolves to a broken relative path +- Add vendored path to the consolidated lint exceptions in `.oxlintrc.base.json`: +```json +"**/integrations/tracing//vendored/**/*.ts" +``` + +## 7. Build and Format + +```bash +yarn install +npx oxfmt --write / +yarn build:dev:filter @sentry/ +``` + +Check `.d.ts` output for type leaks. For `aws-serverless`: needs `preserveModulesRoot: 'src'` in rollup config. + +## 8. Test Verification + +- Run integration tests: `cd dev-packages/node-integration-tests && yarn test suites/tracing/` +- Run unit tests: `cd packages/node && yarn test:unit test/integrations/tracing/.test.ts` +- **Update test imports**: unit tests importing from `@opentelemetry/instrumentation-` need paths updated to the vendored location, and `vi.mock()` calls updated to match +- Docker-dependent tests (amqplib, kafkajs, redis, postgres) timeout locally but pass in CI +- Version-specific tests: use a local `package.json` in the test directory with unique Docker container names + +**Report test coverage gaps to the user** — if an instrumentation has no integration test, no E2E test, or tests don't cover key functionality, flag it. + +## 9. Report Changes + +**Before submitting, report ALL modifications to the user for verification:** + +1. **Files copied as-is** (only header + formatting changes) — list them +2. **TypeScript strictness adjustments** — list each change with file and line context (e.g., "added `: any` to `.catch()` callback parameter") +3. **Type simplifications** — explain what was simplified vs the original and why +4. **Import path changes** — `from 'kafkajs'` → `from './kafkajs-types'` etc. +5. **Any other modifications** — anything beyond the standard vendoring pattern + +The user should be able to verify that no logic was changed and all adjustments were strictly necessary. + +## 10. PR Creation + +- Branch: `nh/vendor--instrumentation` +- Commit: `ref(node): Vendor instrumentation` +- PR body: one sentence on what was vendored, mention inlined types if applicable, reference closing issue +- Always draft PR, base branch `develop` + +## 11. Common Pitfalls + +- `sed` header stripping removing import lines — always verify +- `preserveModules: true` shifting output paths with deeply nested vendored files +- Docker container name conflicts between test suites +- `/* eslint-disable */` kept for consistency even though project uses oxlint +- `yarn.lock` duplicates — run `npx yarn-deduplicate yarn.lock` +- `.oxlintrc.base.json` merge conflicts — keep both HEAD and develop entries From 3dcd8cc420bbbd33f0d99335dc21e01e5723b1ef Mon Sep 17 00:00:00 2001 From: Nicolas Hrubec Date: Tue, 19 May 2026 15:17:56 +0200 Subject: [PATCH 4/4] Revert "chore: Add vendor-otel skill for OTel instrumentation vendoring" This reverts commit 8f81a74dc1fead767982d958feb085876042b5af. --- .agents/skills/vendor-otel/SKILL.md | 168 ---------------------------- 1 file changed, 168 deletions(-) delete mode 100644 .agents/skills/vendor-otel/SKILL.md diff --git a/.agents/skills/vendor-otel/SKILL.md b/.agents/skills/vendor-otel/SKILL.md deleted file mode 100644 index a83710bc2a24..000000000000 --- a/.agents/skills/vendor-otel/SKILL.md +++ /dev/null @@ -1,168 +0,0 @@ ---- -name: vendor-otel -description: Vendor an OpenTelemetry instrumentation package into the Sentry JavaScript SDK. Use when vendoring, inlining, or copying an @opentelemetry/instrumentation-* package (or similar like @prisma/instrumentation, @fastify/otel) into the SDK source. Also use when the user says "vendor", "inline instrumentation", or references the Vendor OpenTelemetry Instrumentation project. ---- - -# Vendor OTel Instrumentation - -Copy upstream OTel instrumentation TypeScript source into a `vendored/` directory, remove the npm dependency, and ensure builds and tests pass. No logic changes — the vendored code must behave identically to the original. - -## 1. Research - -### Find upstream source -```bash -gh api "repos/open-telemetry/opentelemetry-js-contrib/git/trees/main?recursive=1" --jq '.tree[].path' | grep "instrumentation-/src/.*\.ts$" -``` - -### Check versions -- Pinned version: `grep "instrumentation-" packages/node/package.json` -- Latest tag: `gh api repos/open-telemetry/opentelemetry-js-contrib/git/refs/tags --jq '.[].ref' | grep "instrumentation-"` -- Get commit SHA: `gh api repos/open-telemetry/opentelemetry-js-contrib/git/refs/tags/instrumentation--v --jq '.object.sha'` - -### Verify no breaking changes between pinned and latest -**This is critical.** All OTel instrumentations are pre-v1 so any bump could break things. Diff ALL source files: -```bash -diff <(gh api ".../src/?ref=instrumentation--v" --jq '.content' | base64 -d) \ - <(gh api ".../src/?ref=instrumentation--v" --jq '.content' | base64 -d) -``` -In practice most bumps have zero code changes (only license headers). If there ARE changes, evaluate safety and report to the user before proceeding. - -### Check external type imports -```bash -grep "import.*from '" | grep -v "@opentelemetry\|'\./\|@sentry\|'util'\|'path'\|'fs'\|'http'\|'events'" -``` -External type imports need special handling (see section 4). - -### Check test coverage -- Integration tests: `dev-packages/node-integration-tests/suites/tracing//` -- E2E tests: `dev-packages/e2e-tests/test-applications/node-/` -- Unit tests: `packages/node/test/integrations/tracing/.test.ts` - -Report any gaps to the user (e.g., "no integration test exists for this instrumentation" or "tests exist but don't cover X functionality"). - -## 2. Directory Structure - -Move the integration file into a directory: -- `packages/node/src/integrations/tracing/.ts` → `packages/node/src/integrations/tracing//index.ts` -- For non-tracing integrations (like `fs.ts`): `packages/node/src/integrations//index.ts` -- For non-node packages (aws-serverless, nestjs): follow their existing structure -- Create `vendored/` subdirectory for upstream files - -Import paths in barrel exports (`index.ts`, `tracing/index.ts`) resolve to the directory's `index.ts` automatically — usually no changes needed. - -## 3. Vendoring Source Files - -Fetch original TypeScript from the OTel contrib repo (NOT compiled JS from node_modules): -```bash -gh api "repos/open-telemetry/opentelemetry-js-contrib/contents/?ref=" --jq '.content' | base64 -d -``` - -**Be careful with header stripping** — `sed '1,Nd'` to remove the SPDX header can accidentally strip import lines. Always verify all imports are present after stripping. - -Each vendored file gets this header: -``` -/* - * Copyright The OpenTelemetry Authors - * ...full Apache 2.0 license text... - * - * NOTICE from the Sentry authors: - * - Vendored from: https://github.com/open-telemetry/opentelemetry-js-contrib/tree//packages/instrumentation- - * - Upstream version: @opentelemetry/instrumentation-@ - */ -/* eslint-disable */ -``` - -Add additional bullets to the NOTICE only when applicable: -- `* - Minor TypeScript strictness adjustments for this repository's compiler settings` — when TS changes were made -- `* - Some types vendored from with simplifications` — when types were inlined - -Standard replacements in the main instrumentation file: -- Remove `import { PACKAGE_NAME, PACKAGE_VERSION } from './version'` and the `/** @knipignore */` comment above it -- Add `import { SDK_VERSION } from '@sentry/core'` -- Add `const PACKAGE_NAME = '@sentry/instrumentation-';` -- Replace `PACKAGE_VERSION` with `SDK_VERSION` in the `super()` call - -Don't forget barrel exports (`enums/index.ts`, etc.) if the upstream has them. - -## 4. External Type Handling - -Types from external packages can leak into `.d.ts` output and break consumers. - -**Decision tree:** -1. Check if types appear in public class signatures. TypeScript strips private method signatures from `.d.ts`, so private-only usage won't leak. -2. Check if the upstream `types.ts` already vendors some types inline. -3. If types leak or are needed for compilation, **inline simplified types**: - - Put in a separate `-types.ts` file in vendored/ - - Keep as close to originals as possible — same generic parameters, field names, types - - Only simplify when the full type tree is too deep - - Add `[key: string]: any` index signatures for permissiveness - - Check if package ships own types or uses DefinitelyTyped - -4. After building, verify no leaks: `grep "from ''" packages/node/build/types/...` - -## 5. TypeScript Strictness Adjustments - -The Sentry repo has `strict: true` and `noUncheckedIndexedAccess: true`. Common fixes: -- `` angle-bracket assertions → `as any` (sucrase/esbuild doesn't support angle-bracket syntax) -- Implicit `any` in `.then()`, `.catch()`, `.forEach()`, `.map()` callbacks → add `: any` -- Array indexing `T | undefined` → add `!` non-null assertion or default values -- `ConstructorParameters` constraint failures → replace with `any[]` - -Add the TS strictness bullet to the header comment when changes are made. - -## 6. Package.json and Lint Config - -- Remove the dependency from the relevant `package.json` -- If vendored code imports `@opentelemetry/core` and the package didn't previously have it as a direct dependency, **add it** — rollup auto-externalizes based on `dependencies`, without it the import resolves to a broken relative path -- Add vendored path to the consolidated lint exceptions in `.oxlintrc.base.json`: -```json -"**/integrations/tracing//vendored/**/*.ts" -``` - -## 7. Build and Format - -```bash -yarn install -npx oxfmt --write / -yarn build:dev:filter @sentry/ -``` - -Check `.d.ts` output for type leaks. For `aws-serverless`: needs `preserveModulesRoot: 'src'` in rollup config. - -## 8. Test Verification - -- Run integration tests: `cd dev-packages/node-integration-tests && yarn test suites/tracing/` -- Run unit tests: `cd packages/node && yarn test:unit test/integrations/tracing/.test.ts` -- **Update test imports**: unit tests importing from `@opentelemetry/instrumentation-` need paths updated to the vendored location, and `vi.mock()` calls updated to match -- Docker-dependent tests (amqplib, kafkajs, redis, postgres) timeout locally but pass in CI -- Version-specific tests: use a local `package.json` in the test directory with unique Docker container names - -**Report test coverage gaps to the user** — if an instrumentation has no integration test, no E2E test, or tests don't cover key functionality, flag it. - -## 9. Report Changes - -**Before submitting, report ALL modifications to the user for verification:** - -1. **Files copied as-is** (only header + formatting changes) — list them -2. **TypeScript strictness adjustments** — list each change with file and line context (e.g., "added `: any` to `.catch()` callback parameter") -3. **Type simplifications** — explain what was simplified vs the original and why -4. **Import path changes** — `from 'kafkajs'` → `from './kafkajs-types'` etc. -5. **Any other modifications** — anything beyond the standard vendoring pattern - -The user should be able to verify that no logic was changed and all adjustments were strictly necessary. - -## 10. PR Creation - -- Branch: `nh/vendor--instrumentation` -- Commit: `ref(node): Vendor instrumentation` -- PR body: one sentence on what was vendored, mention inlined types if applicable, reference closing issue -- Always draft PR, base branch `develop` - -## 11. Common Pitfalls - -- `sed` header stripping removing import lines — always verify -- `preserveModules: true` shifting output paths with deeply nested vendored files -- Docker container name conflicts between test suites -- `/* eslint-disable */` kept for consistency even though project uses oxlint -- `yarn.lock` duplicates — run `npx yarn-deduplicate yarn.lock` -- `.oxlintrc.base.json` merge conflicts — keep both HEAD and develop entries