Skip to content

Commit 837f3d8

Browse files
committed
feat(node): Add ESM support for postgres.js instrumentation
1 parent 4dc6c7b commit 837f3d8

File tree

6 files changed

+535
-183
lines changed

6 files changed

+535
-183
lines changed
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
const Sentry = require('@sentry/node');
2+
const { loggingTransport } = require('@sentry-internal/node-integration-tests');
3+
4+
Sentry.init({
5+
dsn: 'https://public@dsn.ingest.sentry.io/1337',
6+
release: '1.0',
7+
tracesSampleRate: 1.0,
8+
transport: loggingTransport,
9+
});
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
import * as Sentry from '@sentry/node';
2+
import { loggingTransport } from '@sentry-internal/node-integration-tests';
3+
4+
Sentry.init({
5+
dsn: 'https://public@dsn.ingest.sentry.io/1337',
6+
release: '1.0',
7+
tracesSampleRate: 1.0,
8+
transport: loggingTransport,
9+
});
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
const { loggingTransport } = require('@sentry-internal/node-integration-tests');
2+
const Sentry = require('@sentry/node');
3+
4+
Sentry.init({
5+
dsn: 'https://public@dsn.ingest.sentry.io/1337',
6+
release: '1.0',
7+
tracesSampleRate: 1.0,
8+
transport: loggingTransport,
9+
});
10+
11+
// Import postgres AFTER Sentry.init() so instrumentation is set up
12+
const postgres = require('postgres');
13+
14+
// Stop the process from exiting before the transaction is sent
15+
setInterval(() => {}, 1000);
16+
17+
const sql = postgres({ port: 5444, user: 'test', password: 'test', database: 'test_db' });
18+
19+
async function run() {
20+
await Sentry.startSpan(
21+
{
22+
name: 'Test Transaction',
23+
op: 'transaction',
24+
},
25+
async () => {
26+
try {
27+
await sql`
28+
CREATE TABLE "User" ("id" SERIAL NOT NULL,"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,"email" TEXT NOT NULL,"name" TEXT,CONSTRAINT "User_pkey" PRIMARY KEY ("id"));
29+
`;
30+
31+
await sql`
32+
INSERT INTO "User" ("email", "name") VALUES ('Foo', 'bar@baz.com');
33+
`;
34+
35+
await sql`
36+
UPDATE "User" SET "name" = 'Foo' WHERE "email" = 'bar@baz.com';
37+
`;
38+
39+
await sql`
40+
SELECT * FROM "User" WHERE "email" = 'bar@baz.com';
41+
`;
42+
43+
await sql`SELECT * from generate_series(1,1000) as x `.cursor(10, async rows => {
44+
await Promise.all(rows);
45+
});
46+
47+
await sql`
48+
DROP TABLE "User";
49+
`;
50+
51+
// This will be captured as an error as the table no longer exists
52+
await sql`
53+
SELECT * FROM "User" WHERE "email" = 'foo@baz.com';
54+
`;
55+
} finally {
56+
await sql.end();
57+
}
58+
},
59+
);
60+
}
61+
62+
run();
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
import * as Sentry from '@sentry/node';
2+
import postgres from 'postgres';
3+
4+
// Stop the process from exiting before the transaction is sent
5+
setInterval(() => {}, 1000);
6+
7+
const sql = postgres({ port: 5444, user: 'test', password: 'test', database: 'test_db' });
8+
9+
async function run() {
10+
await Sentry.startSpan(
11+
{
12+
name: 'Test Transaction',
13+
op: 'transaction',
14+
},
15+
async () => {
16+
try {
17+
await sql`
18+
CREATE TABLE "User" ("id" SERIAL NOT NULL,"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,"email" TEXT NOT NULL,"name" TEXT,CONSTRAINT "User_pkey" PRIMARY KEY ("id"));
19+
`;
20+
21+
await sql`
22+
INSERT INTO "User" ("email", "name") VALUES ('Foo', 'bar@baz.com');
23+
`;
24+
25+
await sql`
26+
UPDATE "User" SET "name" = 'Foo' WHERE "email" = 'bar@baz.com';
27+
`;
28+
29+
await sql`
30+
SELECT * FROM "User" WHERE "email" = 'bar@baz.com';
31+
`;
32+
33+
await sql`SELECT * from generate_series(1,1000) as x `.cursor(10, async rows => {
34+
await Promise.all(rows);
35+
});
36+
37+
await sql`
38+
DROP TABLE "User";
39+
`;
40+
41+
// This will be captured as an error as the table no longer exists
42+
await sql`
43+
SELECT * FROM "User" WHERE "email" = 'foo@baz.com';
44+
`;
45+
} finally {
46+
await sql.end();
47+
}
48+
},
49+
);
50+
}
51+
52+
// eslint-disable-next-line @typescript-eslint/no-floating-promises
53+
run();

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

