Skip to content

Commit 23d2923

Browse files
committed
Merge branch 'main' of github.com:open-telemetry/opentelemetry-js-contrib
2 parents 12f6b2b + b3a70d7 commit 23d2923

File tree

10 files changed

+789
-142
lines changed

10 files changed

+789
-142
lines changed

package-lock.json

Lines changed: 509 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

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

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -106,10 +106,12 @@ export class MongooseInstrumentation extends InstrumentationBase<MongooseInstrum
106106
return module;
107107
}
108108

109-
private patch(
110-
moduleExports: typeof mongoose,
111-
moduleVersion: string | undefined
112-
) {
109+
private patch(module: any, moduleVersion: string | undefined) {
110+
const moduleExports: typeof mongoose =
111+
module[Symbol.toStringTag] === 'Module'
112+
? module.default // ESM
113+
: module; // CommonJS
114+
113115
this._wrap(
114116
moduleExports.Model.prototype,
115117
'save',
@@ -154,10 +156,12 @@ export class MongooseInstrumentation extends InstrumentationBase<MongooseInstrum
154156
return moduleExports;
155157
}
156158

157-
private unpatch(
158-
moduleExports: typeof mongoose,
159-
moduleVersion: string | undefined
160-
): void {
159+
private unpatch(module: any, moduleVersion: string | undefined): void {
160+
const moduleExports: typeof mongoose =
161+
module[Symbol.toStringTag] === 'Module'
162+
? module.default // ESM
163+
: module; // CommonJS
164+
161165
const contextCaptureFunctions = getContextCaptureFunctions(moduleVersion);
162166

163167
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: 25 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,
@@ -548,4 +550,27 @@ describe('mongoose instrumentation [common]', () => {
548550
expect(spans.length).toBe(0);
549551
});
550552
});
553+
554+
it('should work with ESM usage', async () => {
555+
await testUtils.runTestFixture({
556+
cwd: __dirname,
557+
argv: ['fixtures/use-mongoose.mjs', MONGO_URI, DB_NAME],
558+
env: {
559+
NODE_OPTIONS:
560+
'--experimental-loader=@opentelemetry/instrumentation/hook.mjs',
561+
NODE_NO_WARNINGS: '1',
562+
},
563+
checkResult: (err, stdout, stderr) => {
564+
assert.ifError(err);
565+
},
566+
checkCollector: (collector: testUtils.TestCollector) => {
567+
const spans = collector.sortedSpans;
568+
assert.strictEqual(spans[0].name, 'manual');
569+
assert.strictEqual(spans[1].name, 'mongoose.Test.save');
570+
assert.strictEqual(spans[1].parentSpanId, spans[0].spanId);
571+
assert.strictEqual(spans[2].name, 'mongoose.Test.findOne');
572+
assert.strictEqual(spans[2].parentSpanId, spans[0].spanId);
573+
},
574+
});
575+
});
551576
});
Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
express:
22
- versions:
3-
include: "^4.16.2"
3+
include: ">=4.16.2 <6"
44
mode: latest-minors
5-
commands: npm run test
5+
commands: npm test

plugins/node/opentelemetry-instrumentation-express/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@
5353
"@types/mocha": "10.0.10",
5454
"@types/node": "18.18.14",
5555
"@types/sinon": "17.0.4",
56-
"express": "4.20.0",
56+
"express": "^5.1.0",
5757
"nyc": "17.1.0",
5858
"rimraf": "5.0.10",
5959
"sinon": "15.2.0",

plugins/node/opentelemetry-instrumentation-express/src/instrumentation.ts

Lines changed: 24 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -60,9 +60,13 @@ export class ExpressInstrumentation extends InstrumentationBase<ExpressInstrumen
6060
return [
6161
new InstrumentationNodeModuleDefinition(
6262
'express',
63-
['>=4.0.0 <5'],
63+
['>=4.0.0 <6'],
6464
moduleExports => {
65-
const routerProto = moduleExports.Router as unknown as express.Router;
65+
const isExpressWithRouterPrototype =
66+
typeof moduleExports?.Router?.prototype?.route === 'function';
67+
const routerProto = isExpressWithRouterPrototype
68+
? moduleExports.Router.prototype // Express v5
69+
: moduleExports.Router; // Express v4
6670
// patch express.Router.route
6771
if (isWrapped(routerProto.route)) {
6872
this._unwrap(routerProto, 'route');
@@ -82,13 +86,17 @@ export class ExpressInstrumentation extends InstrumentationBase<ExpressInstrumen
8286
moduleExports.application,
8387
'use',
8488
// eslint-disable-next-line @typescript-eslint/no-explicit-any
85-
this._getAppUsePatch() as any
89+
this._getAppUsePatch(isExpressWithRouterPrototype) as any
8690
);
8791
return moduleExports;
8892
},
8993
moduleExports => {
9094
if (moduleExports === undefined) return;
91-
const routerProto = moduleExports.Router as unknown as express.Router;
95+
const isExpressWithRouterPrototype =
96+
typeof moduleExports?.Router?.prototype?.route === 'function';
97+
const routerProto = isExpressWithRouterPrototype
98+
? moduleExports.Router.prototype
99+
: moduleExports.Router;
92100
this._unwrap(routerProto, 'route');
93101
this._unwrap(routerProto, 'use');
94102
this._unwrap(moduleExports.application, 'use');
@@ -136,16 +144,24 @@ export class ExpressInstrumentation extends InstrumentationBase<ExpressInstrumen
136144
/**
137145
* Get the patch for Application.use function
138146
*/
139-
private _getAppUsePatch() {
147+
private _getAppUsePatch(isExpressWithRouterPrototype: boolean) {
140148
const instrumentation = this;
141149
return function (original: express.Application['use']) {
142150
return function use(
143-
this: { _router: ExpressRouter },
151+
// `router` in express@5, `_router` in express@4.
152+
this: { _router?: ExpressRouter; router?: ExpressRouter },
144153
...args: Parameters<typeof original>
145154
) {
155+
// If we access app.router in express 4.x we trigger an assertion error.
156+
// This property existed in v3, was removed in v4 and then re-added in v5.
157+
const router = isExpressWithRouterPrototype
158+
? this.router
159+
: this._router;
146160
const route = original.apply(this, args);
147-
const layer = this._router.stack[this._router.stack.length - 1];
148-
instrumentation._applyPatch(layer, getLayerPath(args));
161+
if (router) {
162+
const layer = router.stack[router.stack.length - 1];
163+
instrumentation._applyPatch(layer, getLayerPath(args));
164+
}
149165
return route;
150166
};
151167
};

plugins/node/opentelemetry-instrumentation-express/src/utils.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,7 @@ export const getLayerMetadata = (
9797
},
9898
name: `router - ${extractedRouterPath}`,
9999
};
100-
} else if (layer.name === 'bound dispatch') {
100+
} else if (layer.name === 'bound dispatch' || layer.name === 'handle') {
101101
return {
102102
attributes: {
103103
[AttributeNames.EXPRESS_NAME]:

0 commit comments

Comments
 (0)