From eb21cd01514e0a63c42336d0b62eab76110d8170 Mon Sep 17 00:00:00 2001 From: Nazar Vovk Date: Wed, 7 May 2025 17:50:05 +0300 Subject: [PATCH 1/5] feat(instrumentation-mongoose)!: add instrumentation of static methods --- .../node/instrumentation-mongoose/.tav.yml | 6 +- .../node/instrumentation-mongoose/README.md | 2 +- .../instrumentation-mongoose/src/mongoose.ts | 81 +++++++++++++- .../instrumentation-mongoose/src/types.ts | 2 + .../test/mongoose-common.test.ts | 101 ++++++++++++++++++ .../instrumentation-mongoose/test/user.ts | 44 ++++---- 6 files changed, 213 insertions(+), 23 deletions(-) diff --git a/plugins/node/instrumentation-mongoose/.tav.yml b/plugins/node/instrumentation-mongoose/.tav.yml index ba89e40faf..41a504deb9 100644 --- a/plugins/node/instrumentation-mongoose/.tav.yml +++ b/plugins/node/instrumentation-mongoose/.tav.yml @@ -1,6 +1,10 @@ mongoose: - versions: - include: ">=5.9.7 <7" + include: ">=5.10.19 <6" + mode: latest-minors + commands: npm run test-v5-v6 + - versions: + include: ">=6.7.5 <7" mode: latest-minors commands: npm run test-v5-v6 - versions: diff --git a/plugins/node/instrumentation-mongoose/README.md b/plugins/node/instrumentation-mongoose/README.md index 31a19b4e7d..acdcc31325 100644 --- a/plugins/node/instrumentation-mongoose/README.md +++ b/plugins/node/instrumentation-mongoose/README.md @@ -17,7 +17,7 @@ npm install --save @opentelemetry/instrumentation-mongoose ## Supported Versions -- [`mongoose`](https://www.npmjs.com/package/mongoose) versions `>=5.9.7 <9` +- [`mongoose`](https://www.npmjs.com/package/mongoose) versions `>=5.10.19 <6` and `>=6.7.5 <9` ## Usage diff --git a/plugins/node/instrumentation-mongoose/src/mongoose.ts b/plugins/node/instrumentation-mongoose/src/mongoose.ts index 40ed3617da..f3cbc261e3 100644 --- a/plugins/node/instrumentation-mongoose/src/mongoose.ts +++ b/plugins/node/instrumentation-mongoose/src/mongoose.ts @@ -99,7 +99,7 @@ export class MongooseInstrumentation extends InstrumentationBase=5.9.7 <9'], + ['>=5.10.19 <6', '>=6.7.5 <9'], this.patch.bind(this), this.unpatch.bind(this) ); @@ -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 (options instanceof 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/test/mongoose-common.test.ts b/plugins/node/instrumentation-mongoose/test/mongoose-common.test.ts index 0cf2d72909..06c0bebfc1 100644 --- a/plugins/node/instrumentation-mongoose/test/mongoose-common.test.ts +++ b/plugins/node/instrumentation-mongoose/test/mongoose-common.test.ts @@ -321,6 +321,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/user.ts b/plugins/node/instrumentation-mongoose/test/user.ts index 0e8eff02eb..6236d837c3 100644 --- a/plugins/node/instrumentation-mongoose/test/user.ts +++ b/plugins/node/instrumentation-mongoose/test/user.ts @@ -15,6 +15,8 @@ */ import { Schema, Document } from 'mongoose'; import * as mongoose from 'mongoose'; +import { context } from '@opentelemetry/api'; +import { suppressTracing } from '@opentelemetry/core'; export interface IUser extends Document { email: string; @@ -35,25 +37,27 @@ const User = mongoose.model('User', UserSchema); export default User; export const loadUsers = async () => { - await User.insertMany([ - new User({ - firstName: 'John', - lastName: 'Doe', - email: 'john.doe@example.com', - age: 18, - }), - new User({ - firstName: 'Jane', - lastName: 'Doe', - email: 'jane.doe@example.com', - age: 19, - }), - new User({ - firstName: 'Michael', - lastName: 'Fox', - email: 'michael.fox@example.com', - age: 16, - }), - ]); + await context.with(suppressTracing(context.active()), async () => { + await User.insertMany([ + new User({ + firstName: 'John', + lastName: 'Doe', + email: 'john.doe@example.com', + age: 18, + }), + new User({ + firstName: 'Jane', + lastName: 'Doe', + email: 'jane.doe@example.com', + age: 19, + }), + new User({ + firstName: 'Michael', + lastName: 'Fox', + email: 'michael.fox@example.com', + age: 16, + }), + ]); + }); await User.createIndexes(); }; From 7475099175c25d50cf92456c3511fdda6a6ae520 Mon Sep 17 00:00:00 2001 From: Nazar Vovk Date: Sat, 17 May 2025 17:44:41 +0300 Subject: [PATCH 2/5] fix tests --- .../test/mongoose-common.test.ts | 2 +- .../test/mongoose-v5-v6.test.ts | 2 +- .../test/mongoose-v7-v8.test.ts | 2 +- .../instrumentation-mongoose/test/user.ts | 42 +++++++++---------- 4 files changed, 23 insertions(+), 25 deletions(-) diff --git a/plugins/node/instrumentation-mongoose/test/mongoose-common.test.ts b/plugins/node/instrumentation-mongoose/test/mongoose-common.test.ts index 06c0bebfc1..7c37e9ed15 100644 --- a/plugins/node/instrumentation-mongoose/test/mongoose-common.test.ts +++ b/plugins/node/instrumentation-mongoose/test/mongoose-common.test.ts @@ -74,9 +74,9 @@ describe('mongoose instrumentation [common]', () => { }); }, }); - instrumentation.enable(); await loadUsers(); await User.createIndexes(); + instrumentation.enable(); }); afterEach(async () => { 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..16eabce991 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,9 @@ describe('mongoose instrumentation [v5/v6]', () => { }); }, }); - instrumentation.enable(); await loadUsers(); await User.createIndexes(); + instrumentation.enable(); }); afterEach(async () => { 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..b1ce471fc8 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,9 @@ describe('mongoose instrumentation [v7/v8]', () => { }); }, }); - instrumentation.enable(); await loadUsers(); await User.createIndexes(); + instrumentation.enable(); }); afterEach(async () => { diff --git a/plugins/node/instrumentation-mongoose/test/user.ts b/plugins/node/instrumentation-mongoose/test/user.ts index 6236d837c3..3bdcd18b75 100644 --- a/plugins/node/instrumentation-mongoose/test/user.ts +++ b/plugins/node/instrumentation-mongoose/test/user.ts @@ -37,27 +37,25 @@ const User = mongoose.model('User', UserSchema); export default User; export const loadUsers = async () => { - await context.with(suppressTracing(context.active()), async () => { - await User.insertMany([ - new User({ - firstName: 'John', - lastName: 'Doe', - email: 'john.doe@example.com', - age: 18, - }), - new User({ - firstName: 'Jane', - lastName: 'Doe', - email: 'jane.doe@example.com', - age: 19, - }), - new User({ - firstName: 'Michael', - lastName: 'Fox', - email: 'michael.fox@example.com', - age: 16, - }), - ]); - }); + await User.insertMany([ + new User({ + firstName: 'John', + lastName: 'Doe', + email: 'john.doe@example.com', + age: 18, + }), + new User({ + firstName: 'Jane', + lastName: 'Doe', + email: 'jane.doe@example.com', + age: 19, + }), + new User({ + firstName: 'Michael', + lastName: 'Fox', + email: 'michael.fox@example.com', + age: 16, + }), + ]); await User.createIndexes(); }; From 167682d42bab7dee9891b1b3ebf80b89c6c7c5e6 Mon Sep 17 00:00:00 2001 From: Nazar Vovk Date: Mon, 19 May 2025 22:58:04 +0300 Subject: [PATCH 3/5] latest fixes and additional tests --- .../node/instrumentation-mongoose/.tav.yml | 6 +- .../instrumentation-mongoose/src/mongoose.ts | 5 +- .../instrumentation-mongoose/src/utils.ts | 2 + .../test/mongoose-common.test.ts | 1 - .../test/mongoose-v5-v6.test.ts | 56 ++++++++++++++++++- .../test/mongoose-v7-v8.test.ts | 1 - .../instrumentation-mongoose/test/user.ts | 2 - 7 files changed, 60 insertions(+), 13 deletions(-) diff --git a/plugins/node/instrumentation-mongoose/.tav.yml b/plugins/node/instrumentation-mongoose/.tav.yml index 41a504deb9..ba89e40faf 100644 --- a/plugins/node/instrumentation-mongoose/.tav.yml +++ b/plugins/node/instrumentation-mongoose/.tav.yml @@ -1,10 +1,6 @@ mongoose: - versions: - include: ">=5.10.19 <6" - mode: latest-minors - commands: npm run test-v5-v6 - - versions: - include: ">=6.7.5 <7" + include: ">=5.9.7 <7" mode: latest-minors commands: npm run test-v5-v6 - versions: diff --git a/plugins/node/instrumentation-mongoose/src/mongoose.ts b/plugins/node/instrumentation-mongoose/src/mongoose.ts index f3cbc261e3..30468e6f67 100644 --- a/plugins/node/instrumentation-mongoose/src/mongoose.ts +++ b/plugins/node/instrumentation-mongoose/src/mongoose.ts @@ -99,7 +99,7 @@ export class MongooseInstrumentation extends InstrumentationBase=5.10.19 <6', '>=6.7.5 <9'], + ['>=5.9.7 <9'], this.patch.bind(this), this.unpatch.bind(this) ); @@ -343,8 +343,7 @@ export class MongooseInstrumentation extends InstrumentationBase { diff --git a/plugins/node/instrumentation-mongoose/test/mongoose-common.test.ts b/plugins/node/instrumentation-mongoose/test/mongoose-common.test.ts index 7c37e9ed15..ed954b8b54 100644 --- a/plugins/node/instrumentation-mongoose/test/mongoose-common.test.ts +++ b/plugins/node/instrumentation-mongoose/test/mongoose-common.test.ts @@ -75,7 +75,6 @@ describe('mongoose instrumentation [common]', () => { }, }); await loadUsers(); - await User.createIndexes(); instrumentation.enable(); }); 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 16eabce991..be6f5fc5ed 100644 --- a/plugins/node/instrumentation-mongoose/test/mongoose-v5-v6.test.ts +++ b/plugins/node/instrumentation-mongoose/test/mongoose-v5-v6.test.ts @@ -72,7 +72,6 @@ describe('mongoose instrumentation [v5/v6]', () => { }, }); await loadUsers(); - await User.createIndexes(); instrumentation.enable(); }); @@ -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 b1ce471fc8..2ef423f47f 100644 --- a/plugins/node/instrumentation-mongoose/test/mongoose-v7-v8.test.ts +++ b/plugins/node/instrumentation-mongoose/test/mongoose-v7-v8.test.ts @@ -69,7 +69,6 @@ describe('mongoose instrumentation [v7/v8]', () => { }, }); await loadUsers(); - await User.createIndexes(); instrumentation.enable(); }); diff --git a/plugins/node/instrumentation-mongoose/test/user.ts b/plugins/node/instrumentation-mongoose/test/user.ts index 3bdcd18b75..0e8eff02eb 100644 --- a/plugins/node/instrumentation-mongoose/test/user.ts +++ b/plugins/node/instrumentation-mongoose/test/user.ts @@ -15,8 +15,6 @@ */ import { Schema, Document } from 'mongoose'; import * as mongoose from 'mongoose'; -import { context } from '@opentelemetry/api'; -import { suppressTracing } from '@opentelemetry/core'; export interface IUser extends Document { email: string; From 7bd1296d45fe5bc92a4657f1a0e07f56537d6f17 Mon Sep 17 00:00:00 2001 From: Nazar Vovk Date: Mon, 19 May 2025 23:11:07 +0300 Subject: [PATCH 4/5] readme fix --- plugins/node/instrumentation-mongoose/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/node/instrumentation-mongoose/README.md b/plugins/node/instrumentation-mongoose/README.md index acdcc31325..31a19b4e7d 100644 --- a/plugins/node/instrumentation-mongoose/README.md +++ b/plugins/node/instrumentation-mongoose/README.md @@ -17,7 +17,7 @@ npm install --save @opentelemetry/instrumentation-mongoose ## Supported Versions -- [`mongoose`](https://www.npmjs.com/package/mongoose) versions `>=5.10.19 <6` and `>=6.7.5 <9` +- [`mongoose`](https://www.npmjs.com/package/mongoose) versions `>=5.9.7 <9` ## Usage From e1f15c110478843a9a1ed1da41e165f25da82330 Mon Sep 17 00:00:00 2001 From: Nazar Vovk Date: Mon, 19 May 2025 23:28:48 +0300 Subject: [PATCH 5/5] lint --- plugins/node/instrumentation-mongoose/src/utils.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/node/instrumentation-mongoose/src/utils.ts b/plugins/node/instrumentation-mongoose/src/utils.ts index 2e31ce25b5..72417b3f84 100644 --- a/plugins/node/instrumentation-mongoose/src/utils.ts +++ b/plugins/node/instrumentation-mongoose/src/utils.ts @@ -105,7 +105,7 @@ export function handleCallbackResponse( let callbackArgumentIndex = 0; if (args.length === 2) { callbackArgumentIndex = 1; - } else if (args.length === 3){ + } else if (args.length === 3) { callbackArgumentIndex = 2; }