Skip to content

Conversation

@onurtemizkan
Copy link
Collaborator

@onurtemizkan onurtemizkan commented Oct 17, 2025

Resolves: #17889

This PR rewrites the postgresjs instrumentation with a new architecture:

  • Added ESM support via replaceExports

  • Moved to main export wrapping instead of internal module patching

    • Previously, we were patching connection.js and query.js internal modules
    • New approach: We are wrapping the main postgres module export to intercept sql instance creation
  • Connection context is now stored directly on sql instances using CONNECTION_CONTEXT_SYMBOL

  • Query.prototype fallback (CJS only)

    • Patches Query.prototype.handle as a fallback for pre-existing sql instances
    • Uses QUERY_FROM_INSTRUMENTED_SQL marker to prevent duplicate spans

Also,

  • Improved SQL sanitization
  • port attribute is now stored as a number per OTEL semantic conventions
  • Added fallback regex extraction for operation name when command isn't available

@github-actions
Copy link
Contributor

github-actions bot commented Oct 17, 2025

size-limit report 📦

Path Size % Change Change
@sentry/browser 24.8 kB - -
@sentry/browser - with treeshaking flags 23.31 kB - -
@sentry/browser (incl. Tracing) 41.54 kB - -
@sentry/browser (incl. Tracing, Profiling) 46.13 kB - -
@sentry/browser (incl. Tracing, Replay) 79.96 kB - -
@sentry/browser (incl. Tracing, Replay) - with treeshaking flags 69.69 kB - -
@sentry/browser (incl. Tracing, Replay with Canvas) 84.64 kB - -
@sentry/browser (incl. Tracing, Replay, Feedback) 96.88 kB - -
@sentry/browser (incl. Feedback) 41.48 kB - -
@sentry/browser (incl. sendFeedback) 29.49 kB - -
@sentry/browser (incl. FeedbackAsync) 34.47 kB - -
@sentry/react 26.52 kB - -
@sentry/react (incl. Tracing) 43.74 kB - -
@sentry/vue 29.25 kB - -
@sentry/vue (incl. Tracing) 43.34 kB - -
@sentry/svelte 24.82 kB - -
CDN Bundle 27.21 kB - -
CDN Bundle (incl. Tracing) 42.21 kB - -
CDN Bundle (incl. Tracing, Replay) 78.75 kB - -
CDN Bundle (incl. Tracing, Replay, Feedback) 84.2 kB - -
CDN Bundle - uncompressed 79.96 kB - -
CDN Bundle (incl. Tracing) - uncompressed 125.34 kB - -
CDN Bundle (incl. Tracing, Replay) - uncompressed 241.37 kB - -
CDN Bundle (incl. Tracing, Replay, Feedback) - uncompressed 254.13 kB - -
@sentry/nextjs (client) 45.96 kB - -
@sentry/sveltekit (client) 41.9 kB - -
@sentry/node-core 51.27 kB - -
@sentry/node 160.33 kB +0.57% +894 B 🔺
@sentry/node - without tracing 92.85 kB -0.01% -1 B 🔽
@sentry/aws-serverless 108.14 kB -0.01% -1 B 🔽

View base workflow run

@github-actions
Copy link
Contributor

github-actions bot commented Oct 17, 2025

node-overhead report 🧳

Note: This is a synthetic benchmark with a minimal express app and does not necessarily reflect the real-world performance impact in an application.

Scenario Requests/s % of Baseline Prev. Requests/s Change %
GET Baseline 8,884 - 11,766 -24%
GET With Sentry 1,816 20% 2,078 -13%
GET With Sentry (error only) 6,210 70% 7,762 -20%
POST Baseline 1,212 - 1,203 +1%
POST With Sentry 598 49% 614 -3%
POST With Sentry (error only) 1,067 88% 1,064 +0%
MYSQL Baseline 3,309 - 4,068 -19%
MYSQL With Sentry 445 13% 577 -23%
MYSQL With Sentry (error only) 2,701 82% 3,350 -19%

