Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import * as Sentry from '@sentry/node';
import mysql from 'mysql';

const connection = mysql.createConnection({
port: Number(process.env.MYSQL_PORT),
user: 'root',
password: 'docker',
});

function query(sql) {
return new Promise((resolve, reject) => {
connection.query(sql, err => (err ? reject(err) : resolve()));
});
}

function streamQuery(sql) {
return new Promise((resolve, reject) => {
connection.query(sql).on('error', reject).on('end', resolve);
});
}

async function run() {
await query('SELECT 1 AS before_callback');
await streamQuery('SELECT 1 AS before_stream');

const parent = Sentry.startInactiveSpan({ name: 'parent operation' });
await Sentry.withActiveSpan(parent, async () => {
await query('SELECT 2 AS inside_callback');
await streamQuery('SELECT 2 AS inside_stream');
});

await query('SELECT 3 AS after_callback');
await streamQuery('SELECT 3 AS after_stream');
await new Promise((resolve, reject) => {
connection.end(err => (err ? reject(err) : resolve()));
});

// Flush every query before the parent signals that span collection is complete.
await Sentry.flush();
parent.end();
await Sentry.flush();
}

run();
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import type { SerializedStreamedSpan } from '@sentry/core';
import {
DB_CONNECTION_STRING,
DB_QUERY_SUMMARY,
Expand Down Expand Up @@ -215,6 +216,52 @@ describe('mysql auto instrumentation', () => {
);
}

createEsmAndCjsTests(
__dirname,
'scenario-no-parent.mjs',
'instrument.mjs',
(createTestRunner, test) => {
test('only instruments queries with an active parent span', async () => {
const runner = createTestRunner()
.withEnv({ ...env, MYSQL_PORT: String(mysqlPort) })
.withFlags(...flags);
const dbSpans = new Map<string, SerializedStreamedSpan>();
const spansPromise = runner.collectStreamedSpans(spans => {
for (const span of spans) {
if (span.attributes[SENTRY_ORIGIN]?.value === origin) {
dbSpans.set(span.span_id, span);
}
}
return spans.some(span => span.is_segment && span.name === 'parent operation');
});

await runner.unordered().start().completed();

const spans = await spansPromise;
const parent = spans.find(span => span.is_segment && span.name === 'parent operation');
expect(parent).toBeDefined();
expect(
Array.from(dbSpans.values(), span => ({
query: span.attributes[DB_QUERY_TEXT]?.value,
port: span.attributes[SERVER_PORT]?.value,
parentSpanId: span.parent_span_id,
traceId: span.trace_id,
isSegment: span.is_segment,
})),
).toEqual(
['SELECT ? AS inside_callback', 'SELECT ? AS inside_stream'].map(query => ({
query,
port: mysqlPort,
parentSpanId: parent?.span_id,
traceId: parent?.trace_id,
isSegment: false,
})),
);
});
},
{ failsOnEsm },
);

createEsmAndCjsTests(
__dirname,
'scenario-streamContext.mjs',
Expand Down
1 change: 1 addition & 0 deletions packages/server-utils/src/integrations/mysql.ts
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,7 @@ function instrumentMysql(): void {
});
},
{
requiresParentSpan: true,
// No-callback `query(sql)` returns a streamable `Query` emitter as `result`; it settles on the
// emitter's `'end'`/`'error'`, not the channel, so defer ending to those.
deferSpanEnd({ data, end }) {
Expand Down
Loading