-
-
Notifications
You must be signed in to change notification settings - Fork 1.8k
ref(node): Streamline mongoose instrumentation #21481
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
logaretm
merged 16 commits into
develop
from
awad/js-2389-streamline-opentelemetryinstrumentation-mongoose
Jun 15, 2026
Merged
Changes from all commits
Commits
Show all changes
16 commits
Select commit
Hold shift + click to select a range
7c07b75
ref(node): Swap mongoose vendored span lifecycle to Sentry APIs
logaretm ad5b5d2
ref(node): Prune dead mongoose instrumentation config types
logaretm 5707734
ref(node): Drop unused mongoose semconv constants
logaretm b36eb0c
chore: remove eslint ignores and type the error object
logaretm fb8a47b
test(node): Add mongoose instrumentation unit tests
logaretm 1349dd5
ref(node): Type mongoose module exports and satisfy lint
logaretm 5ef8d03
test(node): Cover aggregate/insertMany/bulkWrite in mongoose integrat…
logaretm 09d476c
ref(node): Nest mongodb driver spans under mongoose spans
logaretm 4da6f23
test(node): Add mongoose version matrix and lazy-Query regression guard
logaretm 6df56a1
test(node): Cover mongoose callback signature against real v6
logaretm 7d8cc89
test(node): Gate mongoose v9 integration suite to Node >=20
logaretm 53b74cb
test(node): Drop fake mongoose unit suite in favor of real integratio…
logaretm 4e32215
test(node): Cover failing mongoose operation produces an error span
logaretm cc3dcd7
test(node): Lock mongoose span nesting and cover v9 document methods
logaretm 2477a59
test(node): Cover mongoose `remove` and cross-context query parenting
logaretm 053fcd1
ref(node): Drop trivial mongoose types.ts, inline InstrumentationConfig
logaretm File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
9 changes: 9 additions & 0 deletions
9
dev-packages/node-integration-tests/suites/tracing/mongoose-v7/instrument.mjs
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,9 @@ | ||
| import * as Sentry from '@sentry/node'; | ||
| import { loggingTransport } from '@sentry-internal/node-integration-tests'; | ||
|
|
||
| Sentry.init({ | ||
| dsn: 'https://public@dsn.ingest.sentry.io/1337', | ||
| release: '1.0', | ||
| tracesSampleRate: 1.0, | ||
| transport: loggingTransport, | ||
| }); |
36 changes: 36 additions & 0 deletions
36
dev-packages/node-integration-tests/suites/tracing/mongoose-v7/scenario.mjs
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,36 @@ | ||
| import * as Sentry from '@sentry/node'; | ||
| import mongoose from 'mongoose'; | ||
|
|
||
| async function run() { | ||
| await mongoose.connect(process.env.MONGO_URL || ''); | ||
|
|
||
| const BlogPostSchema = new mongoose.Schema({ | ||
| title: String, | ||
| body: String, | ||
| date: Date, | ||
| }); | ||
|
|
||
| const BlogPost = mongoose.model('BlogPost', BlogPostSchema); | ||
|
|
||
| await Sentry.startSpan( | ||
| { | ||
| name: 'Test Transaction', | ||
| op: 'transaction', | ||
| }, | ||
| async () => { | ||
| const post = new BlogPost({ title: 'Test', body: 'Test body', date: new Date() }); | ||
|
|
||
| await post.save(); | ||
|
|
||
| await BlogPost.findOne({}); | ||
|
|
||
| await BlogPost.aggregate([{ $match: {} }]); | ||
|
|
||
| await BlogPost.insertMany([{ title: 'Insert', body: 'Insert body', date: new Date() }]); | ||
|
|
||
| await BlogPost.bulkWrite([{ insertOne: { document: { title: 'Bulk', body: 'Bulk body', date: new Date() } } }]); | ||
| }, | ||
| ); | ||
| } | ||
|
|
||
| run(); |
55 changes: 55 additions & 0 deletions
55
dev-packages/node-integration-tests/suites/tracing/mongoose-v7/test.ts
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,55 @@ | ||
| import { MongoMemoryServer } from 'mongodb-memory-server-global'; | ||
| import { afterAll, beforeAll, describe, expect } from 'vitest'; | ||
| import { cleanupChildProcesses, createEsmAndCjsTests } from '../../../utils/runner'; | ||
|
|
||
| // Pins mongoose 7 so the `contextCaptureFunctions7` version branch is exercised against a real mongoose. | ||
| describe('Mongoose v7 Test', () => { | ||
| let mongoServer: MongoMemoryServer; | ||
|
|
||
| beforeAll(async () => { | ||
| mongoServer = await MongoMemoryServer.create(); | ||
| process.env.MONGO_URL = mongoServer.getUri(); | ||
| }, 30000); | ||
|
|
||
| afterAll(async () => { | ||
| if (mongoServer) { | ||
| await mongoServer.stop(); | ||
| } | ||
| cleanupChildProcesses(); | ||
| }); | ||
|
|
||
| const expectedSpan = (operation: string) => | ||
| expect.objectContaining({ | ||
| data: expect.objectContaining({ | ||
| 'db.mongodb.collection': 'blogposts', | ||
| 'db.operation': operation, | ||
| 'db.system': 'mongoose', | ||
| }), | ||
| description: `mongoose.BlogPost.${operation}`, | ||
| op: 'db', | ||
| origin: 'auto.db.otel.mongoose', | ||
| }); | ||
|
|
||
| const EXPECTED_TRANSACTION = { | ||
| transaction: 'Test Transaction', | ||
| spans: expect.arrayContaining([ | ||
| expectedSpan('save'), | ||
| expectedSpan('findOne'), | ||
| expectedSpan('aggregate'), | ||
| expectedSpan('insertMany'), | ||
| expectedSpan('bulkWrite'), | ||
| ]), | ||
| }; | ||
|
|
||
| createEsmAndCjsTests( | ||
| __dirname, | ||
| 'scenario.mjs', | ||
| 'instrument.mjs', | ||
| (createTestRunner, test) => { | ||
| test('auto-instruments `mongoose` v7.', async () => { | ||
| await createTestRunner().expect({ transaction: EXPECTED_TRANSACTION }).start().completed(); | ||
| }); | ||
| }, | ||
| { additionalDependencies: { mongoose: '^7' } }, | ||
| ); | ||
| }); |
9 changes: 9 additions & 0 deletions
9
dev-packages/node-integration-tests/suites/tracing/mongoose-v8/instrument.mjs
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,9 @@ | ||
| import * as Sentry from '@sentry/node'; | ||
| import { loggingTransport } from '@sentry-internal/node-integration-tests'; | ||
|
|
||
| Sentry.init({ | ||
| dsn: 'https://public@dsn.ingest.sentry.io/1337', | ||
| release: '1.0', | ||
| tracesSampleRate: 1.0, | ||
| transport: loggingTransport, | ||
| }); |
56 changes: 56 additions & 0 deletions
56
dev-packages/node-integration-tests/suites/tracing/mongoose-v8/scenario.mjs
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,56 @@ | ||
| import * as Sentry from '@sentry/node'; | ||
| import mongoose from 'mongoose'; | ||
|
|
||
| async function run() { | ||
| await mongoose.connect(process.env.MONGO_URL || ''); | ||
|
|
||
| const BlogPostSchema = new mongoose.Schema({ | ||
| title: String, | ||
| body: String, | ||
| date: Date, | ||
| }); | ||
|
|
||
| const BlogPost = mongoose.model('BlogPost', BlogPostSchema); | ||
|
|
||
| await Sentry.startSpan( | ||
| { | ||
| name: 'Test Transaction', | ||
| op: 'transaction', | ||
| }, | ||
| async () => { | ||
| const post = new BlogPost({ title: 'Test', body: 'Test body', date: new Date() }); | ||
|
|
||
| await post.save(); | ||
|
|
||
| await BlogPost.findOne({}); | ||
|
|
||
| // Document instance methods. On mongoose 8.21.0+ these return a lazy Query that the | ||
| // instrumentation must hand back un-executed (regression guard for the thenable trap). | ||
| await post.updateOne({ title: 'Updated' }); | ||
|
|
||
| // Verify the update actually persisted (i.e. the query executed exactly when awaited). | ||
| const updated = await BlogPost.findById(post._id); | ||
| if (!updated || updated.title !== 'Updated') { | ||
| throw new Error(`updateOne did not persist as expected, got: ${updated && updated.title}`); | ||
| } | ||
|
|
||
| // Lazy-Query guard: a document updateOne returns a lazy Query that only runs when awaited. | ||
| // Building it without awaiting must NOT execute it — if the instrumentation runs it (e.g. by | ||
| // calling `.then()` on the returned thenable), this premature write would change the document. | ||
| const lazyDoc = await new BlogPost({ title: 'Original', body: 'b', date: new Date() }).save(); | ||
| // eslint-disable-next-line @typescript-eslint/no-floating-promises | ||
| lazyDoc.updateOne({ title: 'PrematurelyExecuted' }); | ||
| await new Promise(resolve => setTimeout(resolve, 250)); | ||
| const lazyCheck = await BlogPost.findById(lazyDoc._id); | ||
| if (!lazyCheck || lazyCheck.title !== 'Original') { | ||
| throw new Error( | ||
| `lazy updateOne was executed without being awaited (got title: ${lazyCheck && lazyCheck.title})`, | ||
| ); | ||
| } | ||
|
|
||
| await post.deleteOne(); | ||
| }, | ||
| ); | ||
| } | ||
|
|
||
| run(); | ||
69 changes: 69 additions & 0 deletions
69
dev-packages/node-integration-tests/suites/tracing/mongoose-v8/test.ts
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,69 @@ | ||
| import { MongoMemoryServer } from 'mongodb-memory-server-global'; | ||
| import { afterAll, beforeAll, describe, expect } from 'vitest'; | ||
| import { cleanupChildProcesses, createEsmAndCjsTests } from '../../../utils/runner'; | ||
|
|
||
| // Pins mongoose 8 (>= 8.21) so the document `updateOne`/`deleteOne` lazy-Query path is exercised | ||
| // against a real mongoose, guarding the thenable trap that mongoose 6 (the workspace version) can't hit. | ||
| describe('Mongoose v8 Test', () => { | ||
| let mongoServer: MongoMemoryServer; | ||
|
|
||
| beforeAll(async () => { | ||
| mongoServer = await MongoMemoryServer.create(); | ||
| process.env.MONGO_URL = mongoServer.getUri(); | ||
| }, 30000); | ||
|
|
||
| afterAll(async () => { | ||
| if (mongoServer) { | ||
| await mongoServer.stop(); | ||
| } | ||
| cleanupChildProcesses(); | ||
| }); | ||
|
|
||
| const EXPECTED_TRANSACTION = { | ||
| transaction: 'Test Transaction', | ||
| spans: expect.arrayContaining([ | ||
| expect.objectContaining({ | ||
| data: expect.objectContaining({ | ||
| 'db.mongodb.collection': 'blogposts', | ||
| 'db.operation': 'save', | ||
| 'db.system': 'mongoose', | ||
| }), | ||
| description: 'mongoose.BlogPost.save', | ||
| op: 'db', | ||
| origin: 'auto.db.otel.mongoose', | ||
| }), | ||
| expect.objectContaining({ | ||
| data: expect.objectContaining({ | ||
| 'db.mongodb.collection': 'blogposts', | ||
| 'db.operation': 'updateOne', | ||
| 'db.system': 'mongoose', | ||
| }), | ||
| description: 'mongoose.BlogPost.updateOne', | ||
| op: 'db', | ||
| origin: 'auto.db.otel.mongoose', | ||
| }), | ||
| expect.objectContaining({ | ||
| data: expect.objectContaining({ | ||
| 'db.mongodb.collection': 'blogposts', | ||
| 'db.operation': 'deleteOne', | ||
| 'db.system': 'mongoose', | ||
| }), | ||
| description: 'mongoose.BlogPost.deleteOne', | ||
| op: 'db', | ||
| origin: 'auto.db.otel.mongoose', | ||
| }), | ||
| ]), | ||
| }; | ||
|
|
||
| createEsmAndCjsTests( | ||
| __dirname, | ||
| 'scenario.mjs', | ||
| 'instrument.mjs', | ||
| (createTestRunner, test) => { | ||
| test('auto-instruments `mongoose` v8 document methods.', async () => { | ||
| await createTestRunner().expect({ transaction: EXPECTED_TRANSACTION }).start().completed(); | ||
| }); | ||
| }, | ||
| { additionalDependencies: { mongoose: '^8' } }, | ||
| ); | ||
| }); |
9 changes: 9 additions & 0 deletions
9
dev-packages/node-integration-tests/suites/tracing/mongoose-v9/instrument.mjs
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,9 @@ | ||
| import * as Sentry from '@sentry/node'; | ||
| import { loggingTransport } from '@sentry-internal/node-integration-tests'; | ||
|
|
||
| Sentry.init({ | ||
| dsn: 'https://public@dsn.ingest.sentry.io/1337', | ||
| release: '1.0', | ||
| tracesSampleRate: 1.0, | ||
| transport: loggingTransport, | ||
| }); |
42 changes: 42 additions & 0 deletions
42
dev-packages/node-integration-tests/suites/tracing/mongoose-v9/scenario.mjs
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,42 @@ | ||
| import * as Sentry from '@sentry/node'; | ||
| import mongoose from 'mongoose'; | ||
|
|
||
| async function run() { | ||
| await mongoose.connect(process.env.MONGO_URL || ''); | ||
|
|
||
| const BlogPostSchema = new mongoose.Schema({ | ||
| title: String, | ||
| body: String, | ||
| date: Date, | ||
| }); | ||
|
|
||
| const BlogPost = mongoose.model('BlogPost', BlogPostSchema); | ||
|
|
||
| await Sentry.startSpan( | ||
| { | ||
| name: 'Test Transaction', | ||
| op: 'transaction', | ||
| }, | ||
| async () => { | ||
| const post = new BlogPost({ title: 'Test', body: 'Test body', date: new Date() }); | ||
|
|
||
| await post.save(); | ||
|
|
||
| await BlogPost.findOne({}); | ||
|
|
||
| await BlogPost.aggregate([{ $match: {} }]); | ||
|
|
||
| await BlogPost.insertMany([{ title: 'Insert', body: 'Insert body', date: new Date() }]); | ||
|
|
||
| await BlogPost.bulkWrite([{ insertOne: { document: { title: 'Bulk', body: 'Bulk body', date: new Date() } } }]); | ||
|
|
||
| // Document instance methods. On v9 these are not doc-method-patched (needsDocumentMethodPatch | ||
| // only matches 8.x) but are still instrumented via the patched Query.exec path. | ||
| const doc = await BlogPost.create({ title: 'DocMethod', body: 'b', date: new Date() }); | ||
| await doc.updateOne({ title: 'DocMethodUpdated' }); | ||
| await doc.deleteOne(); | ||
| }, | ||
| ); | ||
| } | ||
|
|
||
| run(); |
60 changes: 60 additions & 0 deletions
60
dev-packages/node-integration-tests/suites/tracing/mongoose-v9/test.ts
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,60 @@ | ||
| import { MongoMemoryServer } from 'mongodb-memory-server-global'; | ||
| import { afterAll, beforeAll, expect } from 'vitest'; | ||
| import { conditionalTest } from '../../../utils'; | ||
| import { cleanupChildProcesses, createEsmAndCjsTests } from '../../../utils/runner'; | ||
|
|
||
| // Pins mongoose 9 (top of our supported `>=5.9.7 <10` range) so the latest major is exercised | ||
| // against a real mongoose. mongoose 9 requires Node >=20.19, so this suite is skipped on older Node. | ||
| conditionalTest({ min: 20 })('Mongoose v9 Test', () => { | ||
| let mongoServer: MongoMemoryServer; | ||
|
|
||
| beforeAll(async () => { | ||
| mongoServer = await MongoMemoryServer.create(); | ||
| process.env.MONGO_URL = mongoServer.getUri(); | ||
| }, 30000); | ||
|
|
||
| afterAll(async () => { | ||
| if (mongoServer) { | ||
| await mongoServer.stop(); | ||
| } | ||
| cleanupChildProcesses(); | ||
| }); | ||
|
|
||
| const expectedSpan = (operation: string) => | ||
| expect.objectContaining({ | ||
| data: expect.objectContaining({ | ||
| 'db.mongodb.collection': 'blogposts', | ||
| 'db.operation': operation, | ||
| 'db.system': 'mongoose', | ||
| }), | ||
| description: `mongoose.BlogPost.${operation}`, | ||
| op: 'db', | ||
| origin: 'auto.db.otel.mongoose', | ||
| }); | ||
|
|
||
| const EXPECTED_TRANSACTION = { | ||
| transaction: 'Test Transaction', | ||
| spans: expect.arrayContaining([ | ||
| expectedSpan('save'), | ||
| expectedSpan('findOne'), | ||
| expectedSpan('aggregate'), | ||
| expectedSpan('insertMany'), | ||
| expectedSpan('bulkWrite'), | ||
| // Document instance methods are instrumented via Query.exec on v9 (no doc-method patch). | ||
| expectedSpan('updateOne'), | ||
| expectedSpan('deleteOne'), | ||
| ]), | ||
| }; | ||
|
|
||
| createEsmAndCjsTests( | ||
| __dirname, | ||
| 'scenario.mjs', | ||
| 'instrument.mjs', | ||
| (createTestRunner, test) => { | ||
| test('auto-instruments `mongoose` v9.', async () => { | ||
| await createTestRunner().expect({ transaction: EXPECTED_TRANSACTION }).start().completed(); | ||
| }); | ||
| }, | ||
| { additionalDependencies: { mongoose: '^9' } }, | ||
| ); | ||
| }); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.