From ae488b5a0e6974e225146d077a2a3cb2b94c5564 Mon Sep 17 00:00:00 2001 From: nicohrubec Date: Wed, 7 Aug 2024 10:15:44 +0200 Subject: [PATCH 1/5] First attempt to make something happen --- .../src/integrations/tracing/nest/helpers.ts | 6 +- .../nest/sentry-nest-instrumentation.ts | 74 ++++++++++++++++++- .../src/integrations/tracing/nest/types.ts | 11 +++ 3 files changed, 86 insertions(+), 5 deletions(-) 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..13ce44b8783b 100644 --- a/packages/node/src/integrations/tracing/nest/sentry-nest-instrumentation.ts +++ b/packages/node/src/integrations/tracing/nest/sentry-nest-instrumentation.ts @@ -9,7 +9,7 @@ import { getActiveSpan, startSpan, startSpanManual, withActiveSpan } from '@sent 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']; @@ -34,7 +34,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 +58,76 @@ 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) { + // console.log('target:', target); + // console.log('prototype: ', target.prototype); + // console.log('token: ', token); + + /* + if (target.prototype === undefined) { + return original(token)(target); + } + + if (target.prototype.set !== undefined && typeof target.prototype.set === 'function' && !target.__SENTRY_INTERNAL__) { + // patch only once + if (isPatched(target)) { + return original(token)(target); + } + + console.log('patch set!') + } + if (target.prototype.set !== undefined && typeof target.prototype.get === 'function' && !target.__SENTRY_INTERNAL__) { + // patch only once + if (isPatched(target)) { + return original(token)(target); + } + + console.log('patch get!') + } + if (target.prototype.set !== undefined && typeof target.prototype.del === 'function' && !target.__SENTRY_INTERNAL__) { + // patch only once + if (isPatched(target)) { + return original(token)(target); + } + + console.log('patch del!') + } + */ + + return original(token)(target); + + } + } + } + } + /** * 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..07a1a024a77f 100644 --- a/packages/node/src/integrations/tracing/nest/types.ts +++ b/packages/node/src/integrations/tracing/nest/types.ts @@ -55,3 +55,14 @@ 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, value: unknown, options?: unknown, ...args: any[]) => Promise | void; + get?: (key: string, ...args: any[]) => Promise | undefined; + del?: (key: string, ...args: any[]) => void | Promise; + } +} From 7753376c5067f7dff5280579cd023b85c5c51c7f Mon Sep 17 00:00:00 2001 From: nicohrubec Date: Fri, 9 Aug 2024 10:59:41 +0200 Subject: [PATCH 2/5] Make barebone work --- .../nest/sentry-nest-instrumentation.ts | 22 +++++++++---------- 1 file changed, 10 insertions(+), 12 deletions(-) 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 13ce44b8783b..43474f29c292 100644 --- a/packages/node/src/integrations/tracing/nest/sentry-nest-instrumentation.ts +++ b/packages/node/src/integrations/tracing/nest/sentry-nest-instrumentation.ts @@ -85,20 +85,20 @@ export class SentryNestInstrumentation extends InstrumentationBase { // eslint-disable-next-line @typescript-eslint/no-explicit-any return function wrapInject(original: any) { return function wrappedInject(token?: unknown) { - return function (target: InjectTarget) { - // console.log('target:', target); - // console.log('prototype: ', target.prototype); - // console.log('token: ', token); + return function (target: InjectTarget, key: string | symbol | undefined, index?: number) { + console.log('target:', target); + console.log('prototype: ', target.prototype); + console.log('token: ', token); + console.log('key: ', key); - /* if (target.prototype === undefined) { - return original(token)(target); + return original(token)(target, key, index); } if (target.prototype.set !== undefined && typeof target.prototype.set === 'function' && !target.__SENTRY_INTERNAL__) { // patch only once if (isPatched(target)) { - return original(token)(target); + return original(token)(target, key, index); } console.log('patch set!') @@ -106,7 +106,7 @@ export class SentryNestInstrumentation extends InstrumentationBase { if (target.prototype.set !== undefined && typeof target.prototype.get === 'function' && !target.__SENTRY_INTERNAL__) { // patch only once if (isPatched(target)) { - return original(token)(target); + return original(token)(target, key, index); } console.log('patch get!') @@ -114,15 +114,13 @@ export class SentryNestInstrumentation extends InstrumentationBase { if (target.prototype.set !== undefined && typeof target.prototype.del === 'function' && !target.__SENTRY_INTERNAL__) { // patch only once if (isPatched(target)) { - return original(token)(target); + return original(token)(target, key, index); } console.log('patch del!') } - */ - - return original(token)(target); + return original(token)(target, key, index); } } } From 100dd448a53968bd45cb2bf373cfb2b6d5b97d18 Mon Sep 17 00:00:00 2001 From: nicohrubec Date: Fri, 9 Aug 2024 11:22:53 +0200 Subject: [PATCH 3/5] Implement basic prototype proxies for get set del --- .../nest/sentry-nest-instrumentation.ts | 45 +++++++++++++++++++ .../src/integrations/tracing/nest/types.ts | 4 +- 2 files changed, 47 insertions(+), 2 deletions(-) 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 43474f29c292..c13b4d94c14c 100644 --- a/packages/node/src/integrations/tracing/nest/sentry-nest-instrumentation.ts +++ b/packages/node/src/integrations/tracing/nest/sentry-nest-instrumentation.ts @@ -101,6 +101,21 @@ export class SentryNestInstrumentation extends InstrumentationBase { return original(token)(target, key, index); } + target.prototype.set = new Proxy(target.prototype.set, { + apply: (originalSet, thisArgSet, argsSet) => { + const [key, value, ...args] = argsSet; + + return startSpan( + { + name: target.name, + op: 'cache.put' + }, () => { + return originalSet.apply(thisArgSet, argsSet); + } + ) + } + }) + console.log('patch set!') } if (target.prototype.set !== undefined && typeof target.prototype.get === 'function' && !target.__SENTRY_INTERNAL__) { @@ -109,6 +124,21 @@ export class SentryNestInstrumentation extends InstrumentationBase { return original(token)(target, key, index); } + target.prototype.get = new Proxy(target.prototype.get, { + apply: (originalGet, thisArgGet, argsGet) => { + const [key, ...args] = argsGet; + + return startSpan( + { + name: target.name, + op: 'cache.get' + }, () => { + return originalGet.apply(thisArgGet, argsGet); + } + ) + } + }) + console.log('patch get!') } if (target.prototype.set !== undefined && typeof target.prototype.del === 'function' && !target.__SENTRY_INTERNAL__) { @@ -117,6 +147,21 @@ export class SentryNestInstrumentation extends InstrumentationBase { return original(token)(target, key, index); } + target.prototype.del = new Proxy(target.prototype.del, { + apply: (originalDel, thisArgDel, argsDel) => { + const [key, ...args] = argsDel; + + return startSpan( + { + name: target.name, + op: 'cache.remove' + }, () => { + return originalDel.apply(thisArgDel, argsDel); + } + ) + } + }) + console.log('patch del!') } diff --git a/packages/node/src/integrations/tracing/nest/types.ts b/packages/node/src/integrations/tracing/nest/types.ts index 07a1a024a77f..9c678d342bfb 100644 --- a/packages/node/src/integrations/tracing/nest/types.ts +++ b/packages/node/src/integrations/tracing/nest/types.ts @@ -60,8 +60,8 @@ export interface InjectTarget { name: string; sentryPatched?: boolean; __SENTRY_INTERNAL__?: boolean; - prototype?: { - set?: (key: string, value: unknown, options?: unknown, ...args: any[]) => Promise | void; + prototype: { + set?: (key: string, value: unknown, ...args: any[]) => Promise | void; get?: (key: string, ...args: any[]) => Promise | undefined; del?: (key: string, ...args: any[]) => void | Promise; } From 7abe2e1cddc68d625975103866c6b07ee1670614 Mon Sep 17 00:00:00 2001 From: nicohrubec Date: Fri, 9 Aug 2024 12:05:14 +0200 Subject: [PATCH 4/5] Send data --- .../nest/sentry-nest-instrumentation.ts | 49 ++++++++++++++----- .../src/integrations/tracing/nest/types.ts | 2 +- 2 files changed, 39 insertions(+), 12 deletions(-) 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 c13b4d94c14c..30ca22e0d939 100644 --- a/packages/node/src/integrations/tracing/nest/sentry-nest-instrumentation.ts +++ b/packages/node/src/integrations/tracing/nest/sentry-nest-instrumentation.ts @@ -5,7 +5,17 @@ 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'; @@ -103,12 +113,16 @@ export class SentryNestInstrumentation extends InstrumentationBase { target.prototype.set = new Proxy(target.prototype.set, { apply: (originalSet, thisArgSet, argsSet) => { - const [key, value, ...args] = argsSet; + const key = argsSet[0]; return startSpan( { name: target.name, - op: 'cache.put' + attributes: { + [SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'cache.put', + [SEMANTIC_ATTRIBUTE_CACHE_KEY]: key, + [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.cache.nestjs', + }, }, () => { return originalSet.apply(thisArgSet, argsSet); } @@ -126,16 +140,25 @@ export class SentryNestInstrumentation extends InstrumentationBase { target.prototype.get = new Proxy(target.prototype.get, { apply: (originalGet, thisArgGet, argsGet) => { - const [key, ...args] = argsGet; + const key = argsGet[0]; - return startSpan( + const span = startInactiveSpan( { name: target.name, - op: 'cache.get' - }, () => { - return originalGet.apply(thisArgGet, argsGet); + attributes: { + [SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'cache.get', + [SEMANTIC_ATTRIBUTE_CACHE_KEY]: key, + [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.cache.nestjs', + }, } - ) + ); + + const returnedValue: Promise | undefined = originalGet.apply(thisArgGet, argsGet); + + span.setAttribute(SEMANTIC_ATTRIBUTE_CACHE_HIT, returnedValue !== undefined); + span.end(); + + return returnedValue; } }) @@ -149,12 +172,16 @@ export class SentryNestInstrumentation extends InstrumentationBase { target.prototype.del = new Proxy(target.prototype.del, { apply: (originalDel, thisArgDel, argsDel) => { - const [key, ...args] = argsDel; + const key = argsDel[0]; return startSpan( { name: target.name, - op: 'cache.remove' + attributes: { + [SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'cache.remove', + [SEMANTIC_ATTRIBUTE_CACHE_KEY]: key, + [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.cache.nestjs', + }, }, () => { return originalDel.apply(thisArgDel, argsDel); } diff --git a/packages/node/src/integrations/tracing/nest/types.ts b/packages/node/src/integrations/tracing/nest/types.ts index 9c678d342bfb..37d9b5751a0b 100644 --- a/packages/node/src/integrations/tracing/nest/types.ts +++ b/packages/node/src/integrations/tracing/nest/types.ts @@ -61,7 +61,7 @@ export interface InjectTarget { sentryPatched?: boolean; __SENTRY_INTERNAL__?: boolean; prototype: { - set?: (key: string, value: unknown, ...args: any[]) => Promise | void; + set?: (key: string, ...args: any[]) => Promise | void; get?: (key: string, ...args: any[]) => Promise | undefined; del?: (key: string, ...args: any[]) => void | Promise; } From 6c2bd26284648aa55dbc609b90a32e036d2f28c4 Mon Sep 17 00:00:00 2001 From: nicohrubec Date: Fri, 9 Aug 2024 13:55:49 +0200 Subject: [PATCH 5/5] Reduce to what is working --- .../nest/sentry-nest-instrumentation.ts | 102 +++--------------- .../src/integrations/tracing/nest/types.ts | 1 + 2 files changed, 15 insertions(+), 88 deletions(-) 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 30ca22e0d939..99bfab9c5776 100644 --- a/packages/node/src/integrations/tracing/nest/sentry-nest-instrumentation.ts +++ b/packages/node/src/integrations/tracing/nest/sentry-nest-instrumentation.ts @@ -23,6 +23,8 @@ import type { InjectableTarget, InjectTarget } from './types'; const supportedVersions = ['>=8.0.0 <11']; +const CACHE_MANAGER = 'CACHE_MANAGER'; + /** * Custom instrumentation for nestjs. * @@ -96,101 +98,25 @@ export class SentryNestInstrumentation extends InstrumentationBase { return function wrapInject(original: any) { return function wrappedInject(token?: unknown) { return function (target: InjectTarget, key: string | symbol | undefined, index?: number) { - console.log('target:', target); - console.log('prototype: ', target.prototype); - console.log('token: ', token); - console.log('key: ', key); - - if (target.prototype === undefined) { + if (token !== CACHE_MANAGER || target.prototype === undefined) { return original(token)(target, key, index); } - if (target.prototype.set !== undefined && typeof target.prototype.set === 'function' && !target.__SENTRY_INTERNAL__) { - // patch only once - if (isPatched(target)) { - return original(token)(target, key, index); - } - - target.prototype.set = new Proxy(target.prototype.set, { - apply: (originalSet, thisArgSet, argsSet) => { - const key = argsSet[0]; - - return startSpan( - { - name: target.name, - attributes: { - [SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'cache.put', - [SEMANTIC_ATTRIBUTE_CACHE_KEY]: key, - [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.cache.nestjs', - }, - }, () => { - return originalSet.apply(thisArgSet, argsSet); - } - ) - } - }) - - console.log('patch set!') - } - if (target.prototype.set !== undefined && typeof target.prototype.get === 'function' && !target.__SENTRY_INTERNAL__) { - // patch only once - if (isPatched(target)) { - return original(token)(target, key, index); - } - - target.prototype.get = new Proxy(target.prototype.get, { - apply: (originalGet, thisArgGet, argsGet) => { - const key = argsGet[0]; - - const span = startInactiveSpan( - { - name: target.name, - attributes: { - [SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'cache.get', - [SEMANTIC_ATTRIBUTE_CACHE_KEY]: key, - [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.cache.nestjs', - }, - } - ); - - const returnedValue: Promise | undefined = originalGet.apply(thisArgGet, argsGet); - - span.setAttribute(SEMANTIC_ATTRIBUTE_CACHE_HIT, returnedValue !== undefined); - span.end(); - - return returnedValue; - } - }) - - console.log('patch get!') + if (index === undefined) { + return original(token)(target, key, index); } - if (target.prototype.set !== undefined && typeof target.prototype.del === 'function' && !target.__SENTRY_INTERNAL__) { - // patch only once - if (isPatched(target)) { - return original(token)(target, key, index); - } - target.prototype.del = new Proxy(target.prototype.del, { - apply: (originalDel, thisArgDel, argsDel) => { - const key = argsDel[0]; + // target is the service that injects the cache manager + // TODO: retrieve cache manager so we can patch it - return startSpan( - { - name: target.name, - attributes: { - [SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'cache.remove', - [SEMANTIC_ATTRIBUTE_CACHE_KEY]: key, - [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.cache.nestjs', - }, - }, () => { - return originalDel.apply(thisArgDel, argsDel); - } - ) - } - }) + console.log('bla: ', target.prototype.cacheManager); - console.log('patch del!') - } + 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); } diff --git a/packages/node/src/integrations/tracing/nest/types.ts b/packages/node/src/integrations/tracing/nest/types.ts index 37d9b5751a0b..ab8d9f1817a2 100644 --- a/packages/node/src/integrations/tracing/nest/types.ts +++ b/packages/node/src/integrations/tracing/nest/types.ts @@ -64,5 +64,6 @@ export interface InjectTarget { set?: (key: string, ...args: any[]) => Promise | void; get?: (key: string, ...args: any[]) => Promise | undefined; del?: (key: string, ...args: any[]) => void | Promise; + cacheManager?: any; } }