diff --git a/dev-packages/node-integration-tests/package.json b/dev-packages/node-integration-tests/package.json index 9243a1512884..0ac7fdbf0aa2 100644 --- a/dev-packages/node-integration-tests/package.json +++ b/dev-packages/node-integration-tests/package.json @@ -44,6 +44,7 @@ "cors": "^2.8.5", "cron": "^3.1.6", "express": "^4.17.3", + "generic-pool": "^3.9.0", "graphql": "^16.3.0", "http-terminator": "^3.2.0", "ioredis": "^5.4.1", diff --git a/dev-packages/node-integration-tests/suites/tracing/genericPool/scenario.js b/dev-packages/node-integration-tests/suites/tracing/genericPool/scenario.js new file mode 100644 index 000000000000..74d5f73693f5 --- /dev/null +++ b/dev-packages/node-integration-tests/suites/tracing/genericPool/scenario.js @@ -0,0 +1,71 @@ +const { loggingTransport } = require('@sentry-internal/node-integration-tests'); +const Sentry = require('@sentry/node'); + +Sentry.init({ + dsn: 'https://public@dsn.ingest.sentry.io/1337', + release: '1.0', + tracesSampleRate: 1.0, + transport: loggingTransport, +}); + +// Stop the process from exiting before the transaction is sent +setInterval(() => {}, 1000); + +const mysql = require('mysql'); +const genericPool = require('generic-pool'); + +const factory = { + create: function () { + return mysql.createConnection({ + user: 'root', + password: 'docker', + }); + }, + destroy: function (client) { + client.end(err => { + if (err) { + // eslint-disable-next-line no-console + console.error('Error while disconnecting MySQL:', err); + } + }); + }, +}; + +const opts = { + max: 10, + min: 2, +}; + +const myPool = genericPool.createPool(factory, opts); + +async function run() { + await Sentry.startSpan( + { + op: 'transaction', + name: 'Test Transaction', + }, + async () => { + try { + const client1 = await myPool.acquire(); + const client2 = await myPool.acquire(); + + client1.query('SELECT NOW()', function () { + myPool.release(client1); + }); + + client2.query('SELECT 1 + 1 AS solution', function () { + myPool.release(client2); + }); + } catch (err) { + // eslint-disable-next-line no-console + console.error('Error while pooling MySQL:', err); + } finally { + await myPool.drain(); + await myPool.clear(); + } + }, + ); +} + +// eslint-disable-next-line @typescript-eslint/no-floating-promises +run(); diff --git a/dev-packages/node-integration-tests/suites/tracing/genericPool/test.ts b/dev-packages/node-integration-tests/suites/tracing/genericPool/test.ts new file mode 100644 index 000000000000..a61782ae0fe5 --- /dev/null +++ b/dev-packages/node-integration-tests/suites/tracing/genericPool/test.ts @@ -0,0 +1,34 @@ +import { cleanupChildProcesses, createRunner } from '../../../utils/runner'; + +describe('genericPool auto instrumentation', () => { + afterAll(() => { + cleanupChildProcesses(); + }); + + test('should auto-instrument `genericPool` package when calling pool.require()', done => { + const EXPECTED_TRANSACTION = { + transaction: 'Test Transaction', + spans: expect.arrayContaining([ + expect.objectContaining({ + description: expect.stringMatching(/^generic-pool\.ac?quire/), + origin: 'auto.db.otel.generic-pool', + data: { + 'sentry.origin': 'auto.db.otel.generic-pool', + }, + status: 'ok', + }), + + expect.objectContaining({ + description: expect.stringMatching(/^generic-pool\.ac?quire/), + origin: 'auto.db.otel.generic-pool', + data: { + 'sentry.origin': 'auto.db.otel.generic-pool', + }, + status: 'ok', + }), + ]), + }; + + createRunner(__dirname, 'scenario.js').expect({ transaction: EXPECTED_TRANSACTION }).start(done); + }); +}); diff --git a/packages/astro/src/index.server.ts b/packages/astro/src/index.server.ts index 0895eb86e364..747870da3014 100644 --- a/packages/astro/src/index.server.ts +++ b/packages/astro/src/index.server.ts @@ -43,6 +43,7 @@ export { fsIntegration, functionToStringIntegration, generateInstrumentOnce, + genericPoolIntegration, getActiveSpan, getAutoPerformanceIntegrations, getClient, diff --git a/packages/aws-serverless/src/index.ts b/packages/aws-serverless/src/index.ts index 27f48c2da0db..f648dba045ec 100644 --- a/packages/aws-serverless/src/index.ts +++ b/packages/aws-serverless/src/index.ts @@ -87,6 +87,7 @@ export { setupConnectErrorHandler, fastifyIntegration, fsIntegration, + genericPoolIntegration, graphqlIntegration, mongoIntegration, mongooseIntegration, diff --git a/packages/bun/src/index.ts b/packages/bun/src/index.ts index d4ca06f85584..1d8b02c33568 100644 --- a/packages/bun/src/index.ts +++ b/packages/bun/src/index.ts @@ -108,6 +108,7 @@ export { setupKoaErrorHandler, connectIntegration, setupConnectErrorHandler, + genericPoolIntegration, graphqlIntegration, mongoIntegration, mongooseIntegration, diff --git a/packages/google-cloud-serverless/src/index.ts b/packages/google-cloud-serverless/src/index.ts index f58305c64f6c..463a0c5c1246 100644 --- a/packages/google-cloud-serverless/src/index.ts +++ b/packages/google-cloud-serverless/src/index.ts @@ -87,6 +87,7 @@ export { connectIntegration, setupConnectErrorHandler, fastifyIntegration, + genericPoolIntegration, graphqlIntegration, mongoIntegration, mongooseIntegration, diff --git a/packages/node/package.json b/packages/node/package.json index f17c37541f63..6a64f646f64c 100644 --- a/packages/node/package.json +++ b/packages/node/package.json @@ -73,6 +73,7 @@ "@opentelemetry/instrumentation-express": "0.41.1", "@opentelemetry/instrumentation-fastify": "0.38.0", "@opentelemetry/instrumentation-fs": "0.14.0", + "@opentelemetry/instrumentation-generic-pool": "0.38.0", "@opentelemetry/instrumentation-graphql": "0.42.0", "@opentelemetry/instrumentation-hapi": "0.40.0", "@opentelemetry/instrumentation-http": "0.52.1", diff --git a/packages/node/src/index.ts b/packages/node/src/index.ts index 5f4adf315fd5..6ce3c325e3ff 100644 --- a/packages/node/src/index.ts +++ b/packages/node/src/index.ts @@ -26,6 +26,7 @@ export { hapiIntegration, setupHapiErrorHandler } from './integrations/tracing/h export { koaIntegration, setupKoaErrorHandler } from './integrations/tracing/koa'; export { connectIntegration, setupConnectErrorHandler } from './integrations/tracing/connect'; export { spotlightIntegration } from './integrations/spotlight'; +export { genericPoolIntegration } from './integrations/tracing/genericPool'; export { SentryContextManager } from './otel/contextManager'; export { generateInstrumentOnce } from './otel/instrument'; diff --git a/packages/node/src/integrations/tracing/genericPool.ts b/packages/node/src/integrations/tracing/genericPool.ts new file mode 100644 index 000000000000..55b8b6095f64 --- /dev/null +++ b/packages/node/src/integrations/tracing/genericPool.ts @@ -0,0 +1,40 @@ +import { GenericPoolInstrumentation } from '@opentelemetry/instrumentation-generic-pool'; +import { SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN, defineIntegration, spanToJSON } from '@sentry/core'; +import type { IntegrationFn } from '@sentry/types'; +import { generateInstrumentOnce } from '../../otel/instrument'; + +const INTEGRATION_NAME = 'GenericPool'; + +export const instrumentGenericPool = generateInstrumentOnce(INTEGRATION_NAME, () => new GenericPoolInstrumentation({})); + +const _genericPoolIntegration = (() => { + return { + name: INTEGRATION_NAME, + setupOnce() { + instrumentGenericPool(); + }, + + setup(client) { + client.on('spanStart', span => { + const spanJSON = spanToJSON(span); + + const spanDescription = spanJSON.description; + + // typo in emitted span for version <= 0.38.0 of @opentelemetry/instrumentation-generic-pool + const isGenericPoolSpan = + spanDescription === 'generic-pool.aquire' || spanDescription === 'generic-pool.acquire'; + + if (isGenericPoolSpan) { + span.setAttribute(SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN, 'auto.db.otel.generic-pool'); + } + }); + }, + }; +}) satisfies IntegrationFn; + +/** + * GenericPool integration + * + * Capture tracing data for GenericPool. + */ +export const genericPoolIntegration = defineIntegration(_genericPoolIntegration); diff --git a/packages/node/src/integrations/tracing/index.ts b/packages/node/src/integrations/tracing/index.ts index 886c11683674..46a9f79e4caa 100644 --- a/packages/node/src/integrations/tracing/index.ts +++ b/packages/node/src/integrations/tracing/index.ts @@ -4,6 +4,7 @@ import { instrumentHttp } from '../http'; import { connectIntegration, instrumentConnect } from './connect'; import { expressIntegration, instrumentExpress } from './express'; import { fastifyIntegration, instrumentFastify } from './fastify'; +import { genericPoolIntegration, instrumentGenericPool } from './genericPool'; import { graphqlIntegration, instrumentGraphql } from './graphql'; import { hapiIntegration, instrumentHapi } from './hapi'; import { instrumentKoa, koaIntegration } from './koa'; @@ -37,6 +38,7 @@ export function getAutoPerformanceIntegrations(): Integration[] { hapiIntegration(), koaIntegration(), connectIntegration(), + genericPoolIntegration(), ]; } @@ -61,5 +63,6 @@ export function getOpenTelemetryInstrumentationToPreload(): (((options?: any) => instrumentHapi, instrumentGraphql, instrumentRedis, + instrumentGenericPool, ]; } diff --git a/packages/remix/src/index.server.ts b/packages/remix/src/index.server.ts index 7ab6efb15827..457dcb9f8685 100644 --- a/packages/remix/src/index.server.ts +++ b/packages/remix/src/index.server.ts @@ -47,6 +47,7 @@ export { flush, functionToStringIntegration, generateInstrumentOnce, + genericPoolIntegration, getActiveSpan, getAutoPerformanceIntegrations, getClient, diff --git a/packages/solidstart/src/server/index.ts b/packages/solidstart/src/server/index.ts index c3499a82459a..995f58d057e3 100644 --- a/packages/solidstart/src/server/index.ts +++ b/packages/solidstart/src/server/index.ts @@ -38,6 +38,7 @@ export { flush, functionToStringIntegration, generateInstrumentOnce, + genericPoolIntegration, getActiveSpan, getAutoPerformanceIntegrations, getClient, diff --git a/packages/sveltekit/src/server/index.ts b/packages/sveltekit/src/server/index.ts index 32dd6627d7a6..d57ec35bd7cc 100644 --- a/packages/sveltekit/src/server/index.ts +++ b/packages/sveltekit/src/server/index.ts @@ -37,6 +37,7 @@ export { fastifyIntegration, flush, functionToStringIntegration, + genericPoolIntegration, generateInstrumentOnce, getActiveSpan, getAutoPerformanceIntegrations, diff --git a/yarn.lock b/yarn.lock index 6f2432d7054a..1acd7e11fbd9 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7113,6 +7113,13 @@ "@opentelemetry/core" "^1.8.0" "@opentelemetry/instrumentation" "^0.52.0" +"@opentelemetry/instrumentation-generic-pool@0.38.0": + version "0.38.0" + resolved "https://registry.yarnpkg.com/@opentelemetry/instrumentation-generic-pool/-/instrumentation-generic-pool-0.38.0.tgz#9ea4d82da23541cda613d553bd405b2cbc0da184" + integrity sha512-0/ULi6pIco1fEnDPmmAul8ZoudFL7St0hjgBbWZlZPBCSyslDll1J7DFeEbjiRSSyUd+0tu73ae0DOKVKNd7VA== + dependencies: + "@opentelemetry/instrumentation" "^0.52.0" + "@opentelemetry/instrumentation-graphql@0.42.0": version "0.42.0" resolved "https://registry.yarnpkg.com/@opentelemetry/instrumentation-graphql/-/instrumentation-graphql-0.42.0.tgz#588a18c39e3b3f655bc09243566172ab0b638d35" @@ -19084,7 +19091,7 @@ generate-function@^2.3.1: dependencies: is-property "^1.0.2" -generic-pool@3.9.0: +generic-pool@3.9.0, generic-pool@^3.9.0: version "3.9.0" resolved "https://registry.yarnpkg.com/generic-pool/-/generic-pool-3.9.0.tgz#36f4a678e963f4fdb8707eab050823abc4e8f5e4" integrity sha512-hymDOu5B53XvN4QT9dBmZxPX4CWhBPPLguTZ9MMFeFa/Kg0xWVfylOVNlJji/E7yTZWFd/q9GO5TxDLq156D7g==