diff --git a/packages/node/src/integrations/tracing/nest/helpers.ts b/packages/node/src/integrations/tracing/nest/helpers.ts index 32eb3a0d5a39..d0d321a0f8aa 100644 --- a/packages/node/src/integrations/tracing/nest/helpers.ts +++ b/packages/node/src/integrations/tracing/nest/helpers.ts @@ -1,6 +1,6 @@ import { SEMANTIC_ATTRIBUTE_SENTRY_OP, SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN } from '@sentry/core'; import { addNonEnumerableProperty } from '@sentry/utils'; -import type { InjectableTarget } from './types'; +import type {InjectableTarget, InjectTarget} from './types'; const sentryPatched = 'sentryPatched'; @@ -10,7 +10,7 @@ const sentryPatched = 'sentryPatched'; * We already guard duplicate patching with isWrapped. However, isWrapped checks whether a file has been patched, whereas we use this check for concrete target classes. * This check might not be necessary, but better to play it safe. */ -export function isPatched(target: InjectableTarget): boolean { +export function isPatched(target: InjectableTarget | InjectTarget): boolean { if (target.sentryPatched) { return true; } @@ -23,7 +23,7 @@ export function isPatched(target: InjectableTarget): boolean { * Returns span options for nest middleware spans. */ // eslint-disable-next-line @typescript-eslint/explicit-function-return-type -export function getMiddlewareSpanOptions(target: InjectableTarget) { +export function getMiddlewareSpanOptions(target: InjectableTarget | InjectTarget) { return { name: target.name, attributes: { diff --git a/packages/node/src/integrations/tracing/nest/sentry-nest-instrumentation.ts b/packages/node/src/integrations/tracing/nest/sentry-nest-instrumentation.ts index 52c3a4ad6b40..99bfab9c5776 100644 --- a/packages/node/src/integrations/tracing/nest/sentry-nest-instrumentation.ts +++ b/packages/node/src/integrations/tracing/nest/sentry-nest-instrumentation.ts @@ -5,14 +5,26 @@ import { InstrumentationNodeModuleDefinition, InstrumentationNodeModuleFile, } from '@opentelemetry/instrumentation'; -import { getActiveSpan, startSpan, startSpanManual, withActiveSpan } from '@sentry/core'; +import { + getActiveSpan, + startSpan, + startSpanManual, + withActiveSpan, + startInactiveSpan, + SEMANTIC_ATTRIBUTE_SENTRY_OP, + SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN, + SEMANTIC_ATTRIBUTE_CACHE_KEY, + SEMANTIC_ATTRIBUTE_CACHE_HIT +} from '@sentry/core'; import type { Span } from '@sentry/types'; import { SDK_VERSION } from '@sentry/utils'; import { getMiddlewareSpanOptions, isPatched } from './helpers'; -import type { InjectableTarget } from './types'; +import type { InjectableTarget, InjectTarget } from './types'; const supportedVersions = ['>=8.0.0 <11']; +const CACHE_MANAGER = 'CACHE_MANAGER'; + /** * Custom instrumentation for nestjs. * @@ -34,7 +46,7 @@ export class SentryNestInstrumentation extends InstrumentationBase { public init(): InstrumentationNodeModuleDefinition { const moduleDef = new InstrumentationNodeModuleDefinition(SentryNestInstrumentation.COMPONENT, supportedVersions); - moduleDef.files.push(this._getInjectableFileInstrumentation(supportedVersions)); + moduleDef.files.push(this._getInjectableFileInstrumentation(supportedVersions), this._getInjectFileInstrumentation(supportedVersions)); return moduleDef; } @@ -58,6 +70,60 @@ export class SentryNestInstrumentation extends InstrumentationBase { ); } + /** + * + */ + private _getInjectFileInstrumentation(versions: string[]): InstrumentationNodeModuleFile { + return new InstrumentationNodeModuleFile( + '@nestjs/common/decorators/core/inject.decorator.js', + versions, + (moduleExports: { Inject: InjectTarget }) => { + if (isWrapped(moduleExports.Inject)) { + this._unwrap(moduleExports, 'Inject'); + } + this._wrap(moduleExports, 'Inject', this._createWrapInject()); + return moduleExports; + }, + (moduleExports: { Inject: InjectTarget }) => { + this._unwrap(moduleExports, 'Inject'); + }, + ) + } + + /** + * + */ + private _createWrapInject() { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + return function wrapInject(original: any) { + return function wrappedInject(token?: unknown) { + return function (target: InjectTarget, key: string | symbol | undefined, index?: number) { + if (token !== CACHE_MANAGER || target.prototype === undefined) { + return original(token)(target, key, index); + } + + if (index === undefined) { + return original(token)(target, key, index); + } + + // target is the service that injects the cache manager + // TODO: retrieve cache manager so we can patch it + + console.log('bla: ', target.prototype.cacheManager); + + console.log('target: ', target); + console.log('target prototype', target.prototype); + console.log('property name: ', Object.getOwnPropertyNames(target.prototype)); + console.log('token: ', token); + console.log('key: ', key); + console.log('index: ', index); + + return original(token)(target, key, index); + } + } + } + } + /** * Creates a wrapper function for the @Injectable decorator. * diff --git a/packages/node/src/integrations/tracing/nest/types.ts b/packages/node/src/integrations/tracing/nest/types.ts index 2cdd1b6aefaf..ab8d9f1817a2 100644 --- a/packages/node/src/integrations/tracing/nest/types.ts +++ b/packages/node/src/integrations/tracing/nest/types.ts @@ -55,3 +55,15 @@ export interface InjectableTarget { intercept?: (context: unknown, next: CallHandler, ...args: any[]) => Observable; }; } + +export interface InjectTarget { + name: string; + sentryPatched?: boolean; + __SENTRY_INTERNAL__?: boolean; + prototype: { + set?: (key: string, ...args: any[]) => Promise | void; + get?: (key: string, ...args: any[]) => Promise | undefined; + del?: (key: string, ...args: any[]) => void | Promise; + cacheManager?: any; + } +}