Skip to content

Commit 1271fb6

Browse files
Lms24claude
andcommitted
feat(core): Emit low cardinality mongoose span names
Mongoose spans wrap an ODM-level operation. With span streaming enabled: - mongoose spans become `{db.operation.name} {db.collection.name}`, e.g. `findOne blogposts` - falls back to `db.namespace`, then to `db.system.name` - `traceLifecycle: 'static'` keeps the existing `mongoose.<Model>.<op>` names Also corrects `db.system.name` on the monkey-patch path from `mongoose` to `mongodb`, matching the diagnostics-channel path. Refs #23523 Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
1 parent 3251e33 commit 1271fb6

15 files changed

Lines changed: 292 additions & 28 deletions

File tree

‎dev-packages/deno-integration-tests/suites/orchestrion-mongoose/test.ts‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ Deno.test('mongoose instrumentation: orchestrion:mongoose:model_save channel pro
5656
const mongooseSpan = parent.spans?.find(s => s.op === 'db');
5757
assertExists(mongooseSpan, `expected a db child span, got ops: ${parent.spans?.map(s => s.op).join(', ')}`);
5858
assertEquals(mongooseSpan!.description, 'mongoose.BlogPost.save');
59-
assertEquals(mongooseSpan!.data?.['db.system.name'], 'mongoose');
59+
assertEquals(mongooseSpan!.data?.['db.system.name'], 'mongodb');
6060
assertEquals(mongooseSpan!.data?.['db.namespace'], 'mydb');
6161
assertEquals(mongooseSpan!.data?.['db.collection.name'], 'blogposts');
6262
assertEquals(mongooseSpan!.data?.['db.operation.name'], 'save');

‎dev-packages/node-integration-tests/suites/tracing/mongoose-tracing-channel/instrument.mjs‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import * as Sentry from '@sentry/node';
22
import { loggingTransport } from '@sentry-internal/node-integration-tests';
33

44
Sentry.init({
5-
traceLifecycle: 'static',
5+
traceLifecycle: process.env.STREAMED === 'true' ? 'stream' : 'static',
66
dsn: 'https://[email protected]/1337',
77
release: '1.0',
88
tracesSampleRate: 1.0,

‎dev-packages/node-integration-tests/suites/tracing/mongoose-tracing-channel/test.ts‎

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import type { SerializedStreamedSpanContainer } from '@sentry/core';
12
import { MongoMemoryServer } from 'mongodb-memory-server-global';
23
import { afterAll, beforeAll, describe, expect } from 'vitest';
34
import { cleanupChildProcesses, createEsmAndCjsTests } from '../../../utils/runner';
@@ -39,6 +40,23 @@ describe('Mongoose tracing channel Test', () => {
3940
origin: 'auto.db.mongoose.diagnostic_channel',
4041
});
4142

43+
const expectedStreamedSpan = (operation: string, extraAttributes: Record<string, unknown> = {}) =>
44+
expect.objectContaining({
45+
name: `${operation} blogposts`,
46+
is_segment: false,
47+
parent_span_id: expect.stringMatching(/^[\da-f]{16}$/),
48+
attributes: expect.objectContaining({
49+
'db.collection.name': { type: 'string', value: 'blogposts' },
50+
'db.namespace': { type: 'string', value: 'test' },
51+
'db.operation.name': { type: 'string', value: operation },
52+
'db.system.name': { type: 'string', value: 'mongodb' },
53+
'sentry.op': { type: 'string', value: 'db' },
54+
'sentry.origin': { type: 'string', value: 'auto.db.mongoose.diagnostic_channel' },
55+
'sentry.trace_lifecycle': { type: 'string', value: 'stream' },
56+
...extraAttributes,
57+
}),
58+
});
59+
4260
const EXPECTED_TRANSACTION = {
4361
transaction: 'Test Transaction',
4462
spans: expect.arrayContaining([
@@ -62,6 +80,35 @@ describe('Mongoose tracing channel Test', () => {
6280
await createTestRunner().expect({ transaction: EXPECTED_TRANSACTION }).start().completed();
6381
});
6482

83+
test('names channel spans after the operation and collection with span streaming enabled', async () => {
84+
await createTestRunner()
85+
.withEnv({ STREAMED: 'true' })
86+
.expect({
87+
span: (container: SerializedStreamedSpanContainer) => {
88+
expect(container.items.find(item => item.is_segment)?.name).toBe('Test Transaction');
89+
90+
expect(container.items).toContainEqual(expectedStreamedSpan('save'));
91+
expect(container.items).toContainEqual(
92+
expectedStreamedSpan('findOne', { 'db.query.text': { type: 'string', value: '{"title":"?"}' } }),
93+
);
94+
expect(container.items).toContainEqual(
95+
expectedStreamedSpan('aggregate', {
96+
'db.query.text': { type: 'string', value: '[{"$match":{"title":"?"}}]' },
97+
}),
98+
);
99+
expect(container.items).toContainEqual(
100+
expectedStreamedSpan('insertMany', { 'db.operation.batch.size': { type: 'integer', value: 2 } }),
101+
);
102+
expect(container.items).toContainEqual(
103+
expectedStreamedSpan('bulkWrite', { 'db.operation.batch.size': { type: 'integer', value: 2 } }),
104+
);
105+
expect(container.items).toContainEqual(expectedStreamedSpan('find'));
106+
},
107+
})
108+
.start()
109+
.completed();
110+
});
111+
65112
test('does not double-instrument: the legacy IITM mongoose patcher does not fire on 9.7', async () => {
66113
await createTestRunner()
67114
.expect({
@@ -153,6 +200,20 @@ describe('Mongoose tracing channel Test', () => {
153200
.start()
154201
.completed();
155202
});
203+
204+
test('flags the streamed mongoose channel span as errored when the operation fails', async () => {
205+
await createTestRunner()
206+
.withEnv({ STREAMED: 'true' })
207+
.expect({
208+
span: (container: SerializedStreamedSpanContainer) => {
209+
const aggregateSpan = container.items.find(item => item.name === 'aggregate blogposts');
210+
expect(aggregateSpan).toBeDefined();
211+
expect(aggregateSpan?.status).toBe('error');
212+
},
213+
})
214+
.start()
215+
.completed();
216+
});
156217
},
157218
{ additionalDependencies: { mongoose: '^9.7' } },
158219
);

‎dev-packages/node-integration-tests/suites/tracing/mongoose-v5/instrument.mjs‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import * as Sentry from '@sentry/node';
22
import { loggingTransport } from '@sentry-internal/node-integration-tests';
33

44
Sentry.init({
5-
traceLifecycle: 'static',
5+
traceLifecycle: process.env.STREAMED === 'true' ? 'stream' : 'static',
66
dsn: 'https://[email protected]/1337',
77
release: '1.0',
88
tracesSampleRate: 1.0,

‎dev-packages/node-integration-tests/suites/tracing/mongoose-v5/test.ts‎

Lines changed: 35 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import type { SerializedStreamedSpanContainer } from '@sentry/core';
12
import { MongoMemoryServer } from 'mongodb-memory-server-global';
23
import { afterAll, beforeAll, describe, expect } from 'vitest';
34
import { cleanupChildProcesses, createEsmAndCjsTests } from '../../../utils/runner';
@@ -26,7 +27,7 @@ describe('Mongoose v5 Test', () => {
2627
data: expect.objectContaining({
2728
'db.collection.name': 'blogposts',
2829
'db.operation.name': operation,
29-
'db.system.name': 'mongoose',
30+
'db.system.name': 'mongodb',
3031
}),
3132
description: `mongoose.BlogPost.${operation}`,
3233
op: 'db',
@@ -44,6 +45,23 @@ describe('Mongoose v5 Test', () => {
4445
]),
4546
};
4647

48+
const expectedStreamedSpan = (operation: string) =>
49+
expect.objectContaining({
50+
name: `${operation} blogposts`,
51+
is_segment: false,
52+
parent_span_id: expect.stringMatching(/^[\da-f]{16}$/),
53+
attributes: expect.objectContaining({
54+
'db.collection.name': { type: 'string', value: 'blogposts' },
55+
'db.operation.name': { type: 'string', value: operation },
56+
'db.system.name': { type: 'string', value: 'mongodb' },
57+
'sentry.op': { type: 'string', value: 'db' },
58+
'sentry.origin': { type: 'string', value: origin },
59+
'sentry.trace_lifecycle': { type: 'string', value: 'stream' },
60+
}),
61+
});
62+
63+
const STREAMED_OPERATIONS = ['save', 'findOne', 'aggregate', 'insertMany', 'bulkWrite'];
64+
4765
createEsmAndCjsTests(
4866
__dirname,
4967
'scenario.mjs',
@@ -52,6 +70,22 @@ describe('Mongoose v5 Test', () => {
5270
test('auto-instruments `mongoose` v5.', async () => {
5371
await createTestRunner().expect({ transaction: EXPECTED_TRANSACTION }).start().completed();
5472
});
73+
74+
test('auto-instruments `mongoose` v5 with span streaming enabled.', async () => {
75+
await createTestRunner()
76+
.withEnv({ STREAMED: 'true' })
77+
.expect({
78+
span: (container: SerializedStreamedSpanContainer) => {
79+
expect(container.items.find(item => item.is_segment)?.name).toBe('Test Transaction');
80+
81+
for (const operation of STREAMED_OPERATIONS) {
82+
expect(container.items).toContainEqual(expectedStreamedSpan(operation));
83+
}
84+
},
85+
})
86+
.start()
87+
.completed();
88+
});
5589
},
5690
{ additionalDependencies: { mongoose: '^5.9.7' } },
5791
);

‎dev-packages/node-integration-tests/suites/tracing/mongoose-v7/instrument.mjs‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import * as Sentry from '@sentry/node';
22
import { loggingTransport } from '@sentry-internal/node-integration-tests';
33

44
Sentry.init({
5-
traceLifecycle: 'static',
5+
traceLifecycle: process.env.STREAMED === 'true' ? 'stream' : 'static',
66
dsn: 'https://[email protected]/1337',
77
release: '1.0',
88
tracesSampleRate: 1.0,

‎dev-packages/node-integration-tests/suites/tracing/mongoose-v7/test.ts‎

Lines changed: 35 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import type { SerializedStreamedSpanContainer } from '@sentry/core';
12
import { MongoMemoryServer } from 'mongodb-memory-server-global';
23
import { afterAll, beforeAll, describe, expect } from 'vitest';
34
import { cleanupChildProcesses, createEsmAndCjsTests } from '../../../utils/runner';
@@ -24,7 +25,7 @@ describe('Mongoose v7 Test', () => {
2425
data: expect.objectContaining({
2526
'db.collection.name': 'blogposts',
2627
'db.operation.name': operation,
27-
'db.system.name': 'mongoose',
28+
'db.system.name': 'mongodb',
2829
}),
2930
description: `mongoose.BlogPost.${operation}`,
3031
op: 'db',
@@ -42,6 +43,23 @@ describe('Mongoose v7 Test', () => {
4243
]),
4344
};
4445

46+
const expectedStreamedSpan = (operation: string) =>
47+
expect.objectContaining({
48+
name: `${operation} blogposts`,
49+
is_segment: false,
50+
parent_span_id: expect.stringMatching(/^[\da-f]{16}$/),
51+
attributes: expect.objectContaining({
52+
'db.collection.name': { type: 'string', value: 'blogposts' },
53+
'db.operation.name': { type: 'string', value: operation },
54+
'db.system.name': { type: 'string', value: 'mongodb' },
55+
'sentry.op': { type: 'string', value: 'db' },
56+
'sentry.origin': { type: 'string', value: origin },
57+
'sentry.trace_lifecycle': { type: 'string', value: 'stream' },
58+
}),
59+
});
60+
61+
const STREAMED_OPERATIONS = ['save', 'findOne', 'aggregate', 'insertMany', 'bulkWrite'];
62+
4563
createEsmAndCjsTests(
4664
__dirname,
4765
'scenario.mjs',
@@ -50,6 +68,22 @@ describe('Mongoose v7 Test', () => {
5068
test('auto-instruments `mongoose` v7.', async () => {
5169
await createTestRunner().expect({ transaction: EXPECTED_TRANSACTION }).start().completed();
5270
});
71+
72+
test('auto-instruments `mongoose` v7 with span streaming enabled.', async () => {
73+
await createTestRunner()
74+
.withEnv({ STREAMED: 'true' })
75+
.expect({
76+
span: (container: SerializedStreamedSpanContainer) => {
77+
expect(container.items.find(item => item.is_segment)?.name).toBe('Test Transaction');
78+
79+
for (const operation of STREAMED_OPERATIONS) {
80+
expect(container.items).toContainEqual(expectedStreamedSpan(operation));
81+
}
82+
},
83+
})
84+
.start()
85+
.completed();
86+
});
5387
},
5488
{ additionalDependencies: { mongoose: '^7' } },
5589
);

‎dev-packages/node-integration-tests/suites/tracing/mongoose-v8/instrument.mjs‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import * as Sentry from '@sentry/node';
22
import { loggingTransport } from '@sentry-internal/node-integration-tests';
33

44
Sentry.init({
5-
traceLifecycle: 'static',
5+
traceLifecycle: process.env.STREAMED === 'true' ? 'stream' : 'static',
66
dsn: 'https://[email protected]/1337',
77
release: '1.0',
88
tracesSampleRate: 1.0,

‎dev-packages/node-integration-tests/suites/tracing/mongoose-v8/test.ts‎

Lines changed: 35 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import type { SerializedStreamedSpanContainer } from '@sentry/core';
12
import { MongoMemoryServer } from 'mongodb-memory-server-global';
23
import { afterAll, beforeAll, describe, expect } from 'vitest';
34
import { cleanupChildProcesses, createEsmAndCjsTests } from '../../../utils/runner';
@@ -27,7 +28,7 @@ describe('Mongoose v8 Test', () => {
2728
data: expect.objectContaining({
2829
'db.collection.name': 'blogposts',
2930
'db.operation.name': 'save',
30-
'db.system.name': 'mongoose',
31+
'db.system.name': 'mongodb',
3132
}),
3233
description: 'mongoose.BlogPost.save',
3334
op: 'db',
@@ -37,7 +38,7 @@ describe('Mongoose v8 Test', () => {
3738
data: expect.objectContaining({
3839
'db.collection.name': 'blogposts',
3940
'db.operation.name': 'updateOne',
40-
'db.system.name': 'mongoose',
41+
'db.system.name': 'mongodb',
4142
}),
4243
description: 'mongoose.BlogPost.updateOne',
4344
op: 'db',
@@ -47,7 +48,7 @@ describe('Mongoose v8 Test', () => {
4748
data: expect.objectContaining({
4849
'db.collection.name': 'blogposts',
4950
'db.operation.name': 'deleteOne',
50-
'db.system.name': 'mongoose',
51+
'db.system.name': 'mongodb',
5152
}),
5253
description: 'mongoose.BlogPost.deleteOne',
5354
op: 'db',
@@ -56,6 +57,21 @@ describe('Mongoose v8 Test', () => {
5657
]),
5758
};
5859

60+
const expectedStreamedSpan = (operation: string) =>
61+
expect.objectContaining({
62+
name: `${operation} blogposts`,
63+
is_segment: false,
64+
parent_span_id: expect.stringMatching(/^[\da-f]{16}$/),
65+
attributes: expect.objectContaining({
66+
'db.collection.name': { type: 'string', value: 'blogposts' },
67+
'db.operation.name': { type: 'string', value: operation },
68+
'db.system.name': { type: 'string', value: 'mongodb' },
69+
'sentry.op': { type: 'string', value: 'db' },
70+
'sentry.origin': { type: 'string', value: origin },
71+
'sentry.trace_lifecycle': { type: 'string', value: 'stream' },
72+
}),
73+
});
74+
5975
createEsmAndCjsTests(
6076
__dirname,
6177
'scenario.mjs',
@@ -64,6 +80,22 @@ describe('Mongoose v8 Test', () => {
6480
test('auto-instruments `mongoose` v8 document methods.', async () => {
6581
await createTestRunner().expect({ transaction: EXPECTED_TRANSACTION }).start().completed();
6682
});
83+
84+
test('auto-instruments `mongoose` v8 document methods with span streaming enabled.', async () => {
85+
await createTestRunner()
86+
.withEnv({ STREAMED: 'true' })
87+
.expect({
88+
span: (container: SerializedStreamedSpanContainer) => {
89+
expect(container.items.find(item => item.is_segment)?.name).toBe('Test Transaction');
90+
91+
for (const operation of ['save', 'updateOne', 'deleteOne']) {
92+
expect(container.items).toContainEqual(expectedStreamedSpan(operation));
93+
}
94+
},
95+
})
96+
.start()
97+
.completed();
98+
});
6799
},
68100
{ additionalDependencies: { mongoose: '^8' } },
69101
);

‎dev-packages/node-integration-tests/suites/tracing/mongoose-v9/instrument.mjs‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import * as Sentry from '@sentry/node';
22
import { loggingTransport } from '@sentry-internal/node-integration-tests';
33

44
Sentry.init({
5-
traceLifecycle: 'static',
5+
traceLifecycle: process.env.STREAMED === 'true' ? 'stream' : 'static',
66
dsn: 'https://[email protected]/1337',
77
release: '1.0',
88
tracesSampleRate: 1.0,

0 commit comments

Comments
 (0)