Skip to content

Commit cd106dc

Browse files
committed
feat(instrumentation-mongoose)!: add instrumentation of static methods
1 parent 1e2db67 commit cd106dc

File tree

5 files changed

+211
-21
lines changed

5 files changed

+211
-21
lines changed

plugins/node/instrumentation-mongoose/.tav.yml

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
11
mongoose:
22
- versions:
3-
include: ">=5.9.7 <7"
3+
include: ">=5.10.19 <6"
4+
mode: latest-minors
5+
commands: npm run test-v5-v6
6+
- versions:
7+
include: ">=6.7.5 <7"
48
mode: latest-minors
59
commands: npm run test-v5-v6
610
- versions:

plugins/node/instrumentation-mongoose/src/mongoose.ts

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -151,6 +151,17 @@ export class MongooseInstrumentation extends InstrumentationBase<MongooseInstrum
151151
});
152152
this._wrap(moduleExports.Model, 'aggregate', this.patchModelAggregate());
153153

154+
this._wrap(
155+
moduleExports.Model,
156+
'insertMany',
157+
this.patchModelStatic('insertMany', moduleVersion)
158+
);
159+
this._wrap(
160+
moduleExports.Model,
161+
'bulkWrite',
162+
this.patchModelStatic('bulkWrite', moduleVersion)
163+
);
164+
154165
return moduleExports;
155166
}
156167

@@ -175,6 +186,9 @@ export class MongooseInstrumentation extends InstrumentationBase<MongooseInstrum
175186
this._unwrap(moduleExports.Query.prototype, funcName as any);
176187
});
177188
this._unwrap(moduleExports.Model, 'aggregate');
189+
190+
this._unwrap(moduleExports.Model, 'insertMany');
191+
this._unwrap(moduleExports.Model, 'bulkWrite');
178192
}
179193

180194
private patchAggregateExec(moduleVersion: string | undefined) {
@@ -310,6 +324,71 @@ export class MongooseInstrumentation extends InstrumentationBase<MongooseInstrum
310324
};
311325
}
312326

327+
private patchModelStatic(op: string, moduleVersion: string | undefined) {
328+
const self = this;
329+
return (original: Function) => {
330+
return function patchedStatic(
331+
this: any,
332+
docsOrOps: any,
333+
options?: any,
334+
callback?: Function
335+
) {
336+
if (
337+
self.getConfig().requireParentSpan &&
338+
trace.getSpan(context.active()) === undefined
339+
) {
340+
return original.apply(this, arguments);
341+
}
342+
343+
if (options instanceof Function) {
344+
callback = options;
345+
options = undefined;
346+
}
347+
348+
const serializePayload: SerializerPayload = {};
349+
switch (op) {
350+
case 'insertMany':
351+
serializePayload.documents = docsOrOps;
352+
break;
353+
case 'bulkWrite':
354+
serializePayload.operations = docsOrOps;
355+
break;
356+
default:
357+
serializePayload.document = docsOrOps;
358+
break;
359+
}
360+
if (options !== undefined) {
361+
serializePayload.options = options;
362+
}
363+
364+
const attributes: Attributes = {};
365+
const { dbStatementSerializer } = self.getConfig();
366+
if (dbStatementSerializer) {
367+
attributes[SEMATTRS_DB_STATEMENT] = dbStatementSerializer(
368+
op,
369+
serializePayload
370+
);
371+
}
372+
373+
const span = self._startSpan(
374+
this.collection,
375+
this.modelName,
376+
op,
377+
attributes
378+
);
379+
380+
return self._handleResponse(
381+
span,
382+
original,
383+
this,
384+
arguments,
385+
callback,
386+
moduleVersion
387+
);
388+
};
389+
};
390+
}
391+
313392
// we want to capture the otel span on the object which is calling exec.
314393
// in the special case of aggregate, we need have no function to path
315394
// on the Aggregate object to capture the context on, so we patch

plugins/node/instrumentation-mongoose/src/types.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@ export interface SerializerPayload {
2323
document?: any;
2424
aggregatePipeline?: any;
2525
fields?: any;
26+
documents?: any;
27+
operations?: any;
2628
}
2729

