Skip to content

Commit 7774f9b

Browse files
committed
fix(instrumentation-pg): connection string parsing
1 parent cb0515a commit 7774f9b

File tree

4 files changed

+93
-1
lines changed

4 files changed

+93
-1
lines changed

plugins/node/opentelemetry-instrumentation-pg/src/internal-types.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ export interface PgPoolOptionsParams {
5353
user: string;
5454
idleTimeoutMillis: number; // the minimum amount of time that an object may sit idle in the pool before it is eligible for eviction due to idle time
5555
maxClient: number; // maximum size of the pool
56+
connectionString?: string; // connection string if provided directly
5657
}
5758

5859
export const EVENT_LISTENERS_SET = Symbol(

plugins/node/opentelemetry-instrumentation-pg/src/utils.ts

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -106,7 +106,26 @@ export function parseNormalizedOperationName(queryText: string) {
106106
return sqlCommand.endsWith(';') ? sqlCommand.slice(0, -1) : sqlCommand;
107107
}
108108

109-
export function getConnectionString(params: PgParsedConnectionParams) {
109+
export function parseAndMaskConnectionString(connectionString: string): string {
110+
try {
111+
// Parse the connection string
112+
const url = new URL(connectionString);
113+
114+
// Remove all auth information (username and password)
115+
url.username = '';
116+
url.password = '';
117+
118+
return url.toString();
119+
} catch (e) {
120+
// If parsing fails, return a generic connection string
121+
return 'postgresql://localhost';
122+
}
123+
}
124+
125+
export function getConnectionString(params: PgParsedConnectionParams | PgPoolOptionsParams) {
126+
if ('connectionString' in params && params.connectionString) {
127+
return parseAndMaskConnectionString(params.connectionString);
128+
}
110129
const host = params.host || 'localhost';
111130
const port = params.port || 5432;
112131
const database = params.database || '';

plugins/node/opentelemetry-instrumentation-pg/test/pg-pool.test.ts

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -221,6 +221,36 @@ describe('pg-pool', () => {
221221
});
222222
});
223223

224+
// Test connection string support
225+
it('should handle connection string in pool options', async () => {
226+
const connectionString = `postgresql://${CONFIG.user}:${CONFIG.password}@${CONFIG.host}:${CONFIG.port}/${CONFIG.database}`;
227+
const poolWithConnString = new pgPool({
228+
connectionString,
229+
});
230+
231+
const expectedAttributes = {
232+
[SEMATTRS_DB_SYSTEM]: DBSYSTEMVALUES_POSTGRESQL,
233+
[SEMATTRS_DB_NAME]: CONFIG.database,
234+
[SEMATTRS_NET_PEER_NAME]: CONFIG.host,
235+
[SEMATTRS_DB_CONNECTION_STRING]: connectionString,
236+
[SEMATTRS_NET_PEER_PORT]: CONFIG.port,
237+
[SEMATTRS_DB_USER]: CONFIG.user,
238+
[AttributeNames.MAX_CLIENT]: CONFIG.maxClient,
239+
[AttributeNames.IDLE_TIMEOUT_MILLIS]: CONFIG.idleTimeoutMillis,
240+
};
241+
242+
const events: TimedEvent[] = [];
243+
const span = provider.getTracer('test-pg-pool').startSpan('test span');
244+
245+
await context.with(trace.setSpan(context.active(), span), async () => {
246+
const client = await poolWithConnString.connect();
247+
runCallbackTest(span, expectedAttributes, events, unsetStatus, 2, 1);
248+
client.release();
249+
});
250+
251+
await poolWithConnString.end();
252+
});
253+
224254
// callback - checkout a client
225255
it('should not return a promise if callback is provided', done => {
226256
const pgPoolAttributes = {

plugins/node/opentelemetry-instrumentation-pg/test/utils.test.ts

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -254,4 +254,46 @@ describe('utils.ts', () => {
254254
);
255255
});
256256
});
257+
258+
describe('.parseAndMaskConnectionString()', () => {
259+
it('should remove all auth information from connection string', () => {
260+
const connectionString = 'postgresql://user:password123@localhost:5432/dbname';
261+
assert.strictEqual(
262+
utils.parseAndMaskConnectionString(connectionString),
263+
'postgresql://localhost:5432/dbname'
264+
);
265+
});
266+
267+
it('should remove username when no password is present', () => {
268+
const connectionString = 'postgresql://user@localhost:5432/dbname';
269+
assert.strictEqual(
270+
utils.parseAndMaskConnectionString(connectionString),
271+
'postgresql://localhost:5432/dbname'
272+
);
273+
});
274+
275+
it('should preserve connection string when no auth is present', () => {
276+
const connectionString = 'postgresql://localhost:5432/dbname';
277+
assert.strictEqual(
278+
utils.parseAndMaskConnectionString(connectionString),
279+
'postgresql://localhost:5432/dbname'
280+
);
281+
});
282+
283+
it('should preserve query parameters while removing auth', () => {
284+
const connectionString = 'postgresql://user:pass@localhost/dbname?sslmode=verify-full&application_name=myapp';
285+
assert.strictEqual(
286+
utils.parseAndMaskConnectionString(connectionString),
287+
'postgresql://localhost/dbname?sslmode=verify-full&application_name=myapp'
288+
);
289+
});
290+
291+
it('should handle invalid connection string', () => {
292+
const connectionString = 'not-a-valid-url';
293+
assert.strictEqual(
294+
utils.parseAndMaskConnectionString(connectionString),
295+
'postgresql://localhost'
296+
);
297+
});
298+
});
257299
});

0 commit comments

Comments
 (0)