Skip to content

Commit

Permalink
fix(instrumentation-mongoose): Fix instrumentation for Mongoose Model…
Browse files Browse the repository at this point in the history
… methods, save() and remove(), by passing options argument to original method calls (#2009)

* Fixing Mongoose instrumentation on Model methods with callback + tests

* Fixing and adding more tests

* Removing skip on existing failing test

* Renaming callback index variable

* use exported strings for attributes

* Defining args as IArguments and updating its values directly

* Fix lint issues

* Replacing tests with session option to use wtimeout to avoid the need of transactions in the instrumentation test

---------

Co-authored-by: Amir Blum <[email protected]>
  • Loading branch information
ferrelucas and blumamir authored May 24, 2024
1 parent 20bc736 commit 96eb7dc
Show file tree
Hide file tree
Showing 3 changed files with 99 additions and 21 deletions.
1 change: 1 addition & 0 deletions plugins/node/instrumentation-mongoose/src/mongoose.ts
Original file line number Diff line number Diff line change
Expand Up @@ -330,6 +330,7 @@ export class MongooseInstrumentation extends InstrumentationBase {
exec,
originalThis,
span,
args,
self._config.responseHook,
moduleVersion
)
Expand Down
25 changes: 16 additions & 9 deletions plugins/node/instrumentation-mongoose/src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -98,16 +98,23 @@ export function handleCallbackResponse(
exec: Function,
originalThis: any,
span: Span,
args: IArguments,
responseHook?: MongooseResponseCustomAttributesFunction,
moduleVersion: string | undefined = undefined
) {
return exec.apply(originalThis, [
(err: Error, response: any) => {
err
? setErrorStatus(span, err)
: applyResponseHook(span, response, responseHook, moduleVersion);
span.end();
return callback!(err, response);
},
]);
let callbackArgumentIndex = 0;
if (args.length === 2) {
callbackArgumentIndex = 1;
}

args[callbackArgumentIndex] = (err: Error, response: any): any => {
err
? setErrorStatus(span, err)
: applyResponseHook(span, response, responseHook, moduleVersion);

span.end();
return callback!(err, response);
};

return exec.apply(originalThis, args);
}
94 changes: 82 additions & 12 deletions plugins/node/instrumentation-mongoose/test/mongoose.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -66,8 +66,11 @@ describe('mongoose instrumentation', () => {
beforeEach(async () => {
instrumentation.disable();
instrumentation.setConfig({
dbStatementSerializer: (_operation: string, payload) =>
JSON.stringify(payload),
dbStatementSerializer: (_operation: string, payload) => {
return JSON.stringify(payload, (key, value) => {
return key === 'session' ? '[Session]' : value;
});
},
});
instrumentation.enable();
await loadUsers();
Expand Down Expand Up @@ -97,23 +100,90 @@ describe('mongoose instrumentation', () => {
expect(statement.document).toEqual(expect.objectContaining(document));
});

it('instrumenting save operation with callback', done => {
const document = {
firstName: 'Test first name',
lastName: 'Test last name',
email: '[email protected]',
};
const user: IUser = new User(document);
describe('when save call does not have callback', async () => {
it('instrumenting save operation with option property set', async () => {
const document = {
firstName: 'Test first name',
lastName: 'Test last name',
email: '[email protected]',
};
const user: IUser = new User(document);
await user.save({ wtimeout: 42 });

user.save(() => {
const spans = getTestSpans();

expect(spans.length).toBe(1);
assertSpan(spans[0] as ReadableSpan);
expect(spans[0].attributes[SEMATTRS_DB_OPERATION]).toBe('save');
const statement = getStatement(spans[0] as ReadableSpan);
expect(statement.document).toEqual(expect.objectContaining(document));
done();
expect(statement.options.wtimeout).toEqual(42);

const createdUser = await User.findById(user._id).lean();
expect(createdUser?._id.toString()).toEqual(user._id.toString());
});
});

describe('when save call has callback', async () => {
it('instrumenting save operation with promise and option property set', done => {
const document = {
firstName: 'Test first name',
lastName: 'Test last name',
email: '[email protected]',
};
const user: IUser = new User(document);
user.save({ wtimeout: 42 }, async () => {
const spans = getTestSpans();
expect(spans.length).toBe(1);
assertSpan(spans[0] as ReadableSpan);
expect(spans[0].attributes[SEMATTRS_DB_OPERATION]).toBe('save');
const statement = getStatement(spans[0] as ReadableSpan);
expect(statement.document).toEqual(expect.objectContaining(document));
expect(statement.options.wtimeout).toEqual(42);

const createdUser = await User.findById(user._id).lean();
expect(createdUser?._id.toString()).toEqual(user._id.toString());
done();
});
});

it('instrumenting save operation with generic options and callback', done => {
const document = {
firstName: 'Test first name',
lastName: 'Test last name',
email: '[email protected]',
};
const user: IUser = new User(document);

user.save({}, () => {
const spans = getTestSpans();

expect(spans.length).toBe(1);
assertSpan(spans[0] as ReadableSpan);
expect(spans[0].attributes[SEMATTRS_DB_OPERATION]).toBe('save');
const statement = getStatement(spans[0] as ReadableSpan);
expect(statement.document).toEqual(expect.objectContaining(document));
done();
});
});

it('instrumenting save operation with only callback', done => {
const document = {
firstName: 'Test first name',
lastName: 'Test last name',
email: '[email protected]',
};
const user: IUser = new User(document);

user.save(() => {
const spans = getTestSpans();

expect(spans.length).toBe(1);
assertSpan(spans[0] as ReadableSpan);
expect(spans[0].attributes[SEMATTRS_DB_OPERATION]).toBe('save');
const statement = getStatement(spans[0] as ReadableSpan);
expect(statement.document).toEqual(expect.objectContaining(document));
done();
});
});
});

Expand Down

0 comments on commit 96eb7dc

Please sign in to comment.