Lines changed: 184 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,15 @@
1-
import { describe, expect, test } from 'vitest';
2-
import { createRunner } from '../../../utils/runner';
1+
import { afterAll, describe, expect, test } from 'vitest';
2+
import { cleanupChildProcesses, createRunner } from '../../../utils/runner';
33

44
const EXISTING_TEST_EMAIL = 'bar@baz.com';
55
const NON_EXISTING_TEST_EMAIL = 'foo@baz.com';
66

77
describe('postgresjs auto instrumentation', () => {
8-
test('should auto-instrument `postgres` package', { timeout: 60_000 }, async () => {
8+
afterAll(() => {
9+
cleanupChildProcesses();
10+
});
11+
12+
test('should auto-instrument `postgres` package (CJS)', { timeout: 60_000 }, async () => {
913
const EXPECTED_TRANSACTION = {
1014
transaction: 'Test Transaction',
1115
spans: expect.arrayContaining([
@@ -32,20 +36,192 @@ describe('postgresjs auto instrumentation', () => {
3236
timestamp: expect.any(Number),
3337
trace_id: expect.any(String),
3438
}),
39+
expect.objectContaining({
40+
data: expect.objectContaining({
41+
'db.namespace': 'test_db',
42+
'db.system.name': 'postgres',
43+
'db.operation.name': 'INSERT',
44+
'db.query.text': `INSERT INTO "User" ("email", "name") VALUES ('Foo', '${EXISTING_TEST_EMAIL}')`,
45+
'sentry.origin': 'auto.db.otel.postgres',
46+
'sentry.op': 'db',
47+
'server.address': 'localhost',
48+
'server.port': 5444,
49+
}),
50+
description: `INSERT INTO "User" ("email", "name") VALUES ('Foo', '${EXISTING_TEST_EMAIL}')`,
51+
op: 'db',
52+
status: 'ok',
53+
origin: 'auto.db.otel.postgres',
54+
parent_span_id: expect.any(String),
55+
span_id: expect.any(String),
56+
start_timestamp: expect.any(Number),
57+
timestamp: expect.any(Number),
58+
trace_id: expect.any(String),
59+
}),
60+
expect.objectContaining({
61+
data: expect.objectContaining({
62+
'db.namespace': 'test_db',
63+
'db.system.name': 'postgres',
64+
'db.operation.name': 'UPDATE',
65+
'db.query.text': `UPDATE "User" SET "name" = 'Foo' WHERE "email" = '${EXISTING_TEST_EMAIL}'`,
66+
'sentry.op': 'db',
67+
'sentry.origin': 'auto.db.otel.postgres',
68+
'server.address': 'localhost',
69+
'server.port': 5444,
70+
}),
71+
description: `UPDATE "User" SET "name" = 'Foo' WHERE "email" = '${EXISTING_TEST_EMAIL}'`,
72+
op: 'db',
73+
status: 'ok',
74+
origin: 'auto.db.otel.postgres',
75+
parent_span_id: expect.any(String),
76+
span_id: expect.any(String),
77+
start_timestamp: expect.any(Number),
78+
timestamp: expect.any(Number),
79+
trace_id: expect.any(String),
80+
}),
3581
expect.objectContaining({
3682
data: expect.objectContaining({
3783
'db.namespace': 'test_db',
3884
'db.system.name': 'postgres',
3985
'db.operation.name': 'SELECT',
86+
'db.query.text': `SELECT * FROM "User" WHERE "email" = '${EXISTING_TEST_EMAIL}'`,
87+
'sentry.op': 'db',
88+
'sentry.origin': 'auto.db.otel.postgres',
89+
'server.address': 'localhost',
90+
'server.port': 5444,
91+
}),
92+
description: `SELECT * FROM "User" WHERE "email" = '${EXISTING_TEST_EMAIL}'`,
93+
op: 'db',
94+
status: 'ok',
95+
origin: 'auto.db.otel.postgres',
96+
parent_span_id: expect.any(String),
97+
span_id: expect.any(String),
98+
start_timestamp: expect.any(Number),
99+
timestamp: expect.any(Number),
100+
trace_id: expect.any(String),
101+
}),
102+
expect.objectContaining({
103+
data: expect.objectContaining({
104+
'db.namespace': 'test_db',
105+
'db.system.name': 'postgres',
106+
'db.operation.name': 'SELECT',
107+
'db.query.text': 'SELECT * from generate_series(?,?) as x',
108+
'sentry.op': 'db',
109+
'sentry.origin': 'auto.db.otel.postgres',
110+
'server.address': 'localhost',
111+
'server.port': 5444,
112+
}),
113+
description: 'SELECT * from generate_series(?,?) as x',
114+
op: 'db',
115+
status: 'ok',
116+
origin: 'auto.db.otel.postgres',
117+
parent_span_id: expect.any(String),
118+
span_id: expect.any(String),
119+
start_timestamp: expect.any(Number),
120+
timestamp: expect.any(Number),
121+
trace_id: expect.any(String),
122+
}),
123+
expect.objectContaining({
124+
data: expect.objectContaining({
125+
'db.namespace': 'test_db',
126+
'db.system.name': 'postgres',
127+
'db.operation.name': 'DROP TABLE',
128+
'db.query.text': 'DROP TABLE "User"',
129+
'sentry.op': 'db',
130+
'sentry.origin': 'auto.db.otel.postgres',
131+
'server.address': 'localhost',
132+
'server.port': 5444,
133+
}),
134+
description: 'DROP TABLE "User"',
135+
op: 'db',
136+
status: 'ok',
137+
origin: 'auto.db.otel.postgres',
138+
parent_span_id: expect.any(String),
139+
span_id: expect.any(String),
140+
start_timestamp: expect.any(Number),
141+
timestamp: expect.any(Number),
142+
trace_id: expect.any(String),
143+
}),
144+
expect.objectContaining({
145+
data: expect.objectContaining({
146+
'db.namespace': 'test_db',
147+
'db.system.name': 'postgres',
148+
// No db.operation.name here, as this is an errored span
149+
'db.response.status_code': '42P01',
150+
'error.type': 'PostgresError',
151+
'db.query.text': `SELECT * FROM "User" WHERE "email" = '${NON_EXISTING_TEST_EMAIL}'`,
152+
'sentry.op': 'db',
153+
'sentry.origin': 'auto.db.otel.postgres',
154+
'server.address': 'localhost',
155+
'server.port': 5444,
156+
}),
157+
description: `SELECT * FROM "User" WHERE "email" = '${NON_EXISTING_TEST_EMAIL}'`,
158+
op: 'db',
159+
status: 'unknown_error',
160+
origin: 'auto.db.otel.postgres',
161+
parent_span_id: expect.any(String),
162+
span_id: expect.any(String),
163+
start_timestamp: expect.any(Number),
164+
timestamp: expect.any(Number),
165+
trace_id: expect.any(String),
166+
}),
167+
]),
168+
};
169+
170+
const EXPECTED_ERROR_EVENT = {
171+
event_id: expect.any(String),
172+
contexts: {
173+
trace: {
174+
trace_id: expect.any(String),
175+
span_id: expect.any(String),
176+
},
177+
},
178+
exception: {
179+
values: [
180+
{
181+
type: 'PostgresError',
182+
value: 'relation "User" does not exist',
183+
stacktrace: expect.objectContaining({
184+
frames: expect.arrayContaining([
185+
expect.objectContaining({
186+
function: 'handle',
187+
module: 'postgres.cjs.src:connection',
188+
filename: expect.any(String),
189+
lineno: expect.any(Number),
190+
colno: expect.any(Number),
191+
}),
192+
]),
193+
}),
194+
},
195+
],
196+
},
197+
};
198+
199+
await createRunner(__dirname, 'scenario.js')
200+
.withDockerCompose({ workingDirectory: [__dirname], readyMatches: ['port 5432'] })
201+
.expect({ transaction: EXPECTED_TRANSACTION })
202+
.expect({ event: EXPECTED_ERROR_EVENT })
203+
.start()
204+
.completed();
205+
});
206+
207+
test('should auto-instrument `postgres` package (ESM)', { timeout: 60_000 }, async () => {
208+
const EXPECTED_TRANSACTION = {
209+
transaction: 'Test Transaction',
210+
spans: expect.arrayContaining([
211+
expect.objectContaining({
212+
data: expect.objectContaining({
213+
'db.namespace': 'test_db',
214+
'db.system.name': 'postgres',
215+
'db.operation.name': 'CREATE TABLE',
40216
'db.query.text':
41-
"select b.oid, b.typarray from pg_catalog.pg_type a left join pg_catalog.pg_type b on b.oid = a.typelem where a.typcategory = 'A' group by b.oid, b.typarray order by b.oid",
217+
'CREATE TABLE "User" ("id" SERIAL NOT NULL,"createdAt" TIMESTAMP(?) NOT NULL DEFAULT CURRENT_TIMESTAMP,"email" TEXT NOT NULL,"name" TEXT,CONSTRAINT "User_pkey" PRIMARY KEY ("id"))',
42218
'sentry.op': 'db',
43219
'sentry.origin': 'auto.db.otel.postgres',
44220
'server.address': 'localhost',
45221
'server.port': 5444,
46222
}),
47223
description:
48-
"select b.oid, b.typarray from pg_catalog.pg_type a left join pg_catalog.pg_type b on b.oid = a.typelem where a.typcategory = 'A' group by b.oid, b.typarray order by b.oid",
224+
'CREATE TABLE "User" ("id" SERIAL NOT NULL,"createdAt" TIMESTAMP(?) NOT NULL DEFAULT CURRENT_TIMESTAMP,"email" TEXT NOT NULL,"name" TEXT,CONSTRAINT "User_pkey" PRIMARY KEY ("id"))',
49225
op: 'db',
50226
status: 'ok',
51227
origin: 'auto.db.otel.postgres',
@@ -203,7 +379,7 @@ describe('postgresjs auto instrumentation', () => {
203379
frames: expect.arrayContaining([
204380
expect.objectContaining({
205381
function: 'handle',
206-
module: 'postgres.cjs.src:connection',
382+
module: 'postgres.src:connection',
207383
filename: expect.any(String),
208384
lineno: expect.any(Number),
209385
colno: expect.any(Number),
@@ -215,7 +391,8 @@ describe('postgresjs auto instrumentation', () => {
215391
},
216392
};
217393

218-
await createRunner(__dirname, 'scenario.js')
394+
await createRunner(__dirname, 'scenario.mjs')
395+
.withFlags('--import', `${__dirname}/instrument.mjs`)
219396
.withDockerCompose({ workingDirectory: [__dirname], readyMatches: ['port 5432'] })
220397
.expect({ transaction: EXPECTED_TRANSACTION })
221398
.expect({ event: EXPECTED_ERROR_EVENT })

0 commit comments

Comments
 (0)