Skip to content

Commit 1ca67d1

Browse files
authored
fix(node): Improve mysql integration (#8923)
1. Make sure it works consistently when not calling `connection.connect()` (which is optional) - it used to only register the mysql config on the second call, as it calls `connect` under the hood but only after we already tried to fetch it again. 2. Make sure it works without a callback.
1 parent 9666506 commit 1ca67d1

File tree

7 files changed

+159
-7
lines changed

7 files changed

+159
-7
lines changed
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
import { assertSentryTransaction, TestEnv } from '../../../../../utils';
2+
3+
test('should auto-instrument `mysql` package when using connection.connect()', async () => {
4+
const env = await TestEnv.init(__dirname);
5+
const envelope = await env.getEnvelopeRequest({ envelopeType: 'transaction' });
6+
7+
expect(envelope).toHaveLength(3);
8+
9+
assertSentryTransaction(envelope[2], {
10+
transaction: 'Test Transaction',
11+
spans: [
12+
{
13+
description: 'SELECT 1 + 1 AS solution',
14+
op: 'db',
15+
data: {
16+
'db.system': 'mysql',
17+
'server.address': 'localhost',
18+
'server.port': 3306,
19+
'db.user': 'root',
20+
},
21+
},
22+
23+
{
24+
description: 'SELECT NOW()',
25+
op: 'db',
26+
data: {
27+
'db.system': 'mysql',
28+
'server.address': 'localhost',
29+
'server.port': 3306,
30+
'db.user': 'root',
31+
},
32+
},
33+
],
34+
});
35+
});
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
import * as Sentry from '@sentry/node';
2+
import mysql from 'mysql';
3+
4+
Sentry.init({
5+
dsn: 'https://[email protected]/1337',
6+
release: '1.0',
7+
tracesSampleRate: 1.0,
8+
integrations: [...Sentry.autoDiscoverNodePerformanceMonitoringIntegrations()],
9+
});
10+
11+
const connection = mysql.createConnection({
12+
user: 'root',
13+
password: 'docker',
14+
});
15+
16+
const transaction = Sentry.startTransaction({
17+
op: 'transaction',
18+
name: 'Test Transaction',
19+
});
20+
21+
Sentry.configureScope(scope => {
22+
scope.setSpan(transaction);
23+
});
24+
25+
connection.query('SELECT 1 + 1 AS solution');
26+
connection.query('SELECT NOW()', ['1', '2']);
27+
28+
// Wait a bit to ensure the queries completed
29+
setTimeout(() => {
30+
transaction.finish();
31+
}, 500);
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
import { assertSentryTransaction, TestEnv } from '../../../../../utils';
2+
3+
test('should auto-instrument `mysql` package when using query without callback', async () => {
4+
const env = await TestEnv.init(__dirname);
5+
const envelope = await env.getEnvelopeRequest({ envelopeType: 'transaction' });
6+
7+
expect(envelope).toHaveLength(3);
8+
9+
assertSentryTransaction(envelope[2], {
10+
transaction: 'Test Transaction',
11+
spans: [
12+
{
13+
description: 'SELECT 1 + 1 AS solution',
14+
op: 'db',
15+
data: {
16+
'db.system': 'mysql',
17+
'server.address': 'localhost',
18+
'server.port': 3306,
19+
'db.user': 'root',
20+
},
21+
},
22+
23+
{
24+
description: 'SELECT NOW()',
25+
op: 'db',
26+
data: {
27+
'db.system': 'mysql',
28+
'server.address': 'localhost',
29+
'server.port': 3306,
30+
'db.user': 'root',
31+
},
32+
},
33+
],
34+
});
35+
});
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
import * as Sentry from '@sentry/node';
2+
import mysql from 'mysql';
3+
4+
Sentry.init({
5+
dsn: 'https://[email protected]/1337',
6+
release: '1.0',
7+
tracesSampleRate: 1.0,
8+
integrations: [...Sentry.autoDiscoverNodePerformanceMonitoringIntegrations()],
9+
});
10+
11+
const connection = mysql.createConnection({
12+
user: 'root',
13+
password: 'docker',
14+
});
15+
16+
const transaction = Sentry.startTransaction({
17+
op: 'transaction',
18+
name: 'Test Transaction',
19+
});
20+
21+
Sentry.configureScope(scope => {
22+
scope.setSpan(transaction);
23+
});
24+
25+
connection.query('SELECT 1 + 1 AS solution', function () {
26+
connection.query('SELECT NOW()', ['1', '2'], () => {
27+
if (transaction) transaction.finish();
28+
connection.end();
29+
});
30+
});

packages/node-integration-tests/suites/tracing-new/auto-instrument/mysql/test.ts renamed to packages/node-integration-tests/suites/tracing-new/auto-instrument/mysql/withoutConnect/test.ts

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1-
import { assertSentryTransaction, TestEnv } from '../../../../utils';
1+
import { assertSentryTransaction, TestEnv } from '../../../../../utils';
22

3-
test('should auto-instrument `mysql` package.', async () => {
3+
test('should auto-instrument `mysql` package without connection.connect()', async () => {
44
const env = await TestEnv.init(__dirname);
55
const envelope = await env.getEnvelopeRequest({ envelopeType: 'transaction' });
66

@@ -14,6 +14,9 @@ test('should auto-instrument `mysql` package.', async () => {
1414
op: 'db',
1515
data: {
1616
'db.system': 'mysql',
17+
'server.address': 'localhost',
18+
'server.port': 3306,
19+
'db.user': 'root',
1720
},
1821
},
1922

@@ -22,6 +25,9 @@ test('should auto-instrument `mysql` package.', async () => {
2225
op: 'db',
2326
data: {
2427
'db.system': 'mysql',
28+
'server.address': 'localhost',
29+
'server.port': 3306,
30+
'db.user': 'root',
2531
},
2632
},
2733
],

packages/tracing-internal/src/node/integrations/mysql.ts

Lines changed: 20 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import type { Hub } from '@sentry/core';
2-
import type { EventProcessor } from '@sentry/types';
2+
import type { EventProcessor, Span } from '@sentry/types';
33
import { fill, loadModule, logger } from '@sentry/utils';
44

55
import type { LazyLoadedIntegration } from './lazy';
@@ -83,6 +83,19 @@ export class Mysql implements LazyLoadedIntegration<MysqlConnection> {
8383
};
8484
}
8585

86+
function finishSpan(span: Span | undefined): void {
87+
if (!span) {
88+
return;
89+
}
90+
91+
const data = spanDataFromConfig();
92+
Object.keys(data).forEach(key => {
93+
span.setData(key, data[key]);
94+
});
95+
96+
span.finish();
97+
}
98+
8699
// The original function will have one of these signatures:
87100
// function (callback) => void
88101
// function (options, callback) => void
@@ -91,31 +104,33 @@ export class Mysql implements LazyLoadedIntegration<MysqlConnection> {
91104
return function (this: unknown, options: unknown, values: unknown, callback: unknown) {
92105
const scope = getCurrentHub().getScope();
93106
const parentSpan = scope?.getSpan();
107+
94108
const span = parentSpan?.startChild({
95109
description: typeof options === 'string' ? options : (options as { sql: string }).sql,
96110
op: 'db',
97111
origin: 'auto.db.mysql',
98112
data: {
99-
...spanDataFromConfig(),
100113
'db.system': 'mysql',
101114
},
102115
});
103116

104117
if (typeof callback === 'function') {
105118
return orig.call(this, options, values, function (err: Error, result: unknown, fields: unknown) {
106-
span?.finish();
119+
finishSpan(span);
107120
callback(err, result, fields);
108121
});
109122
}
110123

111124
if (typeof values === 'function') {
112125
return orig.call(this, options, function (err: Error, result: unknown, fields: unknown) {
113-
span?.finish();
126+
finishSpan(span);
114127
values(err, result, fields);
115128
});
116129
}
117130

118-
return orig.call(this, options, values, callback);
131+
return orig.call(this, options, values, function () {
132+
finishSpan(span);
133+
});
119134
};
120135
});
121136
}

0 commit comments

Comments
 (0)