Skip to content
Merged
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ export interface PgPoolOptionsParams {
user: string;
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
maxClient: number; // maximum size of the pool
connectionString?: string; // connection string if provided directly
}

export const EVENT_LISTENERS_SET = Symbol(
Expand Down
42 changes: 37 additions & 5 deletions plugins/node/opentelemetry-instrumentation-pg/src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,28 @@ export function parseNormalizedOperationName(queryText: string) {
return sqlCommand.endsWith(';') ? sqlCommand.slice(0, -1) : sqlCommand;
}

export function getConnectionString(params: PgParsedConnectionParams) {
export function parseAndMaskConnectionString(connectionString: string): string {
try {
// Parse the connection string
const url = new URL(connectionString);

// Remove all auth information (username and password)
url.username = '';
url.password = '';

return url.toString();
} catch (e) {
// If parsing fails, return a generic connection string
return 'postgresql://localhost:5432/';
}
}

export function getConnectionString(
params: PgParsedConnectionParams | PgPoolOptionsParams
) {
if ('connectionString' in params && params.connectionString) {
return parseAndMaskConnectionString(params.connectionString);
}
const host = params.host || 'localhost';
const port = params.port || 5432;
const database = params.database || '';
Expand Down Expand Up @@ -138,13 +159,24 @@ export function getSemanticAttributesFromConnection(
}

export function getSemanticAttributesFromPool(params: PgPoolOptionsParams) {
// const connectionParams = getConnectionParams(params);
Copy link
Contributor

Choose a reason for hiding this comment

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

looks like you forgot to remove this line

Copy link
Contributor Author

Choose a reason for hiding this comment

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

oh, right! fixed


let url: URL | undefined;
try {
url = params.connectionString
? new URL(params.connectionString)
: undefined;
} catch (e) {
url = undefined;
}

return {
[SEMATTRS_DB_SYSTEM]: DBSYSTEMVALUES_POSTGRESQL,
[SEMATTRS_DB_NAME]: params.database, // required
[SEMATTRS_DB_NAME]: url?.pathname.slice(1) ?? params.database, // required
[SEMATTRS_DB_CONNECTION_STRING]: getConnectionString(params), // required
[SEMATTRS_NET_PEER_NAME]: params.host, // required
[SEMATTRS_NET_PEER_PORT]: getPort(params.port),
[SEMATTRS_DB_USER]: params.user,
[SEMATTRS_NET_PEER_NAME]: url?.hostname ?? params.host, // required
[SEMATTRS_NET_PEER_PORT]: Number(url?.port) || getPort(params.port),
[SEMATTRS_DB_USER]: url?.username ?? params.user,
[AttributeNames.IDLE_TIMEOUT_MILLIS]: params.idleTimeoutMillis,
[AttributeNames.MAX_CLIENT]: params.maxClient,
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -221,6 +221,36 @@ describe('pg-pool', () => {
});
});

// Test connection string support
it('should handle connection string in pool options', async () => {
const connectionString = `postgresql://${CONFIG.user}:${CONFIG.password}@${CONFIG.host}:${CONFIG.port}/${CONFIG.database}`;
const poolWithConnString = new pgPool({
connectionString,
idleTimeoutMillis: CONFIG.idleTimeoutMillis,
});

const expectedAttributes = {
[SEMATTRS_DB_SYSTEM]: DBSYSTEMVALUES_POSTGRESQL,
[SEMATTRS_DB_NAME]: CONFIG.database,
[SEMATTRS_NET_PEER_NAME]: CONFIG.host,
[SEMATTRS_DB_CONNECTION_STRING]: `postgresql://${CONFIG.host}:${CONFIG.port}/${CONFIG.database}`,
[SEMATTRS_NET_PEER_PORT]: CONFIG.port,
[SEMATTRS_DB_USER]: CONFIG.user,
[AttributeNames.IDLE_TIMEOUT_MILLIS]: CONFIG.idleTimeoutMillis,
};

const events: TimedEvent[] = [];
const span = provider.getTracer('test-pg-pool').startSpan('test span');

await context.with(trace.setSpan(context.active(), span), async () => {
const client = await poolWithConnString.connect();
runCallbackTest(span, expectedAttributes, events, unsetStatus, 2, 1);
client.release();
});

await poolWithConnString.end();
});

// callback - checkout a client
it('should not return a promise if callback is provided', done => {
const pgPoolAttributes = {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -254,4 +254,48 @@ describe('utils.ts', () => {
);
});
});

describe('.parseAndMaskConnectionString()', () => {
it('should remove all auth information from connection string', () => {
const connectionString =
'postgresql://user:password123@localhost:5432/dbname';
assert.strictEqual(
utils.parseAndMaskConnectionString(connectionString),
'postgresql://localhost:5432/dbname'
);
});

it('should remove username when no password is present', () => {
const connectionString = 'postgresql://user@localhost:5432/dbname';
assert.strictEqual(
utils.parseAndMaskConnectionString(connectionString),
'postgresql://localhost:5432/dbname'
);
});

it('should preserve connection string when no auth is present', () => {
const connectionString = 'postgresql://localhost:5432/dbname';
assert.strictEqual(
utils.parseAndMaskConnectionString(connectionString),
'postgresql://localhost:5432/dbname'
);
});

it('should preserve query parameters while removing auth', () => {
const connectionString =
'postgresql://user:pass@localhost/dbname?sslmode=verify-full&application_name=myapp';
assert.strictEqual(
utils.parseAndMaskConnectionString(connectionString),
'postgresql://localhost/dbname?sslmode=verify-full&application_name=myapp'
);
});

it('should handle invalid connection string', () => {
const connectionString = 'not-a-valid-url';
assert.strictEqual(
utils.parseAndMaskConnectionString(connectionString),
'postgresql://localhost:5432/'
);
});
});
});