UNPKG

@sentry/node

Version:

Sentry Node SDK using OpenTelemetry for performance instrumentation

231 lines (227 loc) 9.5 kB
Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' }); const instrumentation = require('@opentelemetry/instrumentation'); const attributes = require('@sentry/conventions/attributes'); const core = require('@sentry/core'); const utils = require('./utils.js'); const PACKAGE_NAME = "@sentry/instrumentation-mongoose"; const ORIGIN = "auto.db.otel.mongoose"; 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) { 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) { return moduleVersion && (moduleVersion.startsWith("5.") || moduleVersion.startsWith("6.")) || false; } function needsDocumentMethodPatch(moduleVersion) { if (!moduleVersion || !moduleVersion.startsWith("8.")) { return false; } const minor = parseInt(moduleVersion.split(".")[1], 10); return minor >= 21; } const _STORED_PARENT_SPAN = /* @__PURE__ */ Symbol("stored-parent-span"); const _ALREADY_INSTRUMENTED = /* @__PURE__ */ Symbol("already-instrumented"); class MongooseInstrumentation extends instrumentation.InstrumentationBase { constructor(config = {}) { super(PACKAGE_NAME, core.SDK_VERSION, config); } init() { const module = new instrumentation.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; } patch(module, moduleVersion) { const moduleExports = module[Symbol.toStringTag] === "Module" && module.default ? module.default : module; this._wrap(moduleExports.Model.prototype, "save", this.patchOnModelMethods("save")); moduleExports.Model.prototype.$save = moduleExports.Model.prototype.save; if (instrumentRemove(moduleVersion)) { this._wrap(moduleExports.Model.prototype, "remove", this.patchOnModelMethods("remove")); } 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) => { this._wrap(moduleExports.Query.prototype, funcName, 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; } unpatch(module, moduleVersion) { const moduleExports = module[Symbol.toStringTag] === "Module" && module.default ? module.default : module; const contextCaptureFunctions = getContextCaptureFunctions(moduleVersion); this._unwrap(moduleExports.Model.prototype, "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) => { this._unwrap(moduleExports.Query.prototype, funcName); }); this._unwrap(moduleExports.Model, "aggregate"); this._unwrap(moduleExports.Model, "insertMany"); this._unwrap(moduleExports.Model, "bulkWrite"); } patchAggregateExec() { const self = this; return (originalAggregate) => { return function exec(callback) { const parentSpan = this[_STORED_PARENT_SPAN]; const span = self._startSpan(this._model.collection, this._model?.modelName, "aggregate", parentSpan); return self._handleResponse(span, originalAggregate, this, arguments, callback); }; }; } patchQueryExec() { const self = this; return (originalExec) => { return function exec(callback) { if (this[_ALREADY_INSTRUMENTED]) { return originalExec.apply(this, arguments); } const parentSpan = this[_STORED_PARENT_SPAN]; const span = self._startSpan(this.mongooseCollection, this.model.modelName, this.op, parentSpan); return self._handleResponse(span, originalExec, this, arguments, callback); }; }; } patchOnModelMethods(op) { const self = this; return (originalOnModelFunction) => { return function method(options, callback) { const span = self._startSpan(this.constructor.collection, this.constructor.modelName, op); if (options instanceof Function) { callback = options; } return self._handleResponse(span, originalOnModelFunction, this, arguments, callback); }; }; } // Patch document instance methods (doc.updateOne/deleteOne) for Mongoose 8.21.0+. _patchDocumentUpdateMethods(op) { const self = this; return (originalMethod) => { return function method(update, options, callback) { let actualCallback = callback; if (typeof update === "function") { actualCallback = update; } else if (typeof options === "function") { actualCallback = options; } const span = self._startSpan(this.constructor.collection, this.constructor.modelName, op); const result = self._handleResponse(span, originalMethod, this, arguments, actualCallback); if (result && typeof result === "object") { result[_ALREADY_INSTRUMENTED] = true; } return result; }; }; } patchModelStatic(op) { const self = this; return (original) => { return function patchedStatic(docsOrOps, options, callback) { if (typeof options === "function") { callback = options; } const span = self._startSpan(this.collection, this.modelName, op); 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 patchModelAggregate() { return (original) => { return function captureSpanContext() { const currentSpan = core.getActiveSpan(); const aggregate = original.apply(this, arguments); if (aggregate) aggregate[_STORED_PARENT_SPAN] = currentSpan; return aggregate; }; }; } patchAndCaptureSpanContext(_funcName) { return (original) => { return function captureSpanContext() { this[_STORED_PARENT_SPAN] = core.getActiveSpan(); return original.apply(this, arguments); }; }; } _startSpan(collection, modelName, operation, parentSpan) { const attributes$1 = { ...utils.getAttributesFromCollection(collection), // oxlint-disable-next-line typescript/no-deprecated [attributes.DB_OPERATION]: operation, // oxlint-disable-next-line typescript/no-deprecated [attributes.DB_SYSTEM]: "mongoose", // keep for backwards compatibility [core.SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: ORIGIN }; return core.startInactiveSpan({ name: `mongoose.${modelName}.${operation}`, kind: core.SPAN_KIND.CLIENT, attributes: attributes$1, parentSpan }); } _handleResponse(span, exec, originalThis, args, callback) { return core.withActiveSpan(span, () => { if (callback instanceof Function) { return utils.handleCallbackResponse(callback, exec, originalThis, span, args); } else { const response = exec.apply(originalThis, args); return utils.handlePromiseResponse(response, span); } }); } } exports.MongooseInstrumentation = MongooseInstrumentation; exports._ALREADY_INSTRUMENTED = _ALREADY_INSTRUMENTED; exports._STORED_PARENT_SPAN = _STORED_PARENT_SPAN; //# sourceMappingURL=mongoose.js.map