diff --git a/plugins/node/instrumentation-mongoose/src/mongoose.ts b/plugins/node/instrumentation-mongoose/src/mongoose.ts index 40ed3617da..30468e6f67 100644 --- a/plugins/node/instrumentation-mongoose/src/mongoose.ts +++ b/plugins/node/instrumentation-mongoose/src/mongoose.ts @@ -153,6 +153,17 @@ export class MongooseInstrumentation extends InstrumentationBase { + return function patchedStatic( + this: any, + docsOrOps: any, + options?: any, + callback?: Function + ) { + if ( + self.getConfig().requireParentSpan && + trace.getSpan(context.active()) === undefined + ) { + return original.apply(this, arguments); + } + if (typeof options === 'function') { + callback = options; + options = undefined; + } + + const serializePayload: SerializerPayload = {}; + switch (op) { + case 'insertMany': + serializePayload.documents = docsOrOps; + break; + case 'bulkWrite': + serializePayload.operations = docsOrOps; + break; + default: + serializePayload.document = docsOrOps; + break; + } + if (options !== undefined) { + serializePayload.options = options; + } + + const attributes: Attributes = {}; + const { dbStatementSerializer } = self.getConfig(); + if (dbStatementSerializer) { + attributes[SEMATTRS_DB_STATEMENT] = dbStatementSerializer( + op, + serializePayload + ); + } + + const span = self._startSpan( + this.collection, + this.modelName, + op, + attributes + ); + + return self._handleResponse( + span, + original, + this, + arguments, + callback, + moduleVersion + ); + }; + }; + } + // 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 diff --git a/plugins/node/instrumentation-mongoose/src/types.ts b/plugins/node/instrumentation-mongoose/src/types.ts index 8afce8e347..1e8e46ba98 100644 --- a/plugins/node/instrumentation-mongoose/src/types.ts +++ b/plugins/node/instrumentation-mongoose/src/types.ts @@ -23,6 +23,8 @@ export interface SerializerPayload { document?: any; aggregatePipeline?: any; fields?: any; + documents?: any; + operations?: any; } export type DbStatementSerializer = ( diff --git a/plugins/node/instrumentation-mongoose/src/utils.ts b/plugins/node/instrumentation-mongoose/src/utils.ts index 7f78bcc882..72417b3f84 100644 --- a/plugins/node/instrumentation-mongoose/src/utils.ts +++ b/plugins/node/instrumentation-mongoose/src/utils.ts @@ -105,6 +105,8 @@ export function handleCallbackResponse( let callbackArgumentIndex = 0; if (args.length === 2) { callbackArgumentIndex = 1; + } else if (args.length === 3) { + callbackArgumentIndex = 2; } args[callbackArgumentIndex] = (err: Error, response: any): any => { diff --git a/plugins/node/instrumentation-mongoose/test/mongoose-common.test.ts b/plugins/node/instrumentation-mongoose/test/mongoose-common.test.ts index 0cf2d72909..ed954b8b54 100644 --- a/plugins/node/instrumentation-mongoose/test/mongoose-common.test.ts +++ b/plugins/node/instrumentation-mongoose/test/mongoose-common.test.ts @@ -74,9 +74,8 @@ describe('mongoose instrumentation [common]', () => { }); }, }); - instrumentation.enable(); await loadUsers(); - await User.createIndexes(); + instrumentation.enable(); }); afterEach(async () => { @@ -321,6 +320,107 @@ describe('mongoose instrumentation [common]', () => { expect(statement.document).toEqual(expect.objectContaining(document)); }); + it('instrumenting insertMany operation', async () => { + const documents = [ + { + firstName: 'John', + lastName: 'Doe', + email: 'john.doe+1@example.com', + }, + { + firstName: 'Jane', + lastName: 'Doe', + email: 'jane.doe+1@example.com', + }, + ]; + await User.insertMany(documents); + + const spans = getTestSpans(); + expect(spans.length).toBe(1); + assertSpan(spans[0] as ReadableSpan); + expect(spans[0].attributes[SEMATTRS_DB_OPERATION]).toBe('insertMany'); + const statement = getStatement(spans[0] as ReadableSpan); + expect(statement.documents).toEqual(documents); + }); + + it('instrumenting bulkWrite operation', async () => { + const operations = [ + { + insertOne: { + document: { + firstName: 'Jane', + lastName: 'Doe', + email: 'jane.doe+2@example.com', + age: 25, + }, + }, + }, + { + updateMany: { + filter: { age: { $lte: 20 } }, + update: { $set: { age: 20 } }, + }, + }, + { + updateOne: { + filter: { firstName: 'Jane' }, + update: { $inc: { age: 1 } }, + }, + }, + { deleteOne: { filter: { firstName: 'Michael' } } }, + { + updateOne: { + filter: { firstName: 'Zara' }, + update: { + $set: { lastName: 'Doe', age: 40, email: 'zara@example.com' }, + }, + upsert: true, + }, + }, + ]; + await User.bulkWrite(operations); + + const spans = getTestSpans(); + expect(spans.length).toBe(1); + assertSpan(spans[0] as ReadableSpan); + expect(spans[0].attributes[SEMATTRS_DB_OPERATION]).toBe('bulkWrite'); + const statement = getStatement(spans[0] as ReadableSpan); + expect(statement.operations).toEqual([ + { + insertOne: { + document: { + firstName: 'Jane', + lastName: 'Doe', + email: 'jane.doe+2@example.com', + age: 25, + }, + }, + }, + { + updateMany: { + filter: { age: { $lte: 20 } }, + update: { $set: { age: 20 } }, + }, + }, + { + updateOne: { + filter: { firstName: 'Jane' }, + update: { $inc: { age: 1 } }, + }, + }, + { deleteOne: { filter: { firstName: 'Michael' } } }, + { + updateOne: { + filter: { firstName: 'Zara' }, + update: { + $set: { lastName: 'Doe', age: 40, email: 'zara@example.com' }, + }, + upsert: true, + }, + }, + ]); + }); + it('instrumenting aggregate operation', async () => { await User.aggregate([ { $match: { firstName: 'John' } }, diff --git a/plugins/node/instrumentation-mongoose/test/mongoose-v5-v6.test.ts b/plugins/node/instrumentation-mongoose/test/mongoose-v5-v6.test.ts index 1e2fe97740..be6f5fc5ed 100644 --- a/plugins/node/instrumentation-mongoose/test/mongoose-v5-v6.test.ts +++ b/plugins/node/instrumentation-mongoose/test/mongoose-v5-v6.test.ts @@ -71,9 +71,8 @@ describe('mongoose instrumentation [v5/v6]', () => { }); }, }); - instrumentation.enable(); await loadUsers(); - await User.createIndexes(); + instrumentation.enable(); }); afterEach(async () => { @@ -152,6 +151,61 @@ describe('mongoose instrumentation [v5/v6]', () => { }); }); + describe('when insertMany call has callback', async () => { + it('instrumenting insertMany operation with generic options and callback', done => { + const documents = [ + { + firstName: 'John', + lastName: 'Doe', + email: 'john.doe+1@example.com', + }, + { + firstName: 'Jane', + lastName: 'Doe', + email: 'jane.doe+1@example.com', + }, + ]; + // @ts-ignore - v7 removed callback support + // https://mongoosejs.com/docs/migrating_to_7.html#dropped-callback-support + User.insertMany(documents, { ordered: true }, () => { + const spans = getTestSpans(); + expect(spans.length).toBe(1); + assertSpan(spans[0] as ReadableSpan); + expect(spans[0].attributes[SEMATTRS_DB_OPERATION]).toBe('insertMany'); + const statement = getStatement(spans[0] as ReadableSpan); + expect(statement.documents).toEqual(documents); + expect(statement.options.ordered).toEqual(true); + done(); + }); + }); + + it('instrumenting insertMany operation with only callback', done => { + const documents = [ + { + firstName: 'John', + lastName: 'Doe', + email: 'john.doe+1@example.com', + }, + { + firstName: 'Jane', + lastName: 'Doe', + email: 'jane.doe+1@example.com', + }, + ]; + // @ts-ignore - v7 removed callback support + // https://mongoosejs.com/docs/migrating_to_7.html#dropped-callback-support + User.insertMany(documents, () => { + const spans = getTestSpans(); + expect(spans.length).toBe(1); + assertSpan(spans[0] as ReadableSpan); + expect(spans[0].attributes[SEMATTRS_DB_OPERATION]).toBe('insertMany'); + const statement = getStatement(spans[0] as ReadableSpan); + expect(statement.documents).toEqual(documents); + done(); + }); + }); + }); + describe('remove operation', () => { it('instrumenting remove operation [deprecated]', async () => { const user = await User.findOne({ email: 'john.doe@example.com' }); diff --git a/plugins/node/instrumentation-mongoose/test/mongoose-v7-v8.test.ts b/plugins/node/instrumentation-mongoose/test/mongoose-v7-v8.test.ts index 5324e84b6f..2ef423f47f 100644 --- a/plugins/node/instrumentation-mongoose/test/mongoose-v7-v8.test.ts +++ b/plugins/node/instrumentation-mongoose/test/mongoose-v7-v8.test.ts @@ -68,9 +68,8 @@ describe('mongoose instrumentation [v7/v8]', () => { }); }, }); - instrumentation.enable(); await loadUsers(); - await User.createIndexes(); + instrumentation.enable(); }); afterEach(async () => {