Skip to content

Commit bfac69d

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

File tree

6 files changed

+213
-23
lines changed

6 files changed

+213
-23
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/README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ npm install --save @opentelemetry/instrumentation-mongoose
1717

1818
## Supported Versions
1919

20-
- [`mongoose`](https://www.npmjs.com/package/mongoose) versions `>=5.9.7 <9`
20+
- [`mongoose`](https://www.npmjs.com/package/mongoose) versions `>=5.10.19 <6` and `>=6.7.5 <9`
2121

2222
## Usage
2323

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

Lines changed: 80 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -99,7 +99,7 @@ export class MongooseInstrumentation extends InstrumentationBase<MongooseInstrum
9999
protected init(): InstrumentationModuleDefinition {
100100
const module = new InstrumentationNodeModuleDefinition(
101101
'mongoose',
102-
['>=5.9.7 <9'],
102+
['>=5.10.19 <6', '>=6.7.5 <9'],
103103
this.patch.bind(this),
104104
this.unpatch.bind(this)
105105
);
@@ -153,6 +153,17 @@ export class MongooseInstrumentation extends InstrumentationBase<MongooseInstrum
153153
});
154154
this._wrap(moduleExports.Model, 'aggregate', this.patchModelAggregate());
155155

156+
this._wrap(
157+
moduleExports.Model,
158+
'insertMany',
159+
this.patchModelStatic('insertMany', moduleVersion)
160+
);
161+
this._wrap(
162+
moduleExports.Model,
163+
'bulkWrite',
164+
this.patchModelStatic('bulkWrite', moduleVersion)
165+
);
166+
156167
return moduleExports;
157168
}
158169

@@ -179,6 +190,9 @@ export class MongooseInstrumentation extends InstrumentationBase<MongooseInstrum
179190
this._unwrap(moduleExports.Query.prototype, funcName as any);
180191
});
181192
this._unwrap(moduleExports.Model, 'aggregate');
193+
194+
this._unwrap(moduleExports.Model, 'insertMany');
195+
this._unwrap(moduleExports.Model, 'bulkWrite');
182196
}
183197

184198
private patchAggregateExec(moduleVersion: string | undefined) {
@@ -314,6 +328,71 @@ export class MongooseInstrumentation extends InstrumentationBase<MongooseInstrum
314328
};
315329
}
316330

331+
private patchModelStatic(op: string, moduleVersion: string | undefined) {
332+
const self = this;
333+
return (original: Function) => {
334+
return function patchedStatic(
335+
this: any,
336+
docsOrOps: any,
337+
options?: any,
338+
callback?: Function
339+
) {
340+
if (
341+
self.getConfig().requireParentSpan &&
342+
trace.getSpan(context.active()) === undefined
343+
) {
344+
return original.apply(this, arguments);
345+
}
346+
347+
if (options instanceof Function) {
348+
callback = options;
349+
options = undefined;
350+
}
351+
352+
const serializePayload: SerializerPayload = {};
353+
switch (op) {
354+
case 'insertMany':
355+
serializePayload.documents = docsOrOps;
356+
break;
357+
case 'bulkWrite':
358+
serializePayload.operations = docsOrOps;
359+
break;
360+
default:
361+
serializePayload.document = docsOrOps;
362+
break;
363+
}
364+
if (options !== undefined) {
365+
serializePayload.options = options;
366+
}
367+
368+
const attributes: Attributes = {};
369+
const { dbStatementSerializer } = self.getConfig();
370+
if (dbStatementSerializer) {
371+
attributes[SEMATTRS_DB_STATEMENT] = dbStatementSerializer(
372+
op,
373+
serializePayload
374+
);
375+
}
376+
377+
const span = self._startSpan(
378+
this.collection,
379+
this.modelName,
380+
op,
381+
attributes
382+
);
383+
384+
return self._handleResponse(
385+
span,
386+
original,
387+
this,
388+
arguments,
389+
callback,
390+
moduleVersion
391+
);
392+
};
393+
};
394+
}
395+
317396
// we want to capture the otel span on the object which is calling exec.
318397
// in the special case of aggregate, we need have no function to path
319398
// 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
@@ -321,6 +321,107 @@ describe('mongoose instrumentation [common]', () => {
321321
expect(statement.document).toEqual(expect.objectContaining(document));
322322
});
323323

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