View base workflow run

@onurtemizkan onurtemizkan force-pushed the onur/postgres-js-esm-support branch 2 times, most recently from f635159 to 96641f9 Compare October 17, 2025 10:06
@onurtemizkan onurtemizkan marked this pull request as ready for review November 21, 2025 14:28
@onurtemizkan onurtemizkan requested a review from Copilot November 21, 2025 14:31
Copilot finished reviewing on behalf of onurtemizkan November 21, 2025 14:32
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR adds ESM (ECMAScript Module) support for postgres.js instrumentation by refactoring the patching approach from file-based module instrumentation to module export wrapping. The instrumentation now wraps the main postgres export function to intercept SQL instance creation and instrument query methods dynamically.

Key changes:

  • Refactored instrumentation to wrap the main postgres export instead of patching internal files
  • Added comprehensive ESM test coverage alongside existing CJS tests
  • Enhanced SQL query sanitization to handle PostgreSQL placeholders ($1, $2, etc.)

Reviewed changes

Copilot reviewed 14 out of 14 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
packages/node/src/integrations/tracing/postgresjs.ts Complete rewrite of instrumentation using module export wrapping; adds ESM support and improves query/connection handling
dev-packages/node-integration-tests/suites/tracing/postgresjs/test.ts Added ESM tests, requestHook tests, URL initialization tests, and unsafe query tests
dev-packages/node-integration-tests/suites/tracing/postgresjs/scenario.mjs New ESM scenario file for testing ESM imports
dev-packages/node-integration-tests/suites/tracing/postgresjs/scenario.cjs New CJS scenario file with consolidated initialization
dev-packages/node-integration-tests/suites/tracing/postgresjs/scenario.js Enhanced with additional query test cases
dev-packages/node-integration-tests/suites/tracing/postgresjs/scenario-url.mjs New ESM test scenario for URL-based postgres initialization
dev-packages/node-integration-tests/suites/tracing/postgresjs/scenario-url.cjs New CJS test scenario for URL-based postgres initialization
dev-packages/node-integration-tests/suites/tracing/postgresjs/scenario-unsafe.cjs New test scenario for sql.unsafe() method
dev-packages/node-integration-tests/suites/tracing/postgresjs/scenario-requestHook.mjs New ESM test scenario for requestHook functionality
dev-packages/node-integration-tests/suites/tracing/postgresjs/scenario-requestHook.js New CJS test scenario for requestHook functionality
dev-packages/node-integration-tests/suites/tracing/postgresjs/instrument.mjs New ESM instrumentation file for tests
dev-packages/node-integration-tests/suites/tracing/postgresjs/instrument.cjs New CJS instrumentation file for tests
dev-packages/node-integration-tests/suites/tracing/postgresjs/instrument-requestHook.mjs New ESM instrumentation with requestHook configuration
dev-packages/node-integration-tests/suites/tracing/postgresjs/instrument-requestHook.cjs New CJS instrumentation with requestHook configuration
Comments suppressed due to low confidence (1)

dev-packages/node-integration-tests/suites/tracing/postgresjs/scenario.js:83

  • The floating promise should be handled with proper error handling. Consider using run().catch(console.error) or wrapping in an async IIFE to ensure errors are not silently swallowed.
