Skip to content

Commit 8c2aa1b

Browse files
feat(node): Add tedious integration (#13486)
Implement Tedious OTEL instrumentation in `packages/node`. Signed-off-by: Kaung Zin Hein <[email protected]> Co-authored-by: Abhijeet Prasad <[email protected]>
1 parent 39ad3bb commit 8c2aa1b

File tree

16 files changed

+536
-4
lines changed

16 files changed

+536
-4
lines changed

dev-packages/node-integration-tests/package.json

+1
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@
6565
"redis-4": "npm:redis@^4.6.14",
6666
"reflect-metadata": "0.2.1",
6767
"rxjs": "^7.8.1",
68+
"tedious": "^18.6.1",
6869
"yargs": "^16.2.0"
6970
},
7071
"devDependencies": {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
version: '3.9'
2+
3+
services:
4+
db:
5+
image: mcr.microsoft.com/mssql/server:2022-latest
6+
restart: always
7+
container_name: integration-tests-tedious
8+
ports:
9+
- '1433:1433'
10+
environment:
11+
ACCEPT_EULA: 'Y'
12+
MSSQL_SA_PASSWORD: 'TESTing123'
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
const { loggingTransport } = require('@sentry-internal/node-integration-tests');
2+
const Sentry = require('@sentry/node');
3+
4+
Sentry.init({
5+
dsn: 'https://[email protected]/1337',
6+
release: '1.0',
7+
tracesSampleRate: 1.0,
8+
transport: loggingTransport,
9+
});
10+
11+
const { Connection, Request } = require('tedious');
12+
13+
const config = {
14+
server: '127.0.0.1',
15+
authentication: {
16+
type: 'default',
17+
options: {
18+
userName: 'sa',
19+
password: 'TESTing123',
20+
},
21+
},
22+
options: {
23+
port: 1433,
24+
encrypt: false,
25+
},
26+
};
27+
28+
const connection = new Connection(config);
29+
30+
function executeAllStatements(span) {
31+
executeStatement('SELECT 1 + 1 AS solution', () => {
32+
executeStatement('SELECT GETDATE()', () => {
33+
span.end();
34+
connection.close();
35+
});
36+
});
37+
}
38+
39+
function executeStatement(query, callback) {
40+
const request = new Request(query, err => {
41+
if (err) {
42+
throw err;
43+
}
44+
callback();
45+
});
46+
47+
connection.execSql(request);
48+
}
49+
50+
connection.connect(err => {
51+
if (err) {
52+
throw err;
53+
}
54+
55+
Sentry.startSpanManual(
56+
{
57+
op: 'transaction',
58+
name: 'Test Transaction',
59+
},
60+
span => {
61+
// span must be ended manually after all queries
62+
executeAllStatements(span);
63+
},
64+
);
65+
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
import { conditionalTest } from '../../../utils';
2+
import { cleanupChildProcesses, createRunner } from '../../../utils/runner';
3+
4+
jest.setTimeout(75000);
5+
6+
// Tedious version we are testing against only supports Node 18+
7+
// https://github.com/tediousjs/tedious/blob/8310c455a2cc1cba83c1ca3c16677da4f83e12a9/package.json#L38
8+
conditionalTest({ min: 18 })('tedious auto instrumentation', () => {
9+
afterAll(() => {
10+
cleanupChildProcesses();
11+
});
12+
13+
test('should auto-instrument `tedious` package', done => {
14+
const EXPECTED_TRANSACTION = {
15+
transaction: 'Test Transaction',
16+
spans: expect.arrayContaining([
17+
expect.objectContaining({
18+
description: 'SELECT GETDATE()',
19+
data: expect.objectContaining({
20+
'sentry.origin': 'auto.db.otel.tedious',
21+
'sentry.op': 'db',
22+
'db.name': 'master',
23+
'db.statement': 'SELECT GETDATE()',
24+
'db.system': 'mssql',
25+
'db.user': 'sa',
26+
'net.peer.name': '127.0.0.1',
27+
'net.peer.port': 1433,
28+
}),
29+
status: 'ok',
30+
}),
31+
expect.objectContaining({
32+
description: 'SELECT 1 + 1 AS solution',
33+
data: expect.objectContaining({
34+
'sentry.origin': 'auto.db.otel.tedious',
35+
'sentry.op': 'db',
36+
'db.name': 'master',
37+
'db.statement': 'SELECT 1 + 1 AS solution',
38+
'db.system': 'mssql',
39+
'db.user': 'sa',
40+
'net.peer.name': '127.0.0.1',
41+
'net.peer.port': 1433,
42+
}),
43+
status: 'ok',
44+
}),
45+
]),
46+
};
47+
48+
createRunner(__dirname, 'scenario.js')
49+
.withDockerCompose({ workingDirectory: [__dirname], readyMatches: ['1433'] })
50+
.expect({ transaction: EXPECTED_TRANSACTION })
51+
.start(done);
52+
});
53+
});

packages/astro/src/index.server.ts

+1
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,7 @@ export {
125125
startSession,
126126
startSpan,
127127
startSpanManual,
128+
tediousIntegration,
128129
trpcMiddleware,
129130
withActiveSpan,
130131
withIsolationScope,

packages/aws-serverless/src/index.ts

+1
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,7 @@ export {
9999
mysqlIntegration,
100100
mysql2Integration,
101101
redisIntegration,
102+
tediousIntegration,
102103
nestIntegration,
103104
setupNestErrorHandler,
104105
postgresIntegration,

packages/bun/src/index.ts

+1
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,7 @@ export {
120120
mysqlIntegration,
121121
mysql2Integration,
122122
redisIntegration,
123+
tediousIntegration,
123124
nestIntegration,
124125
setupNestErrorHandler,
125126
postgresIntegration,

packages/google-cloud-serverless/src/index.ts

+1
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,7 @@ export {
9999
mysqlIntegration,
100100
mysql2Integration,
101101
redisIntegration,
102+
tediousIntegration,
102103
nestIntegration,
103104
setupNestErrorHandler,
104105
postgresIntegration,

packages/node/package.json

+1
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,7 @@
9090
"@opentelemetry/instrumentation-nestjs-core": "0.40.0",
9191
"@opentelemetry/instrumentation-pg": "0.44.0",
9292
"@opentelemetry/instrumentation-redis-4": "0.42.0",
93+
"@opentelemetry/instrumentation-tedious": "0.15.0",
9394
"@opentelemetry/instrumentation-undici": "0.6.0",
9495
"@opentelemetry/resources": "^1.26.0",
9596
"@opentelemetry/sdk-trace-base": "^1.26.0",

packages/node/src/index.ts

+1
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ export { hapiIntegration, setupHapiErrorHandler } from './integrations/tracing/h
2828
export { koaIntegration, setupKoaErrorHandler } from './integrations/tracing/koa';
2929
export { connectIntegration, setupConnectErrorHandler } from './integrations/tracing/connect';
3030
export { spotlightIntegration } from './integrations/spotlight';
31+
export { tediousIntegration } from './integrations/tracing/tedious';
3132
export { genericPoolIntegration } from './integrations/tracing/genericPool';
3233
export { dataloaderIntegration } from './integrations/tracing/dataloader';
3334
export { amqplibIntegration } from './integrations/tracing/amqplib';

packages/node/src/integrations/tracing/index.ts

+3
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import { instrumentMysql2, mysql2Integration } from './mysql2';
1818
import { instrumentNest, nestIntegration } from './nest/nest';
1919
import { instrumentPostgres, postgresIntegration } from './postgres';
2020
import { instrumentRedis, redisIntegration } from './redis';
21+
import { instrumentTedious, tediousIntegration } from './tedious';
2122

2223
/**
2324
* With OTEL, all performance integrations will be added, as OTEL only initializes them when the patched package is actually required.
@@ -41,6 +42,7 @@ export function getAutoPerformanceIntegrations(): Integration[] {
4142
hapiIntegration(),
4243
koaIntegration(),
4344
connectIntegration(),
45+
tediousIntegration(),
4446
genericPoolIntegration(),
4547
kafkaIntegration(),
4648
amqplibIntegration(),
@@ -71,6 +73,7 @@ export function getOpenTelemetryInstrumentationToPreload(): (((options?: any) =>
7173
instrumentHapi,
7274
instrumentGraphql,
7375
instrumentRedis,
76+
instrumentTedious,
7477
instrumentGenericPool,
7578
instrumentAmqplib,
7679
];
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
import { TediousInstrumentation } from '@opentelemetry/instrumentation-tedious';
2+
import { SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN, defineIntegration, spanToJSON } from '@sentry/core';
3+
import type { IntegrationFn } from '@sentry/types';
4+
import { generateInstrumentOnce } from '../../otel/instrument';
5+
6+
const TEDIUS_INSTRUMENTED_METHODS = new Set([
7+
'callProcedure',
8+
'execSql',
9+
'execSqlBatch',
10+
'execBulkLoad',
11+
'prepare',
12+
'execute',
13+
]);
14+
15+
const INTEGRATION_NAME = 'Tedious';
16+
17+
export const instrumentTedious = generateInstrumentOnce(INTEGRATION_NAME, () => new TediousInstrumentation({}));
18+
19+
const _tediousIntegration = (() => {
20+
return {
21+
name: INTEGRATION_NAME,
22+
setupOnce() {
23+
instrumentTedious();
24+
},
25+
26+
setup(client) {
27+
client.on('spanStart', span => {
28+
const { description, data } = spanToJSON(span);
29+
// Tedius integration always set a span name and `db.system` attribute to `mssql`.
30+
if (!description || data?.['db.system'] !== 'mssql') {
31+
return;
32+
}
33+
34+
const operation = description?.split(' ')[0] || '';
35+
if (TEDIUS_INSTRUMENTED_METHODS.has(operation)) {
36+
span.setAttribute(SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN, 'auto.db.otel.tedious');
37+
}
38+
});
39+
},
40+
};
41+
}) satisfies IntegrationFn;
42+
43+
/**
44+
* Tedious integration
45+
*
46+
* Capture tracing data for tedious.
47+
*/
48+
export const tediousIntegration = defineIntegration(_tediousIntegration);

packages/remix/src/index.server.ts

+1
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,7 @@ export {
124124
startSession,
125125
startSpan,
126126
startSpanManual,
127+
tediousIntegration,
127128
trpcMiddleware,
128129
withActiveSpan,
129130
withIsolationScope,

packages/solidstart/src/server/index.ts

+1
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,7 @@ export {
115115
startSession,
116116
startSpan,
117117
startSpanManual,
118+
tediousIntegration,
118119
trpcMiddleware,
119120
withActiveSpan,
120121
withIsolationScope,

packages/sveltekit/src/server/index.ts

+1
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,7 @@ export {
117117
startSession,
118118
startSpan,
119119
startSpanManual,
120+
tediousIntegration,
120121
trpcMiddleware,
121122
withActiveSpan,
122123
withIsolationScope,

0 commit comments

Comments
 (0)