Skip to content

Commit 1ec1c8a

Browse files
committed
fix instrumentation of ESM-imported mongoose
1 parent d579630 commit 1ec1c8a

File tree

3 files changed

+102
-2
lines changed

3 files changed

+102
-2
lines changed

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

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -107,9 +107,14 @@ export class MongooseInstrumentation extends InstrumentationBase<MongooseInstrum
107107
}
108108

109109
private patch(
110-
moduleExports: typeof mongoose,
110+
module: any,
111111
moduleVersion: string | undefined
112112
) {
113+
const moduleExports: typeof mongoose =
114+
module[Symbol.toStringTag] === 'Module'
115+
? module.default // ESM
116+
: module; // CommonJS
117+
113118
this._wrap(
114119
moduleExports.Model.prototype,
115120
'save',
@@ -155,9 +160,14 @@ export class MongooseInstrumentation extends InstrumentationBase<MongooseInstrum
155160
}
156161

157162
private unpatch(
158-
moduleExports: typeof mongoose,
163+
module: any,
159164
moduleVersion: string | undefined
160165
): void {
166+
const moduleExports: typeof mongoose =
167+
module[Symbol.toStringTag] === 'Module'
168+
? module.default // ESM
169+
: module; // CommonJS
170+
161171
const contextCaptureFunctions = getContextCaptureFunctions(moduleVersion);
162172

163173
this._unwrap(moduleExports.Model.prototype, 'save');
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
/*
2+
* Copyright The OpenTelemetry Authors
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
// Use mongoose from an ES module:
18+
// node --experimental-loader=@opentelemetry/instrumentation/hook.mjs use-mongoose.mjs [MONGO_URL] [DB_NAME
19+
20+
import { trace } from '@opentelemetry/api';
21+
import { createTestNodeSdk } from '@opentelemetry/contrib-test-utils';
22+
23+
import { MongooseInstrumentation } from '../../build/src/index.js';
24+
25+
const sdk = createTestNodeSdk({
26+
serviceName: 'use-mongoose',
27+
instrumentations: [new MongooseInstrumentation()],
28+
});
29+
sdk.start();
30+
31+
import assert from 'assert';
32+
import mongoose from 'mongoose';
33+
34+
const MONGO_URL = process.argv[2] || '';
35+
const DB_NAME = process.argv[3] || '';
36+
37+
const randomId = ((Math.random() * 2 ** 32) >>> 0).toString(16);
38+
39+
await mongoose.connect(MONGO_URL, {
40+
dbName: DB_NAME,
41+
});
42+
43+
const TestModel = mongoose.model(
44+
'Test',
45+
new mongoose.Schema({
46+
name: String,
47+
})
48+
);
49+
50+
const tracer = trace.getTracer('mongoose-instrumentation');
51+
await tracer.startActiveSpan('manual', async span => {
52+
const name = `test-${randomId}`;
53+
const [createdDoc] = await TestModel.create([{ name }]);
54+
55+
const doc = await TestModel.findOne({ name }).exec();
56+
57+
assert(doc && createdDoc?._id.toString() === doc?._id.toString());
58+
59+
span.end();
60+
});
61+
62+
await mongoose.disconnect();
63+
await sdk.shutdown();

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

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,10 @@
1414
* limitations under the License.
1515
*/
1616
import 'mocha';
17+
import * as assert from 'assert';
1718
import { expect } from 'expect';
1819
import { context, ROOT_CONTEXT } from '@opentelemetry/api';
20+
import * as testUtils from '@opentelemetry/contrib-test-utils';
1921
import {
2022
SEMATTRS_DB_OPERATION,
2123
SEMATTRS_DB_STATEMENT,
@@ -35,6 +37,8 @@ import * as mongoose from 'mongoose';
3537
import User, { IUser, loadUsers } from './user';
3638
import { assertSpan, getStatement } from './asserts';
3739
import { DB_NAME, MONGO_URI } from './config';
40+
import * as testUtils from "@opentelemetry/contrib-test-utils";
41+
import assert from "assert";
3842

3943
// Please run mongodb in the background: docker run -d -p 27017:27017 -v ~/data:/data/db mongo
4044
describe('mongoose instrumentation [common]', () => {
@@ -548,4 +552,27 @@ describe('mongoose instrumentation [common]', () => {
548552
expect(spans.length).toBe(0);
549553
});
550554
});
555+
556+
it('should work with ESM usage', async () => {
557+
await testUtils.runTestFixture({
558+
cwd: __dirname,
559+
argv: ['fixtures/use-mongoose.mjs', MONGO_URI, DB_NAME],
560+
env: {
561+
NODE_OPTIONS:
562+
'--experimental-loader=@opentelemetry/instrumentation/hook.mjs',
563+
NODE_NO_WARNINGS: '1',
564+
},
565+
checkResult: (err, stdout, stderr) => {
566+
assert.ifError(err);
567+
},
568+
checkCollector: (collector: testUtils.TestCollector) => {
569+
const spans = collector.sortedSpans;
570+
assert.strictEqual(spans[0].name, 'manual');
571+
assert.strictEqual(spans[1].name, 'mongoose.Test.save');
572+
assert.strictEqual(spans[1].parentSpanId, spans[0].spanId);
573+
assert.strictEqual(spans[2].name, 'mongoose.Test.findOne');
574+
assert.strictEqual(spans[2].parentSpanId, spans[0].spanId);
575+
},
576+
});
577+
});
551578
});

0 commit comments

Comments
 (0)