From efb6464adfdd8d447746cde1e4dc3bd55d5e96a5 Mon Sep 17 00:00:00 2001 From: Andrei Borza Date: Tue, 28 Jul 2026 12:14:36 +0200 Subject: [PATCH] ref(node): Remove vendored Mongoose instrumentation This instrumentation is dead code: the Mongoose integration is provided by the channel-based implementation in `@sentry/server-utils` (default since #22501), and the vendored OpenTelemetry instrumentation here is no longer wired up. Ref: #22346 Co-Authored-By: Claude Opus 4.8 --- .../integrations/tracing/mongoose/index.ts | 6 - .../mongoose/vendored/mongoose-types.ts | 42 --- .../tracing/mongoose/vendored/mongoose.ts | 333 ------------------ .../tracing/mongoose/vendored/utils.ts | 61 ---- 4 files changed, 442 deletions(-) delete mode 100644 packages/node/src/integrations/tracing/mongoose/index.ts delete mode 100644 packages/node/src/integrations/tracing/mongoose/vendored/mongoose-types.ts delete mode 100644 packages/node/src/integrations/tracing/mongoose/vendored/mongoose.ts delete mode 100644 packages/node/src/integrations/tracing/mongoose/vendored/utils.ts diff --git a/packages/node/src/integrations/tracing/mongoose/index.ts b/packages/node/src/integrations/tracing/mongoose/index.ts deleted file mode 100644 index fe18a73490b4..000000000000 --- a/packages/node/src/integrations/tracing/mongoose/index.ts +++ /dev/null @@ -1,6 +0,0 @@ -import { MongooseInstrumentation } from './vendored/mongoose'; -import { generateInstrumentOnce } from '../../../otel/instrument'; - -const INTEGRATION_NAME = 'Mongoose' as const; - -export const instrumentMongoose = generateInstrumentOnce(INTEGRATION_NAME, () => new MongooseInstrumentation()); diff --git a/packages/node/src/integrations/tracing/mongoose/vendored/mongoose-types.ts b/packages/node/src/integrations/tracing/mongoose/vendored/mongoose-types.ts deleted file mode 100644 index 0d1753007b70..000000000000 --- a/packages/node/src/integrations/tracing/mongoose/vendored/mongoose-types.ts +++ /dev/null @@ -1,42 +0,0 @@ -/* - * Simplified type definitions vendored from mongoose. - * Only includes the types actually accessed by the instrumentation. - */ - -export interface Collection { - name: string; - conn: Connection; - [key: string]: any; -} - -export interface Connection { - name: string; - host: string; - port: number; - user?: string; - [key: string]: any; -} - -export declare const Model: { - prototype: any; - collection: Collection; - modelName: string; - aggregate: Function; - insertMany: Function; - bulkWrite: Function; - [key: string]: any; -}; - -export declare const Query: { - prototype: any; - [key: string]: any; -}; - -export declare const Aggregate: { - prototype: any; - [key: string]: any; -}; - -export interface MongooseError extends Error { - code?: number; -} diff --git a/packages/node/src/integrations/tracing/mongoose/vendored/mongoose.ts b/packages/node/src/integrations/tracing/mongoose/vendored/mongoose.ts deleted file mode 100644 index 579408acf6c5..000000000000 --- a/packages/node/src/integrations/tracing/mongoose/vendored/mongoose.ts +++ /dev/null @@ -1,333 +0,0 @@ -/* - * Copyright The OpenTelemetry Authors - * SPDX-License-Identifier: Apache-2.0 - * - * NOTICE from the Sentry authors: - * - Vendored from: https://github.com/open-telemetry/opentelemetry-js-contrib/tree/15ef7506553f631ea4181391e0c5725a56f0d082/packages/instrumentation-mongoose - * - Upstream version: @opentelemetry/instrumentation-mongoose@0.64.0 - * - Types vendored from mongoose as simplified interfaces - * - Minor TypeScript strictness adjustments for this repository's compiler settings - * - Refactored to use Sentry's span APIs instead of OpenTelemetry tracing APIs - */ - -import { - type InstrumentationConfig, - InstrumentationBase, - type InstrumentationModuleDefinition, - InstrumentationNodeModuleDefinition, -} from '@opentelemetry/instrumentation'; -import type { Span } from '@sentry/core'; -import { getActiveSpan, SDK_VERSION, withActiveSpan } from '@sentry/core'; -import { startMongooseLegacySpan } from '@sentry/server-utils'; -import type * as mongoose from './mongoose-types'; -import { handleCallbackResponse, handlePromiseResponse } from './utils'; - -const PACKAGE_NAME = '@sentry/instrumentation-mongoose'; -const ORIGIN = 'auto.db.otel.mongoose'; - -type MongooseModuleExports = typeof mongoose; - -// The raw imported `mongoose` module: either the CJS object itself, or an ESM -// namespace wrapper exposing the same shape under `.default`. -type MongooseModule = MongooseModuleExports & { - default?: MongooseModuleExports; - [Symbol.toStringTag]?: string; -}; - -const contextCaptureFunctionsCommon = [ - 'deleteOne', - 'deleteMany', - 'find', - 'findOne', - 'estimatedDocumentCount', - 'countDocuments', - 'distinct', - 'where', - '$where', - 'findOneAndUpdate', - 'findOneAndDelete', - 'findOneAndReplace', -]; - -const contextCaptureFunctions6 = ['remove', 'count', 'findOneAndRemove', ...contextCaptureFunctionsCommon]; -const contextCaptureFunctions7 = ['count', 'findOneAndRemove', ...contextCaptureFunctionsCommon]; -const contextCaptureFunctions8 = [...contextCaptureFunctionsCommon]; - -function getContextCaptureFunctions(moduleVersion: string | undefined): string[] { - /* istanbul ignore next */ - if (!moduleVersion) { - return contextCaptureFunctionsCommon; - } else if (moduleVersion.startsWith('6.') || moduleVersion.startsWith('5.')) { - return contextCaptureFunctions6; - } else if (moduleVersion.startsWith('7.')) { - return contextCaptureFunctions7; - } else { - return contextCaptureFunctions8; - } -} - -function instrumentRemove(moduleVersion: string | undefined): boolean { - return (moduleVersion && (moduleVersion.startsWith('5.') || moduleVersion.startsWith('6.'))) || false; -} - -/** - * 8.21.0 changed Document.updateOne/deleteOne so that the Query is not fully built when Query.exec() is called. - * @param moduleVersion - */ -function needsDocumentMethodPatch(moduleVersion: string | undefined): boolean { - if (!moduleVersion || !moduleVersion.startsWith('8.')) { - return false; - } - - const minor = parseInt(moduleVersion.split('.')[1]!, 10); - return minor >= 21; -} - -// when mongoose functions are called, we store the original call context -// and then set it as the parent for the spans created by Query/Aggregate exec() -// calls. this bypass the unlinked spans issue on thenables await operations. -export const _STORED_PARENT_SPAN: unique symbol = Symbol('stored-parent-span'); - -// Prevents double-instrumentation when doc.updateOne/deleteOne (Mongoose 8.21.0+) -// creates a span and returns a Query that also calls exec() -export const _ALREADY_INSTRUMENTED: unique symbol = Symbol('already-instrumented'); - -export class MongooseInstrumentation extends InstrumentationBase { - constructor(config: InstrumentationConfig = {}) { - super(PACKAGE_NAME, SDK_VERSION, config); - } - - protected init(): InstrumentationModuleDefinition { - const module = new InstrumentationNodeModuleDefinition( - 'mongoose', - // mongoose >= 9.7.0 publishes via diagnostics_channel and is instrumented by - // `subscribeMongooseDiagnosticChannels` instead, so this IITM patcher must not - // overlap it — otherwise every operation would emit two mongoose spans. - ['>=5.9.7 <9.7.0'], - this.patch.bind(this), - this.unpatch.bind(this), - ); - return module; - } - - private patch(module: MongooseModule, moduleVersion: string | undefined) { - const moduleExports: MongooseModuleExports = - module[Symbol.toStringTag] === 'Module' && module.default ? module.default : module; - - this._wrap(moduleExports.Model.prototype, 'save', this.patchOnModelMethods('save')); - // mongoose applies this code on module require: - // Model.prototype.$save = Model.prototype.save; - // which captures the save function before it is patched. - // so we need to apply the same logic after instrumenting the save function. - moduleExports.Model.prototype.$save = moduleExports.Model.prototype.save; - - if (instrumentRemove(moduleVersion)) { - this._wrap(moduleExports.Model.prototype, 'remove', this.patchOnModelMethods('remove')); - } - - // Mongoose 8.21.0+ changed Document.updateOne()/deleteOne() so that the Query is not fully built when Query.exec() is called. - if (needsDocumentMethodPatch(moduleVersion)) { - this._wrap(moduleExports.Model.prototype, 'updateOne', this._patchDocumentUpdateMethods('updateOne')); - this._wrap(moduleExports.Model.prototype, 'deleteOne', this._patchDocumentUpdateMethods('deleteOne')); - } - - this._wrap(moduleExports.Query.prototype, 'exec', this.patchQueryExec()); - this._wrap(moduleExports.Aggregate.prototype, 'exec', this.patchAggregateExec()); - - const contextCaptureFunctions = getContextCaptureFunctions(moduleVersion); - - contextCaptureFunctions.forEach((funcName: string) => { - this._wrap(moduleExports.Query.prototype, funcName as any, this.patchAndCaptureSpanContext(funcName)); - }); - this._wrap(moduleExports.Model, 'aggregate', this.patchModelAggregate()); - - this._wrap(moduleExports.Model, 'insertMany', this.patchModelStatic('insertMany')); - this._wrap(moduleExports.Model, 'bulkWrite', this.patchModelStatic('bulkWrite')); - - return moduleExports; - } - - private unpatch(module: MongooseModule, moduleVersion: string | undefined): void { - const moduleExports: MongooseModuleExports = - module[Symbol.toStringTag] === 'Module' && module.default ? module.default : module; - - const contextCaptureFunctions = getContextCaptureFunctions(moduleVersion); - - this._unwrap(moduleExports.Model.prototype, 'save'); - // revert the patch for $save which we applied by aliasing it to patched `save` - moduleExports.Model.prototype.$save = moduleExports.Model.prototype.save; - - if (instrumentRemove(moduleVersion)) { - this._unwrap(moduleExports.Model.prototype, 'remove'); - } - - if (needsDocumentMethodPatch(moduleVersion)) { - this._unwrap(moduleExports.Model.prototype, 'updateOne'); - this._unwrap(moduleExports.Model.prototype, 'deleteOne'); - } - - this._unwrap(moduleExports.Query.prototype, 'exec'); - this._unwrap(moduleExports.Aggregate.prototype, 'exec'); - - contextCaptureFunctions.forEach((funcName: string) => { - this._unwrap(moduleExports.Query.prototype, funcName as any); - }); - this._unwrap(moduleExports.Model, 'aggregate'); - - this._unwrap(moduleExports.Model, 'insertMany'); - this._unwrap(moduleExports.Model, 'bulkWrite'); - } - - private patchAggregateExec() { - const self = this; - return (originalAggregate: Function) => { - return function exec(this: any, callback?: Function) { - const parentSpan = this[_STORED_PARENT_SPAN]; - const span = startMongooseLegacySpan({ - collection: this._model.collection, - modelName: this._model?.modelName, - operation: 'aggregate', - origin: ORIGIN, - parentSpan, - }); - - return self._handleResponse(span, originalAggregate, this, arguments, callback); - }; - }; - } - - private patchQueryExec() { - const self = this; - return (originalExec: Function) => { - return function exec(this: any, callback?: Function) { - // Skip if already instrumented by document instance method patch - if (this[_ALREADY_INSTRUMENTED]) { - return originalExec.apply(this, arguments); - } - - const parentSpan = this[_STORED_PARENT_SPAN]; - const span = startMongooseLegacySpan({ - collection: this.mongooseCollection, - modelName: this.model.modelName, - operation: this.op, - origin: ORIGIN, - parentSpan, - }); - - return self._handleResponse(span, originalExec, this, arguments, callback); - }; - }; - } - - private patchOnModelMethods(op: string) { - const self = this; - return (originalOnModelFunction: Function) => { - return function method(this: any, options?: any, callback?: Function) { - const span = startMongooseLegacySpan({ - collection: this.constructor.collection, - modelName: this.constructor.modelName, - operation: op, - origin: ORIGIN, - }); - - if (options instanceof Function) { - // oxlint-disable-next-line no-param-reassign - callback = options; - } - - return self._handleResponse(span, originalOnModelFunction, this, arguments, callback); - }; - }; - } - - // Patch document instance methods (doc.updateOne/deleteOne) for Mongoose 8.21.0+. - private _patchDocumentUpdateMethods(op: string) { - const self = this; - return (originalMethod: Function) => { - return function method(this: any, update?: any, options?: any, callback?: Function) { - // determine actual callback since different argument patterns are allowed - let actualCallback: Function | undefined = callback; - if (typeof update === 'function') { - actualCallback = update; - } else if (typeof options === 'function') { - actualCallback = options; - } - - const span = startMongooseLegacySpan({ - collection: this.constructor.collection, - modelName: this.constructor.modelName, - operation: op, - origin: ORIGIN, - }); - - const result = self._handleResponse(span, originalMethod, this, arguments, actualCallback); - - // Mark returned Query to prevent double-instrumentation when exec() is eventually called - if (result && typeof result === 'object') { - result[_ALREADY_INSTRUMENTED] = true; - } - - return result; - }; - }; - } - - private patchModelStatic(op: string) { - const self = this; - return (original: Function) => { - return function patchedStatic(this: any, docsOrOps: any, options?: any, callback?: Function) { - if (typeof options === 'function') { - // oxlint-disable-next-line no-param-reassign - callback = options; - } - - const span = startMongooseLegacySpan({ - collection: this.collection, - modelName: this.modelName, - operation: op, - origin: ORIGIN, - }); - - return self._handleResponse(span, original, this, arguments, callback); - }; - }; - } - - // we want to capture the otel span on the object which is calling exec. - // in the special case of aggregate, we need have no function to path - // on the Aggregate object to capture the context on, so we patch - // the aggregate of Model, and set the context on the Aggregate object - private patchModelAggregate() { - return (original: Function) => { - return function captureSpanContext(this: any) { - const currentSpan = getActiveSpan(); - const aggregate = original.apply(this, arguments); - if (aggregate) aggregate[_STORED_PARENT_SPAN] = currentSpan; - return aggregate; - }; - }; - } - - private patchAndCaptureSpanContext(_funcName: string) { - return (original: Function) => { - return function captureSpanContext(this: any) { - this[_STORED_PARENT_SPAN] = getActiveSpan(); - return original.apply(this, arguments); - }; - }; - } - - private _handleResponse(span: Span, exec: Function, originalThis: any, args: IArguments, callback?: Function) { - // Activate the span while the underlying operation runs so that nested instrumentation - // (e.g. the mongodb driver spans) is parented to this span. `withActiveSpan` returns the - // callback's result untouched, so lazy mongoose Query thenables are handed back unexecuted. - return withActiveSpan(span, () => { - if (callback instanceof Function) { - return handleCallbackResponse(callback, exec, originalThis, span, args); - } else { - const response = exec.apply(originalThis, args); - return handlePromiseResponse(response, span); - } - }); - } -} diff --git a/packages/node/src/integrations/tracing/mongoose/vendored/utils.ts b/packages/node/src/integrations/tracing/mongoose/vendored/utils.ts deleted file mode 100644 index ea5aeb259a8f..000000000000 --- a/packages/node/src/integrations/tracing/mongoose/vendored/utils.ts +++ /dev/null @@ -1,61 +0,0 @@ -/* - * Copyright The OpenTelemetry Authors - * SPDX-License-Identifier: Apache-2.0 - * - * NOTICE from the Sentry authors: - * - Vendored from: https://github.com/open-telemetry/opentelemetry-js-contrib/tree/15ef7506553f631ea4181391e0c5725a56f0d082/packages/instrumentation-mongoose - * - Upstream version: @opentelemetry/instrumentation-mongoose@0.64.0 - * - Types vendored from mongoose as simplified interfaces - * - Refactored to use Sentry's span APIs instead of OpenTelemetry tracing APIs - */ - -import type { Span } from '@sentry/core'; -import { SPAN_STATUS_ERROR } from '@sentry/core'; -import type { MongooseError } from './mongoose-types'; - -function setErrorStatus(span: Span, error: MongooseError): void { - span.setStatus({ - code: SPAN_STATUS_ERROR, - message: `${error.message} ${error.code ? `\nMongoose Error Code: ${error.code}` : ''}`, - }); -} - -export function handlePromiseResponse(execResponse: any, span: Span): any { - if (!(execResponse instanceof Promise)) { - span.end(); - return execResponse; - } - - return execResponse - .catch((err: any) => { - setErrorStatus(span, err); - throw err; - }) - .finally(() => span.end()); -} - -export function handleCallbackResponse( - callback: Function, - exec: Function, - originalThis: any, - span: Span, - args: IArguments, -) { - let callbackArgumentIndex = 0; - if (args.length === 2) { - callbackArgumentIndex = 1; - } else if (args.length === 3) { - callbackArgumentIndex = 2; - } - - args[callbackArgumentIndex] = (err: Error, response: any): any => { - if (err) { - setErrorStatus(span, err); - } - - span.end(); - return callback(err, response); - }; - - return exec.apply(originalThis, args); -}