diff --git a/dev-packages/node-integration-tests/package.json b/dev-packages/node-integration-tests/package.json index 91792650a13f..ff6b5665d237 100644 --- a/dev-packages/node-integration-tests/package.json +++ b/dev-packages/node-integration-tests/package.json @@ -51,6 +51,7 @@ "http-terminator": "^3.2.0", "ioredis": "^5.4.1", "kafkajs": "2.2.4", + "lru-memoizer": "2.3.0", "mongodb": "^3.7.3", "mongodb-memory-server-global": "^7.6.3", "mongoose": "^5.13.22", diff --git a/dev-packages/node-integration-tests/suites/tracing/lru-memoizer/scenario.js b/dev-packages/node-integration-tests/suites/tracing/lru-memoizer/scenario.js new file mode 100644 index 000000000000..79f5564d1971 --- /dev/null +++ b/dev-packages/node-integration-tests/suites/tracing/lru-memoizer/scenario.js @@ -0,0 +1,47 @@ +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 run = async () => { + // Test ported from the OTEL implementation: + // https://github.com/open-telemetry/opentelemetry-js-contrib/blob/0d6ebded313bb75b5a0e7a6422206c922daf3943/plugins/node/instrumentation-lru-memoizer/test/index.test.ts#L28 + const memoizer = require('lru-memoizer'); + + let memoizerLoadCallback; + const memoizedFoo = memoizer({ + load: (_param, callback) => { + memoizerLoadCallback = callback; + }, + hash: () => 'bar', + }); + + Sentry.startSpan({ op: 'run' }, async span => { + const outerSpanContext = span.spanContext(); + + memoizedFoo({ foo: 'bar' }, () => { + const innerContext = Sentry.getActiveSpan().spanContext(); + + // The span context should be the same as the outer span + // Throwing an error here will cause the test to fail + if (outerSpanContext !== innerContext) { + throw new Error('Outer and inner span context should match'); + } + }); + + span.end(); + }); + + // Invoking the load callback outside the span + memoizerLoadCallback(); +}; + +run(); diff --git a/dev-packages/node-integration-tests/suites/tracing/lru-memoizer/test.ts b/dev-packages/node-integration-tests/suites/tracing/lru-memoizer/test.ts new file mode 100644 index 000000000000..050505e4055e --- /dev/null +++ b/dev-packages/node-integration-tests/suites/tracing/lru-memoizer/test.ts @@ -0,0 +1,29 @@ +import { cleanupChildProcesses, createRunner } from '../../../utils/runner'; + +describe('lru-memoizer', () => { + afterAll(() => { + cleanupChildProcesses(); + }); + + test('keeps outer context inside the memoized inner functions', done => { + createRunner(__dirname, 'scenario.js') + // We expect only one transaction and nothing else. + // A failed test will result in an error event being sent to Sentry. + // Which will fail this suite. + .expect({ + transaction: { + transaction: '', + contexts: { + trace: expect.objectContaining({ + op: 'run', + data: expect.objectContaining({ + 'sentry.op': 'run', + 'sentry.origin': 'manual', + }), + }), + }, + }, + }) + .start(done); + }); +}); diff --git a/packages/astro/src/index.server.ts b/packages/astro/src/index.server.ts index bfd6886f3861..91ea79f833bc 100644 --- a/packages/astro/src/index.server.ts +++ b/packages/astro/src/index.server.ts @@ -72,6 +72,7 @@ export { lastEventId, linkedErrorsIntegration, localVariablesIntegration, + lruMemoizerIntegration, makeNodeTransport, metrics, modulesIntegration, diff --git a/packages/aws-serverless/src/index.ts b/packages/aws-serverless/src/index.ts index 9bbba6eeda66..7b0e05c9a48f 100644 --- a/packages/aws-serverless/src/index.ts +++ b/packages/aws-serverless/src/index.ts @@ -91,6 +91,7 @@ export { genericPoolIntegration, graphqlIntegration, kafkaIntegration, + lruMemoizerIntegration, mongoIntegration, mongooseIntegration, mysqlIntegration, diff --git a/packages/bun/src/index.ts b/packages/bun/src/index.ts index ef3bcb020823..7026e6800b14 100644 --- a/packages/bun/src/index.ts +++ b/packages/bun/src/index.ts @@ -112,6 +112,7 @@ export { genericPoolIntegration, graphqlIntegration, kafkaIntegration, + lruMemoizerIntegration, mongoIntegration, mongooseIntegration, mysqlIntegration, diff --git a/packages/google-cloud-serverless/src/index.ts b/packages/google-cloud-serverless/src/index.ts index a9d3e5025d92..c2d743eb1bf1 100644 --- a/packages/google-cloud-serverless/src/index.ts +++ b/packages/google-cloud-serverless/src/index.ts @@ -91,6 +91,7 @@ export { genericPoolIntegration, graphqlIntegration, kafkaIntegration, + lruMemoizerIntegration, mongoIntegration, mongooseIntegration, mysqlIntegration, diff --git a/packages/node/package.json b/packages/node/package.json index 1ace8a11f408..7f3b18b4590a 100644 --- a/packages/node/package.json +++ b/packages/node/package.json @@ -82,6 +82,7 @@ "@opentelemetry/instrumentation-ioredis": "0.43.0", "@opentelemetry/instrumentation-kafkajs": "0.3.0", "@opentelemetry/instrumentation-koa": "0.43.0", + "@opentelemetry/instrumentation-lru-memoizer": "0.40.0", "@opentelemetry/instrumentation-mongodb": "0.47.0", "@opentelemetry/instrumentation-mongoose": "0.42.0", "@opentelemetry/instrumentation-mysql": "0.41.0", diff --git a/packages/node/src/index.ts b/packages/node/src/index.ts index e97780f79ead..bc63094e2e87 100644 --- a/packages/node/src/index.ts +++ b/packages/node/src/index.ts @@ -15,6 +15,7 @@ export { expressIntegration, expressErrorHandler, setupExpressErrorHandler } fro export { fastifyIntegration, setupFastifyErrorHandler } from './integrations/tracing/fastify'; export { graphqlIntegration } from './integrations/tracing/graphql'; export { kafkaIntegration } from './integrations/tracing/kafka'; +export { lruMemoizerIntegration } from './integrations/tracing/lrumemoizer'; export { mongoIntegration } from './integrations/tracing/mongo'; export { mongooseIntegration } from './integrations/tracing/mongoose'; export { mysqlIntegration } from './integrations/tracing/mysql'; diff --git a/packages/node/src/integrations/tracing/index.ts b/packages/node/src/integrations/tracing/index.ts index cc8ef752c815..3c038b14354c 100644 --- a/packages/node/src/integrations/tracing/index.ts +++ b/packages/node/src/integrations/tracing/index.ts @@ -11,6 +11,7 @@ import { graphqlIntegration, instrumentGraphql } from './graphql'; import { hapiIntegration, instrumentHapi } from './hapi'; import { instrumentKafka, kafkaIntegration } from './kafka'; import { instrumentKoa, koaIntegration } from './koa'; +import { instrumentLruMemoizer, lruMemoizerIntegration } from './lrumemoizer'; import { instrumentMongo, mongoIntegration } from './mongo'; import { instrumentMongoose, mongooseIntegration } from './mongoose'; import { instrumentMysql, mysqlIntegration } from './mysql'; @@ -45,6 +46,7 @@ export function getAutoPerformanceIntegrations(): Integration[] { kafkaIntegration(), dataloaderIntegration(), amqplibIntegration(), + lruMemoizerIntegration(), ]; } @@ -61,6 +63,7 @@ export function getOpenTelemetryInstrumentationToPreload(): (((options?: any) => instrumentHapi, instrumentKafka, instrumentKoa, + instrumentLruMemoizer, instrumentNest, instrumentMongo, instrumentMongoose, diff --git a/packages/node/src/integrations/tracing/lrumemoizer.ts b/packages/node/src/integrations/tracing/lrumemoizer.ts new file mode 100644 index 000000000000..d94234c3e57d --- /dev/null +++ b/packages/node/src/integrations/tracing/lrumemoizer.ts @@ -0,0 +1,25 @@ +import { LruMemoizerInstrumentation } from '@opentelemetry/instrumentation-lru-memoizer'; + +import { defineIntegration } from '@sentry/core'; +import type { IntegrationFn } from '@sentry/types'; +import { generateInstrumentOnce } from '../../otel/instrument'; + +const INTEGRATION_NAME = 'LruMemoizer'; + +export const instrumentLruMemoizer = generateInstrumentOnce(INTEGRATION_NAME, () => new LruMemoizerInstrumentation()); + +const _lruMemoizerIntegration = (() => { + return { + name: INTEGRATION_NAME, + setupOnce() { + instrumentLruMemoizer(); + }, + }; +}) satisfies IntegrationFn; + +/** + * LruMemoizer integration + * + * Propagate traces through LruMemoizer. + */ +export const lruMemoizerIntegration = defineIntegration(_lruMemoizerIntegration); diff --git a/yarn.lock b/yarn.lock index a32337e3835f..e19f0aac72e6 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7211,6 +7211,13 @@ "@opentelemetry/instrumentation" "^0.53.0" "@opentelemetry/semantic-conventions" "^1.27.0" +"@opentelemetry/instrumentation-lru-memoizer@0.40.0": + version "0.40.0" + resolved "https://registry.yarnpkg.com/@opentelemetry/instrumentation-lru-memoizer/-/instrumentation-lru-memoizer-0.40.0.tgz#dc60d7fdfd2a0c681cb23e7ed4f314d1506ccdc0" + integrity sha512-21xRwZsEdMPnROu/QsaOIODmzw59IYpGFmuC4aFWvMj6stA8+Ei1tX67nkarJttlNjoM94um0N4X26AD7ff54A== + dependencies: + "@opentelemetry/instrumentation" "^0.53.0" + "@opentelemetry/instrumentation-mongodb@0.47.0": version "0.47.0" resolved "https://registry.yarnpkg.com/@opentelemetry/instrumentation-mongodb/-/instrumentation-mongodb-0.47.0.tgz#f8107d878281433905e717f223fb4c0f10356a7b" @@ -23303,6 +23310,13 @@ lowercase-keys@^2.0.0: resolved "https://registry.yarnpkg.com/lowercase-keys/-/lowercase-keys-2.0.0.tgz#2603e78b7b4b0006cbca2fbcc8a3202558ac9479" integrity sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA== +lru-cache@6.0.0, lru-cache@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-6.0.0.tgz#6d6fe6570ebd96aaf90fcad1dafa3b2566db3a94" + integrity sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA== + dependencies: + yallist "^4.0.0" + lru-cache@^10.0.1, lru-cache@^10.2.0: version "10.2.2" resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-10.2.2.tgz#48206bc114c1252940c41b25b41af5b545aca878" @@ -23315,13 +23329,6 @@ lru-cache@^5.1.1: dependencies: yallist "^3.0.2" -lru-cache@^6.0.0: - version "6.0.0" - resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-6.0.0.tgz#6d6fe6570ebd96aaf90fcad1dafa3b2566db3a94" - integrity sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA== - dependencies: - yallist "^4.0.0" - lru-cache@^7.10.1, lru-cache@^7.4.4, lru-cache@^7.5.1, lru-cache@^7.7.1: version "7.14.1" resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-7.14.1.tgz#8da8d2f5f59827edb388e63e459ac23d6d408fea" @@ -23347,6 +23354,14 @@ lru-cache@^9.0.0: resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-10.0.0.tgz#b9e2a6a72a129d81ab317202d93c7691df727e61" integrity sha512-svTf/fzsKHffP42sujkO/Rjs37BCIsQVRCeNYIm9WN8rgT7ffoUnRtZCqU+6BqcSBdv8gwJeTz8knJpgACeQMw== +lru-memoizer@2.3.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/lru-memoizer/-/lru-memoizer-2.3.0.tgz#ef0fbc021bceb666794b145eefac6be49dc47f31" + integrity sha512-GXn7gyHAMhO13WSKrIiNfztwxodVsP8IoZ3XfrJV4yH2x0/OeTO/FIaAHTY5YekdGgW94njfuKmyyt1E0mR6Ug== + dependencies: + lodash.clonedeep "^4.5.0" + lru-cache "6.0.0" + lunr@^2.3.8: version "2.3.9" resolved "https://registry.yarnpkg.com/lunr/-/lunr-2.3.9.tgz#18b123142832337dd6e964df1a5a7707b25d35e1"