From 4adaf1cc121016c33a03bb6ceb65362c05c26b7a Mon Sep 17 00:00:00 2001 From: Jacek Tomaszewski Date: Sun, 7 Dec 2025 21:10:45 +0100 Subject: [PATCH] feat(instrumentation-pg): add skipConnectSpans option MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add a configuration option to skip creating `pg.connect` and `pg-pool.connect` spans while preserving query spans and pool metrics. This helps reduce trace noise in high-throughput scenarios where connection time is not of interest. - Add `skipConnectSpans?: boolean` to PgInstrumentationConfig - Skip span creation for both client and pool connect operations - Still set up event listeners for pool metrics when skipping spans - Add tests for new option - Update README with new option documentation 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- packages/instrumentation-pg/README.md | 1 + .../instrumentation-pg/src/instrumentation.ts | 17 ++++-- packages/instrumentation-pg/src/types.ts | 8 +++ .../instrumentation-pg/test/pg-pool.test.ts | 61 +++++++++++++++++++ 4 files changed, 83 insertions(+), 4 deletions(-) diff --git a/packages/instrumentation-pg/README.md b/packages/instrumentation-pg/README.md index 518acc7ad7..cda03756bc 100644 --- a/packages/instrumentation-pg/README.md +++ b/packages/instrumentation-pg/README.md @@ -50,6 +50,7 @@ PostgreSQL instrumentation has few options available to choose from. You can set | `responseHook` | `PgInstrumentationExecutionResponseHook` (function) | Function for adding custom span attributes from db response | | `requireParentSpan` | `boolean` | If true, requires a parent span to create new spans (default false) | | `addSqlCommenterCommentToQueries` | `boolean` | If true, adds [sqlcommenter](https://github.com/open-telemetry/opentelemetry-sqlcommenter) specification compliant comment to queries with tracing context (default false). _NOTE: A comment will not be added to queries that already contain `--` or `/* ... */` in them, even if these are not actually part of comments_ | +| `skipConnectSpans` | `boolean` | If true, `pg.connect` and `pg-pool.connect` spans will not be created. Query spans and pool metrics are still recorded (default false) | ## Semantic Conventions diff --git a/packages/instrumentation-pg/src/instrumentation.ts b/packages/instrumentation-pg/src/instrumentation.ts index 87811bd8a2..cc754461f0 100644 --- a/packages/instrumentation-pg/src/instrumentation.ts +++ b/packages/instrumentation-pg/src/instrumentation.ts @@ -249,7 +249,9 @@ export class PgInstrumentation extends InstrumentationBase { return function connect(this: pgTypes.Client, callback?: Function) { - if (utils.shouldSkipInstrumentation(plugin.getConfig())) { + const config = plugin.getConfig(); + + if (utils.shouldSkipInstrumentation(config) || config.skipConnectSpans) { return original.call(this, callback); } @@ -574,7 +576,16 @@ export class PgInstrumentation extends InstrumentationBase { return function connect(this: PgPoolExtended, callback?: PgPoolCallback) { - if (utils.shouldSkipInstrumentation(plugin.getConfig())) { + const config = plugin.getConfig(); + + if (utils.shouldSkipInstrumentation(config)) { + return originalConnect.call(this, callback as any); + } + + // Still set up event listeners for metrics even when skipping spans + plugin._setPoolConnectEventListeners(this); + + if (config.skipConnectSpans) { return originalConnect.call(this, callback as any); } @@ -587,8 +598,6 @@ export class PgInstrumentation extends InstrumentationBase { const spans = memoryExporter.getFinishedSpans(); assert.strictEqual(spans.length, 0); }); + + it('should not create connect spans when skipConnectSpans=true', async () => { + const newPool = new pgPool(CONFIG); + create({ + skipConnectSpans: true, + }); + const span = provider.getTracer('test-pg-pool').startSpan('test span'); + await context.with(trace.setSpan(context.active(), span), async () => { + const client = await newPool.connect(); + try { + await client.query('SELECT NOW()'); + } finally { + client.release(); + } + }); + await newPool.end(); + + const spans = memoryExporter.getFinishedSpans(); + const poolConnectSpans = spans.filter(s => s.name === 'pg-pool.connect'); + const clientConnectSpans = spans.filter(s => s.name === 'pg.connect'); + const querySpans = spans.filter(s => s.name.startsWith('pg.query')); + + assert.strictEqual( + poolConnectSpans.length, + 0, + 'Expected no pg-pool.connect spans' + ); + assert.strictEqual( + clientConnectSpans.length, + 0, + 'Expected no pg.connect spans' + ); + assert.ok(querySpans.length > 0, 'Expected query spans to be created'); + }); + + it('should still record pool metrics when skipConnectSpans=true', async () => { + const metricReader = testUtils.initMeterProvider(instrumentation); + const newPool = new pgPool(CONFIG); + create({ + skipConnectSpans: true, + }); + + const client = await newPool.connect(); + try { + await client.query('SELECT NOW()'); + } finally { + client.release(); + } + + const { resourceMetrics, errors } = await metricReader.collect(); + assert.deepEqual(errors, [], 'expected no errors during metric collection'); + + const metrics = resourceMetrics.scopeMetrics[0].metrics; + assert.strictEqual( + metrics[1].descriptor.name, + METRIC_DB_CLIENT_CONNECTION_COUNT, + 'Expected connection count metric' + ); + + await newPool.end(); + }); }); describe('#pool.query()', () => {