Skip to content

Commit b382e30

Browse files
nicohrubeccodex
andauthored
fix(server-utils): Require a parent span for Tedious queries (#25822)
**Why:** Skip automatic Tedious DB spans outside an instrumented operation because they lack the application context needed to identify their origin, aligning with other DB integrations. **How to review:** Fix is to early return in case there is no active span. The rest of the PR is just adding regression coverage. Closes #25797 --------- Co-authored-by: GPT-6 <codex@openai.com>
1 parent 3aaac64 commit b382e30

3 files changed

Lines changed: 95 additions & 0 deletions

File tree

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
import * as Sentry from '@sentry/node';
2+
import { waitForConnection } from '@sentry-internal/node-integration-tests';
3+
import { Connection, Request } from 'tedious';
4+
5+
function connect() {
6+
return new Promise((resolve, reject) => {
7+
const connection = new Connection({
8+
server: '127.0.0.1',
9+
authentication: {
10+
type: 'default',
11+
options: { userName: 'sa', password: 'TESTing123' },
12+
},
13+
options: { port: 1433, encrypt: false },
14+
});
15+
16+
connection.on('connect', err => {
17+
if (err) {
18+
connection.close();
19+
reject(err);
20+
} else {
21+
resolve(connection);
22+
}
23+
});
24+
connection.connect();
25+
});
26+
}
27+
28+
function query(connection, sql) {
29+
return new Promise((resolve, reject) => {
30+
connection.execSql(new Request(sql, err => (err ? reject(err) : resolve())));
31+
});
32+
}
33+
34+
async function run() {
35+
const connection = await waitForConnection(connect);
36+
37+
await query(connection, 'SELECT 1 AS before_parent');
38+
39+
const parent = Sentry.startInactiveSpan({ name: 'parent operation' });
40+
await Sentry.withActiveSpan(parent, () => query(connection, 'SELECT 2 AS inside_parent'));
41+
42+
await query(connection, 'SELECT 3 AS after_parent');
43+
connection.close();
44+
45+
// Flush every query before the parent signals that span collection is complete.
46+
await Sentry.flush();
47+
parent.end();
48+
await Sentry.flush();
49+
}
50+
51+
run();

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

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import type { SerializedStreamedSpan } from '@sentry/core';
12
import {
23
DB_NAMESPACE,
34
DB_QUERY_SUMMARY,
@@ -122,6 +123,44 @@ describeWithDockerCompose('tedious auto instrumentation', { workingDirectory: [_
122123
});
123124
});
124125

126+
createEsmAndCjsTests(__dirname, 'scenario-no-parent.mjs', 'instrument.mjs', (createTestRunner, test) => {
127+
test('only instruments queries with an active parent span', async () => {
128+
const runner = createTestRunner();
129+
const dbSpans = new Map<string, SerializedStreamedSpan>();
130+
const spansPromise = runner.collectStreamedSpans(spans => {
131+
for (const span of spans) {
132+
if (span.attributes[SENTRY_ORIGIN]?.value === ORIGIN) {
133+
dbSpans.set(span.span_id, span);
134+
}
135+
}
136+
return spans.some(span => span.is_segment && span.name === 'parent operation');
137+
});
138+
139+
await runner.unordered().start().completed();
140+
141+
const spans = await spansPromise;
142+
const parent = spans.find(span => span.is_segment && span.name === 'parent operation');
143+
expect(parent).toBeDefined();
144+
expect(
145+
Array.from(dbSpans.values(), span => ({
146+
query: span.attributes[DB_QUERY_TEXT]?.value,
147+
database: span.attributes[DB_NAMESPACE]?.value,
148+
parentSpanId: span.parent_span_id,
149+
traceId: span.trace_id,
150+
isSegment: span.is_segment,
151+
})),
152+
).toEqual([
153+
{
154+
query: 'SELECT ? AS inside_parent',
155+
database: 'master',
156+
parentSpanId: parent?.span_id,
157+
traceId: parent?.trace_id,
158+
isSegment: false,
159+
},
160+
]);
161+
});
162+
});
163+
125164
// tedious 20 requires Node >= 22.
126165
describe.runIf(supports({ min: 22 }))('tedious v20', () => {
127166
createEsmAndCjsTests(

‎packages/server-utils/src/integrations/tedious.ts‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import * as diagnosticsChannel from '../utils/diagnosticsChannel';
77
import type { IntegrationFn, SpanAttributes } from '@sentry/core';
88
import {
99
defineIntegration,
10+
getActiveSpan,
1011
getClient,
1112
hasSpanStreamingEnabled,
1213
SPAN_STATUS_ERROR,
@@ -110,6 +111,10 @@ function subscribeConnect(): void {
110111

111112
function subscribeQuery(channelName: string, operation: string): void {
112113
diagnosticsChannel.tracingChannel(channelName).start.subscribe(message => {
114+
if (!getActiveSpan()) {
115+
return;
116+
}
117+
113118
const data = message as TediousQueryChannelContext;
114119
const connection = data.self;
115120
const request = data.arguments[0];

0 commit comments

Comments
 (0)