// eslint-disable-next-line @typescript-eslint/no-floating-promises
run();

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines 363 to 367
let spanName = sanitizedSqlQuery?.trim() || '';
if (!spanName) {
// Fallback: try to extract operation from the sanitized query
const operationMatch = sanitizedSqlQuery?.match(SQL_OPERATION_REGEX);
spanName = operationMatch?.[1] ? `db.${operationMatch[1].toLowerCase()}` : 'db.query';
Copy link

Copilot AI Nov 21, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Logic error: spanName is set to the trimmed sanitizedSqlQuery on line 363, which means the condition !spanName on line 364 will only be true if sanitizedSqlQuery is empty or undefined. However, line 366 attempts to match the same sanitizedSqlQuery against SQL_OPERATION_REGEX. This code path is unreachable when sanitizedSqlQuery has content. The fallback logic appears to be intended for cases where sanitizedSqlQuery exists but is not suitable as a span name, but the current implementation doesn't achieve this.

Suggested change
let spanName = sanitizedSqlQuery?.trim() || '';
if (!spanName) {
// Fallback: try to extract operation from the sanitized query
const operationMatch = sanitizedSqlQuery?.match(SQL_OPERATION_REGEX);
spanName = operationMatch?.[1] ? `db.${operationMatch[1].toLowerCase()}` : 'db.query';
let spanName: string;
const operationMatch = sanitizedSqlQuery?.match(SQL_OPERATION_REGEX);
if (operationMatch?.[1]) {
spanName = `db.${operationMatch[1].toLowerCase()}`;
} else if (sanitizedSqlQuery?.trim()) {
spanName = sanitizedSqlQuery.trim();
} else {
spanName = 'db.query';

Copilot uses AI. Check for mistakes.
InstrumentationBase,
InstrumentationNodeModuleDefinition,
InstrumentationNodeModuleFile,
safeExecuteInTheMiddle,
Copy link

Copilot AI Nov 21, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The import of safeExecuteInTheMiddle should be removed from '@opentelemetry/instrumentation' and imported from '@sentry/core' instead, as indicated by the usage pattern at line 394. However, checking the actual usage shows it is used correctly. If this import is not used elsewhere in the file, it should be removed.

Copilot uses AI. Check for mistakes.
@onurtemizkan onurtemizkan force-pushed the onur/postgres-js-esm-support branch from d9a0dfa to 35bf27d Compare November 26, 2025 14:56
@onurtemizkan onurtemizkan force-pushed the onur/postgres-js-esm-support branch from 35bf27d to 5143c10 Compare November 27, 2025 10:54
spanName = sanitizedSqlQuery.trim();
} else {
spanName = 'db.query';
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bug: Span description uses operation prefix instead of full query

The span name logic sets spanName to db.${operation} (e.g., db.create, db.select) for recognized SQL operations, but this value becomes the span's description when serialized (as confirmed in sentrySpan.ts line 226: description: this._name). The tests in this same PR explicitly expect the span description to be the full sanitized SQL query (e.g., 'CREATE TABLE "User" (...)'), not the abbreviated db.create format. This mismatch means the detailed test expectations will fail.

Fix in Cursor Fix in Web

@onurtemizkan onurtemizkan force-pushed the onur/postgres-js-esm-support branch from 5143c10 to 898802b Compare November 27, 2025 17:16
Comment on lines 482 to 510
sqlQuery
.replace(/\s+/g, ' ')
.trim() // Remove extra spaces including newlines and trim
.substring(0, 1024) // Truncate to 1024 characters
.replace(/--.*?(\r?\n|$)/g, '') // Single line comments
// Remove comments first (they may contain newlines and extra spaces)
.replace(/--.*$/gm, '') // Single line comments (multiline mode)
.replace(/\/\*[\s\S]*?\*\//g, '') // Multi-line comments

This comment was marked as outdated.

@onurtemizkan onurtemizkan force-pushed the onur/postgres-js-esm-support branch from b0cf889 to 29ff73f Compare November 28, 2025 14:58
@onurtemizkan onurtemizkan requested a review from Lms24 November 28, 2025 15:29
.replace(/\b\d+\b/g, '?') // Replace standalone numbers
// Collapse IN and in clauses (eg. IN (?, ?, ?, ?) to IN (?))
.replace(/\bIN\b\s*\(\s*\?(?:\s*,\s*\?)*\s*\)/gi, 'IN (?)')
.substring(0, 1024) // Truncate to 1024 characters AFTER sanitization
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

m: let's avoid truncating the query string. We decided to avoid truncation as much as possible in the SDK (and rather have Relay do that).

Suggested change
.substring(0, 1024) // Truncate to 1024 characters AFTER sanitization
.substring(0, 1024) // Truncate to 1024 characters AFTER sanitization

Comment on lines +508 to +509
// Remove comments first (they may contain newlines and extra spaces)
.replace(/--.*$/gm, '') // Single line comments (multiline mode)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

l: Let's add a couple of unit tests for the sanitization

op: 'db',
},
(span: Span) => {
addOriginToSpan(span, 'auto.db.otel.postgres');
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

l: maybe I've missed something but this is our instrumentation, right? Or did we fork it from OTel?

Suggested change
addOriginToSpan(span, 'auto.db.otel.postgres');
addOriginToSpan(span, 'auto.db.postgresjs');

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, actually, that was a mistake in the initial implementation. I updated the origin to auto.db.postgresjs. But not sure whether that may make this a breaking change?

@onurtemizkan onurtemizkan force-pushed the onur/postgres-js-esm-support branch from 299da8a to 11a366d Compare December 2, 2025 11:42
Comment on lines 510 to 520
.replace(/\/\*[\s\S]*?\*\//g, '') // Multi-line comments
.replace(/;\s*$/, '') // Remove trailing semicolons
.replace(/\b\d+\b/g, '?') // Replace standalone numbers
// Collapse whitespace to a single space
// Collapse whitespace to a single space (after removing comments)
.replace(/\s+/g, ' ')
// Collapse IN and in clauses
// eg. IN (?, ?, ?, ?) to IN (?)
.replace(/\bIN\b\s*\(\s*\?(?:\s*,\s*\?)*\s*\)/g, 'IN (?)')
.trim() // Remove extra spaces and trim
// Replace standalone numbers and parameterized queries ($1, $2, etc.) BEFORE truncation
.replace(/\$\d+/g, '?') // Replace PostgreSQL placeholders ($1, $2, etc.)
.replace(/\b\d+\b/g, '?') // Replace standalone numbers
// Collapse IN and in clauses (eg. IN (?, ?, ?, ?) to IN (?))
.replace(/\bIN\b\s*\(\s*\?(?:\s*,\s*\?)*\s*\)/gi, 'IN (?)')
);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bug: The _sanitizeSqlQuery() method is missing SQL query truncation, allowing arbitrarily long queries in span attributes.
Severity: HIGH | Confidence: High

🔍 Detailed Analysis

The _sanitizeSqlQuery() method in packages/node/src/integrations/tracing/postgresjs.ts no longer truncates SQL queries to 1024 characters before assigning them to ATTR_DB_QUERY_TEXT span attributes. This is a regression from the previous implementation, which included a .substring(0, 1024) operation. The absence of truncation can lead to arbitrarily long SQL queries being stored in span attributes, increasing payload sizes and potentially impacting transmission and storage efficiency.

💡 Suggested Fix

Re-add .substring(0, 1024) to the return statement of the _sanitizeSqlQuery() method in packages/node/src/integrations/tracing/postgresjs.ts to restore the original truncation behavior.

🤖 Prompt for AI Agent
Review the code at the location below. A potential bug has been identified by an AI
agent.
Verify if this is a real issue. If it is, propose a fix; if not, explain why it's not
valid.

Location: packages/node/src/integrations/tracing/postgresjs.ts#L507-L520

Potential issue: The `_sanitizeSqlQuery()` method in
`packages/node/src/integrations/tracing/postgresjs.ts` no longer truncates SQL queries
to 1024 characters before assigning them to `ATTR_DB_QUERY_TEXT` span attributes. This
is a regression from the previous implementation, which included a `.substring(0, 1024)`
operation. The absence of truncation can lead to arbitrarily long SQL queries being
stored in span attributes, increasing payload sizes and potentially impacting
transmission and storage efficiency.

Did we get this right? 👍 / 👎 to inform future reviews.
Reference ID: 4826878

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Sentry.postgresJsIntegration() not creating any DB spans

3 participants