2830
export type DbStatementSerializer = (

plugins/node/instrumentation-mongoose/test/mongoose-common.test.ts

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -319,6 +319,107 @@ describe('mongoose instrumentation [common]', () => {
319319
expect(statement.document).toEqual(expect.objectContaining(document));
320320
});
321321

322+
it('instrumenting insertMany operation', async () => {
323+
const documents = [
324+
{
325+
firstName: 'John',
326+
lastName: 'Doe',
327+
328+
},
329+
{
330+
firstName: 'Jane',
331+
lastName: 'Doe',
332+
333+
},
334+
];
335+
await User.insertMany(documents);
336+
337+
const spans = getTestSpans();
338+
expect(spans.length).toBe(1);
339+
assertSpan(spans[0] as ReadableSpan);
340+
expect(spans[0].attributes[SEMATTRS_DB_OPERATION]).toBe('insertMany');
341+
const statement = getStatement(spans[0] as ReadableSpan);
342+
expect(statement.documents).toEqual(documents);
343+
});
344+
345+
it('instrumenting bulkWrite operation', async () => {
346+
const operations = [
347+
{
348+
insertOne: {
349+
document: {
350+
firstName: 'Jane',
351+
lastName: 'Doe',
352+
353+
age: 25,
354+
},
355+
},
356+
},
357+
{
358+
updateMany: {
359+
filter: { age: { $lte: 20 } },
360+
update: { $set: { age: 20 } },
361+
},
362+
},
363+
{
364+
updateOne: {
365+
filter: { firstName: 'Jane' },
366+
update: { $inc: { age: 1 } },
367+
},
368+
},
369+
{ deleteOne: { filter: { firstName: 'Michael' } } },
370+
{
371+
updateOne: {
372+
filter: { firstName: 'Zara' },
373+
update: {
374+
$set: { lastName: 'Doe', age: 40, email: '[email protected]' },
375+
},
376+
upsert: true,
377+
},
378+
},
379+
];
380+
await User.bulkWrite(operations);
381+
382+
const spans = getTestSpans();
383+
expect(spans.length).toBe(1);
384+
assertSpan(spans[0] as ReadableSpan);
385+
expect(spans[0].attributes[SEMATTRS_DB_OPERATION]).toBe('bulkWrite');
386+
const statement = getStatement(spans[0] as ReadableSpan);
387+
expect(statement.operations).toEqual([
388+
{
389+
insertOne: {
390+
document: {
391+
firstName: 'Jane',
392+
lastName: 'Doe',
393+
394+
age: 25,
395+
},
396+
},
397+
},
398+
{
399+
updateMany: {
400+
filter: { age: { $lte: 20 } },
401+
update: { $set: { age: 20 } },
402+
},
403+
},
404+
{
405+
updateOne: {
406+
filter: { firstName: 'Jane' },
407+
update: { $inc: { age: 1 } },
408+
},
409+
},
410+
{ deleteOne: { filter: { firstName: 'Michael' } } },
411+
{
412+
updateOne: {
413+
filter: { firstName: 'Zara' },
414+
update: {
415+
$set: { lastName: 'Doe', age: 40, email: '[email protected]' },
416+
},
417+
upsert: true,
418+
},
419+
},
420+
]);
421+
});
422+
322423
it('instrumenting aggregate operation', async () => {
323424
await User.aggregate([
324425
{ $match: { firstName: 'John' } },

plugins/node/instrumentation-mongoose/test/user.ts

Lines changed: 24 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@
1515
*/
1616
import { Schema, Document } from 'mongoose';
1717
import * as mongoose from 'mongoose';
18+
import { context } from '@opentelemetry/api';
19+
import { suppressTracing } from '@opentelemetry/core';
1820

1921
export interface IUser extends Document {
2022
email: string;
@@ -35,25 +37,27 @@ const User = mongoose.model<IUser>('User', UserSchema);
3537
export default User;
3638

3739
export const loadUsers = async () => {
38-
await User.insertMany([
39-
new User({
40-
firstName: 'John',
41-
lastName: 'Doe',
42-
43-
age: 18,
44-
}),
45-
new User({
46-
firstName: 'Jane',
47-
lastName: 'Doe',
48-
49-
age: 19,
50-
}),
51-
new User({
52-
firstName: 'Michael',
53-
lastName: 'Fox',
54-
55-
age: 16,
56-
}),
57-
]);
40+
await context.with(suppressTracing(context.active()), async () => {
41+
await User.insertMany([
42+
new User({
43+
firstName: 'John',
44+
lastName: 'Doe',
45+
46+
age: 18,
47+
}),
48+
new User({
49+
firstName: 'Jane',
50+
lastName: 'Doe',
51+
52+
age: 19,
53+
}),
54+
new User({
55+
firstName: 'Michael',
56+
lastName: 'Fox',
57+
58+
age: 16,
59+
}),
60+
]);
61+
});
5862
await User.createIndexes();
5963
};

0 commit comments

Comments
 (0)