From 0596208cefa21fc5958c6461a196bf8b65a22e04 Mon Sep 17 00:00:00 2001 From: Charly Gomez Date: Thu, 7 Nov 2024 14:42:04 +0100 Subject: [PATCH 1/8] try instrumenting OnEvent --- packages/nestjs/package.json | 5 +- packages/nestjs/src/sdk.ts | 1 + packages/nestjs/src/setup.ts | 2 + .../src/integrations/tracing/nest/nest.ts | 10 +++ .../nest/sentry-nest-event-instrumentation.ts | 82 +++++++++++++++++++ .../nest/sentry-nest-instrumentation.ts | 2 + .../src/integrations/tracing/nest/types.ts | 9 ++ yarn.lock | 14 +++- 8 files changed, 122 insertions(+), 3 deletions(-) create mode 100644 packages/node/src/integrations/tracing/nest/sentry-nest-event-instrumentation.ts diff --git a/packages/nestjs/package.json b/packages/nestjs/package.json index 9b7360a45706..2ba8e6194053 100644 --- a/packages/nestjs/package.json +++ b/packages/nestjs/package.json @@ -50,8 +50,9 @@ "@sentry/utils": "8.38.0" }, "devDependencies": { - "@nestjs/common": "10.4.7", - "@nestjs/core": "10.4.7" + "@nestjs/common": "^8.0.0 || ^9.0.0 || ^10.0.0", + "@nestjs/core": "^8.0.0 || ^9.0.0 || ^10.0.0", + "@nestjs/event-emitter": "^2.0.0" }, "peerDependencies": { "@nestjs/common": "^8.0.0 || ^9.0.0 || ^10.0.0", diff --git a/packages/nestjs/src/sdk.ts b/packages/nestjs/src/sdk.ts index 8d5ca21b1706..7815f4372d71 100644 --- a/packages/nestjs/src/sdk.ts +++ b/packages/nestjs/src/sdk.ts @@ -6,6 +6,7 @@ import { init as nodeInit } from '@sentry/node'; * Initializes the NestJS SDK */ export function init(options: NodeOptions | undefined = {}): NodeClient | undefined { + console.log(9, 'init v4'); const opts: NodeOptions = { ...options, }; diff --git a/packages/nestjs/src/setup.ts b/packages/nestjs/src/setup.ts index a18f95417f11..11585b28ad4b 100644 --- a/packages/nestjs/src/setup.ts +++ b/packages/nestjs/src/setup.ts @@ -193,6 +193,7 @@ class SentryModule { module: SentryModule, providers: [ SentryService, + { provide: APP_INTERCEPTOR, useClass: SentryTracingInterceptor, @@ -206,6 +207,7 @@ Global()(SentryModule); Module({ providers: [ SentryService, + { provide: APP_INTERCEPTOR, useClass: SentryTracingInterceptor, diff --git a/packages/node/src/integrations/tracing/nest/nest.ts b/packages/node/src/integrations/tracing/nest/nest.ts index 4f8d88fa8f86..68eb871019d7 100644 --- a/packages/node/src/integrations/tracing/nest/nest.ts +++ b/packages/node/src/integrations/tracing/nest/nest.ts @@ -14,6 +14,7 @@ import { logger } from '@sentry/utils'; import { generateInstrumentOnce } from '../../../otel/instrument'; import { SentryNestInstrumentation } from './sentry-nest-instrumentation'; import type { MinimalNestJsApp, NestJsErrorFilter } from './types'; +import { SentryNestEventInstrumentation } from './sentry-nest-event-instrumentation'; const INTEGRATION_NAME = 'Nest'; @@ -25,10 +26,17 @@ const instrumentNestCommon = generateInstrumentOnce('Nest-Common', () => { return new SentryNestInstrumentation(); }); +const instrumentNestEvent = generateInstrumentOnce('Nest-Event', () => { + return new SentryNestEventInstrumentation(); +}); + export const instrumentNest = Object.assign( (): void => { + // eslint-disable-next-line no-console + console.log('HIT instrumentNest'); instrumentNestCore(); instrumentNestCommon(); + instrumentNestEvent(); }, { id: INTEGRATION_NAME }, ); @@ -37,6 +45,8 @@ const _nestIntegration = (() => { return { name: INTEGRATION_NAME, setupOnce() { + // eslint-disable-next-line no-console + console.log('HIT nest setup'); instrumentNest(); }, }; diff --git a/packages/node/src/integrations/tracing/nest/sentry-nest-event-instrumentation.ts b/packages/node/src/integrations/tracing/nest/sentry-nest-event-instrumentation.ts new file mode 100644 index 000000000000..f3279e6b2231 --- /dev/null +++ b/packages/node/src/integrations/tracing/nest/sentry-nest-event-instrumentation.ts @@ -0,0 +1,82 @@ +import { isWrapped } from '@opentelemetry/core'; +import type { InstrumentationConfig } from '@opentelemetry/instrumentation'; +import { + InstrumentationBase, + InstrumentationNodeModuleDefinition, + InstrumentationNodeModuleFile, +} from '@opentelemetry/instrumentation'; +import { SDK_VERSION } from '@sentry/utils'; +import type { OnEventTarget } from './types'; + +const supportedVersions = ['>=2.0.0']; + +/** + * Custom instrumentation for nestjs event-emitter + * + * This hooks into the `OnEvent` decorator, which is applied on event handlers. + */ +export class SentryNestEventInstrumentation extends InstrumentationBase { + public static readonly COMPONENT = '@nestjs/event-emitter'; + public static readonly COMMON_ATTRIBUTES = { + component: SentryNestEventInstrumentation.COMPONENT, + }; + + public constructor(config: InstrumentationConfig = {}) { + super('sentry-nestjs-event', SDK_VERSION, config); + } + + /** + * Initializes the instrumentation by defining the modules to be patched. + */ + public init(): InstrumentationNodeModuleDefinition { + // eslint-disable-next-line no-console + console.log(32); + const moduleDef = new InstrumentationNodeModuleDefinition( + SentryNestEventInstrumentation.COMPONENT, + supportedVersions, + ); + + moduleDef.files.push(this._getOnEventFileInstrumentation(supportedVersions)); + return moduleDef; + } + + /** + * Wraps the @OnEvent decorator. + */ + private _getOnEventFileInstrumentation(versions: string[]): InstrumentationNodeModuleFile { + // eslint-disable-next-line no-console + console.log(46); + return new InstrumentationNodeModuleFile( + '@nestjs/event-emitter/dist/decorators/on-event.decorator.js', + versions, + (moduleExports: { OnEvent: OnEventTarget }) => { + if (isWrapped(moduleExports.OnEvent)) { + this._unwrap(moduleExports, 'OnEvent'); + } + this._wrap(moduleExports, 'OnEvent', this._createWrapOnEvent()); + return moduleExports; + }, + (moduleExports: { OnEvent: OnEventTarget }) => { + this._unwrap(moduleExports, 'OnEvent'); + }, + ); + } + + /** + * Creates a wrapper function for the @OnEvent decorator. + */ + private _createWrapOnEvent() { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + return function wrapOnEvent(original: any) { + return function wrappedOnEvent(target: OnEventTarget) { + return function (name: string) { + return function () { + // eslint-disable-next-line no-console + console.log('wrappedOnEvent', name); + return original(name)(target); + }; + }; + }; + }; + } +} 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 2d59d97d87fd..3b140b17ce7c 100644 --- a/packages/node/src/integrations/tracing/nest/sentry-nest-instrumentation.ts +++ b/packages/node/src/integrations/tracing/nest/sentry-nest-instrumentation.ts @@ -34,6 +34,8 @@ export class SentryNestInstrumentation extends InstrumentationBase { * Initializes the instrumentation by defining the modules to be patched. */ public init(): InstrumentationNodeModuleDefinition { + // eslint-disable-next-line no-console + console.log('HIT init common'); const moduleDef = new InstrumentationNodeModuleDefinition(SentryNestInstrumentation.COMPONENT, supportedVersions); moduleDef.files.push( diff --git a/packages/node/src/integrations/tracing/nest/types.ts b/packages/node/src/integrations/tracing/nest/types.ts index 0590462c09d5..ed7e968a9600 100644 --- a/packages/node/src/integrations/tracing/nest/types.ts +++ b/packages/node/src/integrations/tracing/nest/types.ts @@ -74,6 +74,15 @@ export interface CatchTarget { }; } +/** + * Represents a target method in NestJS annotated with @OnEvent. + */ +export interface OnEventTarget { + name: string; + sentryPatched?: boolean; + __SENTRY_INTERNAL__?: boolean; +} + /** * Represents an express NextFunction. */ diff --git a/yarn.lock b/yarn.lock index 01efab263fca..6b6ba1e04520 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6665,7 +6665,14 @@ path-to-regexp "3.3.0" tslib "2.7.0" -"@nestjs/platform-express@10.4.6": +"@nestjs/event-emitter@^2.0.0": + version "2.1.1" + resolved "https://registry.yarnpkg.com/@nestjs/event-emitter/-/event-emitter-2.1.1.tgz#4e34edc487c507edbe6d02033e3dd014a19210f9" + integrity sha512-6L6fBOZTyfFlL7Ih/JDdqlCzZeCW0RjCX28wnzGyg/ncv5F/EOeT1dfopQr1loBRQ3LTgu8OWM7n4zLN4xigsg== + dependencies: + eventemitter2 "6.4.9" + +"@nestjs/platform-express@^10.4.6": version "10.4.6" resolved "https://registry.yarnpkg.com/@nestjs/platform-express/-/platform-express-10.4.6.tgz#6c39c522fa66036b4256714fea203fbeb49fc4de" integrity sha512-HcyCpAKccAasrLSGRTGWv5BKRs0rwTIFOSsk6laNyqfqvgvYcJQAedarnm4jmaemtmSJ0PFI9PmtEZADd2ahCg== @@ -18007,6 +18014,11 @@ eventemitter-asyncresource@^1.0.0: resolved "https://registry.yarnpkg.com/eventemitter-asyncresource/-/eventemitter-asyncresource-1.0.0.tgz#734ff2e44bf448e627f7748f905d6bdd57bdb65b" integrity sha512-39F7TBIV0G7gTelxwbEqnwhp90eqCPON1k0NwNfwhgKn4Co4ybUbj2pECcXT0B3ztRKZ7Pw1JujUUgmQJHcVAQ== +eventemitter2@6.4.9: + version "6.4.9" + resolved "https://registry.yarnpkg.com/eventemitter2/-/eventemitter2-6.4.9.tgz#41f2750781b4230ed58827bc119d293471ecb125" + integrity sha512-JEPTiaOt9f04oa6NOkc4aH+nVp5I3wEjpHbIPqfgCdD5v5bUzy7xQqwcVO2aDQgOWhI28da57HksMrzK9HlRxg== + eventemitter3@^4.0.0, eventemitter3@^4.0.4: version "4.0.7" resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-4.0.7.tgz#2de9b68f6528d5644ef5c59526a1b4a07306169f" From b2704e7e353e0bc738855e5049053f5bd498800f Mon Sep 17 00:00:00 2001 From: Charly Gomez Date: Thu, 14 Nov 2024 15:41:39 +0100 Subject: [PATCH 2/8] instrument event handler --- .../src/integrations/tracing/nest/helpers.ts | 18 ++++ .../src/integrations/tracing/nest/nest.ts | 4 - .../nest/sentry-nest-event-instrumentation.ts | 57 +++++++++-- .../nest/sentry-nest-instrumentation.ts | 2 - .../test/integrations/tracing/nest.test.ts | 98 +++++++++++++++++++ yarn.lock | 33 +------ 6 files changed, 167 insertions(+), 45 deletions(-) diff --git a/packages/node/src/integrations/tracing/nest/helpers.ts b/packages/node/src/integrations/tracing/nest/helpers.ts index cc83dda3855d..04dab67f65b0 100644 --- a/packages/node/src/integrations/tracing/nest/helpers.ts +++ b/packages/node/src/integrations/tracing/nest/helpers.ts @@ -36,6 +36,24 @@ export function getMiddlewareSpanOptions(target: InjectableTarget | CatchTarget, }; } +/** + * Returns span options for nest event spans. + */ +export function getEventSpanOptions(event: string): { + name: string; + attributes: Record; + forceTransaction: boolean; +} { + return { + name: `event ${event}`, + attributes: { + [SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'event.nestjs', + [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.event.nestjs', + }, + forceTransaction: true, + }; +} + /** * Adds instrumentation to a js observable and attaches the span to an active parent span. */ diff --git a/packages/node/src/integrations/tracing/nest/nest.ts b/packages/node/src/integrations/tracing/nest/nest.ts index 68eb871019d7..91ab9e9068e2 100644 --- a/packages/node/src/integrations/tracing/nest/nest.ts +++ b/packages/node/src/integrations/tracing/nest/nest.ts @@ -32,8 +32,6 @@ const instrumentNestEvent = generateInstrumentOnce('Nest-Event', () => { export const instrumentNest = Object.assign( (): void => { - // eslint-disable-next-line no-console - console.log('HIT instrumentNest'); instrumentNestCore(); instrumentNestCommon(); instrumentNestEvent(); @@ -45,8 +43,6 @@ const _nestIntegration = (() => { return { name: INTEGRATION_NAME, setupOnce() { - // eslint-disable-next-line no-console - console.log('HIT nest setup'); instrumentNest(); }, }; diff --git a/packages/node/src/integrations/tracing/nest/sentry-nest-event-instrumentation.ts b/packages/node/src/integrations/tracing/nest/sentry-nest-event-instrumentation.ts index f3279e6b2231..cb3070bfc8b8 100644 --- a/packages/node/src/integrations/tracing/nest/sentry-nest-event-instrumentation.ts +++ b/packages/node/src/integrations/tracing/nest/sentry-nest-event-instrumentation.ts @@ -7,6 +7,8 @@ import { } from '@opentelemetry/instrumentation'; import { SDK_VERSION } from '@sentry/utils'; import type { OnEventTarget } from './types'; +import { captureException, startSpan } from '@sentry/core'; +import { getEventSpanOptions } from './helpers'; const supportedVersions = ['>=2.0.0']; @@ -29,8 +31,6 @@ export class SentryNestEventInstrumentation extends InstrumentationBase { * Initializes the instrumentation by defining the modules to be patched. */ public init(): InstrumentationNodeModuleDefinition { - // eslint-disable-next-line no-console - console.log(32); const moduleDef = new InstrumentationNodeModuleDefinition( SentryNestEventInstrumentation.COMPONENT, supportedVersions, @@ -44,8 +44,6 @@ export class SentryNestEventInstrumentation extends InstrumentationBase { * Wraps the @OnEvent decorator. */ private _getOnEventFileInstrumentation(versions: string[]): InstrumentationNodeModuleFile { - // eslint-disable-next-line no-console - console.log(46); return new InstrumentationNodeModuleFile( '@nestjs/event-emitter/dist/decorators/on-event.decorator.js', versions, @@ -68,13 +66,52 @@ export class SentryNestEventInstrumentation extends InstrumentationBase { private _createWrapOnEvent() { // eslint-disable-next-line @typescript-eslint/no-explicit-any return function wrapOnEvent(original: any) { - return function wrappedOnEvent(target: OnEventTarget) { - return function (name: string) { - return function () { - // eslint-disable-next-line no-console - console.log('wrappedOnEvent', name); - return original(name)(target); + // eslint-disable-next-line @typescript-eslint/no-explicit-any + return function wrappedOnEvent(event: any, options?: any) { + const eventName = Array.isArray(event) + ? event.join(',') + : typeof event === 'string' || typeof event === 'symbol' + ? event.toString() + : ''; + + // Get the original decorator result + const decoratorResult = original(event, options); + + // Return a new decorator function that wraps the handler + return function (target: OnEventTarget, propertyKey: string | symbol, descriptor: PropertyDescriptor) { + if (!descriptor.value || typeof descriptor.value !== 'function' || target.__SENTRY_INTERNAL__) { + return decoratorResult(target, propertyKey, descriptor); + } + + // Get the original handler + const originalHandler = descriptor.value; + // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access + const handlerName = originalHandler.name || propertyKey; + + // Instrument the handler + // eslint-disable-next-line @typescript-eslint/no-explicit-any + descriptor.value = async function (...args: any[]) { + return startSpan(getEventSpanOptions(eventName), async () => { + try { + // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access + const result = await originalHandler.apply(this, args); + return result; + } catch (error) { + // exceptions from event handlers are not caught by global error filter + captureException(error); + throw error; + } + }); }; + + // Preserve the original function name + Object.defineProperty(descriptor.value, 'name', { + value: handlerName, + configurable: true, + }); + + // Apply the original decorator + return decoratorResult(target, propertyKey, descriptor); }; }; }; 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 3b140b17ce7c..2d59d97d87fd 100644 --- a/packages/node/src/integrations/tracing/nest/sentry-nest-instrumentation.ts +++ b/packages/node/src/integrations/tracing/nest/sentry-nest-instrumentation.ts @@ -34,8 +34,6 @@ export class SentryNestInstrumentation extends InstrumentationBase { * Initializes the instrumentation by defining the modules to be patched. */ public init(): InstrumentationNodeModuleDefinition { - // eslint-disable-next-line no-console - console.log('HIT init common'); const moduleDef = new InstrumentationNodeModuleDefinition(SentryNestInstrumentation.COMPONENT, supportedVersions); moduleDef.files.push( diff --git a/packages/node/test/integrations/tracing/nest.test.ts b/packages/node/test/integrations/tracing/nest.test.ts index 3837e3e4ee3d..7ab98fceaad5 100644 --- a/packages/node/test/integrations/tracing/nest.test.ts +++ b/packages/node/test/integrations/tracing/nest.test.ts @@ -1,5 +1,8 @@ import { isPatched } from '../../../src/integrations/tracing/nest/helpers'; import type { InjectableTarget } from '../../../src/integrations/tracing/nest/types'; +import { SentryNestEventInstrumentation } from '../../../src/integrations/tracing/nest/sentry-nest-event-instrumentation'; +import * as core from '@sentry/core'; +import type { OnEventTarget } from '../../../src/integrations/tracing/nest/types'; describe('Nest', () => { describe('isPatched', () => { @@ -14,4 +17,99 @@ describe('Nest', () => { expect(target.sentryPatched).toBe(true); }); }); + + describe('EventInstrumentation', () => { + let instrumentation: SentryNestEventInstrumentation; + let mockOnEvent: jest.Mock; + let mockTarget: OnEventTarget; + + beforeEach(() => { + instrumentation = new SentryNestEventInstrumentation(); + // Mock OnEvent to return a function that applies the descriptor + mockOnEvent = jest.fn().mockImplementation(() => { + return (target: any, propertyKey: string, descriptor: PropertyDescriptor) => { + return descriptor; + }; + }); + mockTarget = { + name: 'TestClass', + prototype: {}, + } as OnEventTarget; + jest.spyOn(core, 'startSpan'); + jest.spyOn(core, 'captureException'); + }); + + afterEach(() => { + jest.restoreAllMocks(); + }); + + describe('init()', () => { + it('should return module definition with correct component name', () => { + const moduleDef = instrumentation.init(); + expect(moduleDef.name).toBe('@nestjs/event-emitter'); + }); + }); + + describe('OnEvent decorator wrapping', () => { + let wrappedOnEvent: any; + let descriptor: PropertyDescriptor; + let originalHandler: jest.Mock; + + beforeEach(() => { + originalHandler = jest.fn().mockResolvedValue('result'); + descriptor = { + value: originalHandler, + }; + + const moduleDef = instrumentation.init(); + const onEventFile = moduleDef.files[0]; + const moduleExports = { OnEvent: mockOnEvent }; + onEventFile?.patch(moduleExports); + wrappedOnEvent = moduleExports.OnEvent; + }); + + it('should wrap string event handlers', async () => { + const decorated = wrappedOnEvent('test.event'); + decorated(mockTarget, 'testMethod', descriptor); + + await descriptor.value(); + + expect(core.startSpan).toHaveBeenCalled(); + expect(originalHandler).toHaveBeenCalled(); + }); + + it('should wrap array event handlers', async () => { + const decorated = wrappedOnEvent(['test.event1', 'test.event2']); + decorated(mockTarget, 'testMethod', descriptor); + + await descriptor.value(); + + expect(core.startSpan).toHaveBeenCalled(); + expect(originalHandler).toHaveBeenCalled(); + }); + + it('should capture exceptions and rethrow', async () => { + const error = new Error('Test error'); + originalHandler.mockRejectedValue(error); + + const decorated = wrappedOnEvent('test.event'); + decorated(mockTarget, 'testMethod', descriptor); + + await expect(descriptor.value()).rejects.toThrow(error); + expect(core.captureException).toHaveBeenCalledWith(error); + }); + + it('should skip wrapping for internal Sentry handlers', () => { + const internalTarget = { + ...mockTarget, + __SENTRY_INTERNAL__: true, + }; + + const decorated = wrappedOnEvent('test.event'); + decorated(internalTarget, 'testMethod', descriptor); + + expect(descriptor.value).toBe(originalHandler); + }); + }); + }); }); diff --git a/yarn.lock b/yarn.lock index 6b6ba1e04520..230246b7dfdc 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6632,7 +6632,7 @@ iterare "1.2.1" tslib "2.7.0" -"@nestjs/common@10.4.7": +"@nestjs/common@^8.0.0 || ^9.0.0 || ^10.0.0": version "10.4.7" resolved "https://registry.yarnpkg.com/@nestjs/common/-/common-10.4.7.tgz#076cb77c06149805cb1e193d8cdc69bbe8446c75" integrity sha512-gIOpjD3Mx8gfYGxYm/RHPcJzqdknNNFCyY+AxzBT3gc5Xvvik1Dn5OxaMGw5EbVfhZgJKVP0n83giUOAlZQe7w== @@ -6653,7 +6653,7 @@ path-to-regexp "3.3.0" tslib "2.7.0" -"@nestjs/core@10.4.7": +"@nestjs/core@^8.0.0 || ^9.0.0 || ^10.0.0": version "10.4.7" resolved "https://registry.yarnpkg.com/@nestjs/core/-/core-10.4.7.tgz#adb27067a8c40b79f0713b417457fdfc6cf3406a" integrity sha512-AIpQzW/vGGqSLkKvll1R7uaSNv99AxZI2EFyVJPNGDgFsfXaohfV1Ukl6f+s75Km+6Fj/7aNl80EqzNWQCS8Ig== @@ -6672,7 +6672,7 @@ dependencies: eventemitter2 "6.4.9" -"@nestjs/platform-express@^10.4.6": +"@nestjs/platform-express@10.4.6": version "10.4.6" resolved "https://registry.yarnpkg.com/@nestjs/platform-express/-/platform-express-10.4.6.tgz#6c39c522fa66036b4256714fea203fbeb49fc4de" integrity sha512-HcyCpAKccAasrLSGRTGWv5BKRs0rwTIFOSsk6laNyqfqvgvYcJQAedarnm4jmaemtmSJ0PFI9PmtEZADd2ahCg== @@ -12962,25 +12962,7 @@ bluebird@^3.4.6, bluebird@^3.7.2: resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.7.2.tgz#9f229c15be272454ffa973ace0dbee79a1b0c36f" integrity sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg== -body-parser@1.20.3, body-parser@^1.18.3, body-parser@^1.19.0: - version "1.20.3" - resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.20.3.tgz#1953431221c6fb5cd63c4b36d53fab0928e548c6" - integrity sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g== - dependencies: - bytes "3.1.2" - content-type "~1.0.5" - debug "2.6.9" - depd "2.0.0" - destroy "1.2.0" - http-errors "2.0.0" - iconv-lite "0.4.24" - on-finished "2.4.1" - qs "6.13.0" - raw-body "2.5.2" - type-is "~1.6.18" - unpipe "1.0.0" - -body-parser@^1.20.3: +body-parser@1.20.3, body-parser@^1.18.3, body-parser@^1.19.0, body-parser@^1.20.3: version "1.20.3" resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.20.3.tgz#1953431221c6fb5cd63c4b36d53fab0928e548c6" integrity sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g== @@ -28366,13 +28348,6 @@ qs@^6.4.0: dependencies: side-channel "^1.0.4" -qs@6.13.0: - version "6.13.0" - resolved "https://registry.yarnpkg.com/qs/-/qs-6.13.0.tgz#6ca3bd58439f7e245655798997787b0d88a51906" - integrity sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg== - dependencies: - side-channel "^1.0.6" - query-string@^4.2.2: version "4.3.4" resolved "https://registry.yarnpkg.com/query-string/-/query-string-4.3.4.tgz#bbb693b9ca915c232515b228b1a02b609043dbeb" From 2faf6dc3f993e73a7a792b05ec7a584fd969a038 Mon Sep 17 00:00:00 2001 From: Charly Gomez Date: Thu, 14 Nov 2024 15:44:17 +0100 Subject: [PATCH 3/8] remove log --- packages/nestjs/src/sdk.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/nestjs/src/sdk.ts b/packages/nestjs/src/sdk.ts index 7815f4372d71..8d5ca21b1706 100644 --- a/packages/nestjs/src/sdk.ts +++ b/packages/nestjs/src/sdk.ts @@ -6,7 +6,6 @@ import { init as nodeInit } from '@sentry/node'; * Initializes the NestJS SDK */ export function init(options: NodeOptions | undefined = {}): NodeClient | undefined { - console.log(9, 'init v4'); const opts: NodeOptions = { ...options, }; From 71e2eac89efeb7858371bb9aa95bc7ac8f4282e5 Mon Sep 17 00:00:00 2001 From: Charly Gomez Date: Thu, 14 Nov 2024 15:45:13 +0100 Subject: [PATCH 4/8] lint --- packages/nestjs/src/setup.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/packages/nestjs/src/setup.ts b/packages/nestjs/src/setup.ts index 11585b28ad4b..a18f95417f11 100644 --- a/packages/nestjs/src/setup.ts +++ b/packages/nestjs/src/setup.ts @@ -193,7 +193,6 @@ class SentryModule { module: SentryModule, providers: [ SentryService, - { provide: APP_INTERCEPTOR, useClass: SentryTracingInterceptor, @@ -207,7 +206,6 @@ Global()(SentryModule); Module({ providers: [ SentryService, - { provide: APP_INTERCEPTOR, useClass: SentryTracingInterceptor, From 22fca1a9374313f791f8d833413636ea7bd1665a Mon Sep 17 00:00:00 2001 From: Charly Gomez Date: Thu, 14 Nov 2024 18:07:33 +0100 Subject: [PATCH 5/8] add e2e test --- .../nestjs-distributed-tracing/package.json | 1 + .../src/events.controller.ts | 14 ++++++ .../src/events.module.ts | 21 +++++++++ .../src/events.service.ts | 14 ++++++ .../src/listeners/test-event.listener.ts | 16 +++++++ .../nestjs-distributed-tracing/src/main.ts | 5 +++ .../tests/events.test.ts | 43 +++++++++++++++++++ 7 files changed, 114 insertions(+) create mode 100644 dev-packages/e2e-tests/test-applications/nestjs-distributed-tracing/src/events.controller.ts create mode 100644 dev-packages/e2e-tests/test-applications/nestjs-distributed-tracing/src/events.module.ts create mode 100644 dev-packages/e2e-tests/test-applications/nestjs-distributed-tracing/src/events.service.ts create mode 100644 dev-packages/e2e-tests/test-applications/nestjs-distributed-tracing/src/listeners/test-event.listener.ts create mode 100644 dev-packages/e2e-tests/test-applications/nestjs-distributed-tracing/tests/events.test.ts diff --git a/dev-packages/e2e-tests/test-applications/nestjs-distributed-tracing/package.json b/dev-packages/e2e-tests/test-applications/nestjs-distributed-tracing/package.json index b4d0ead875f9..efc52a8a4db9 100644 --- a/dev-packages/e2e-tests/test-applications/nestjs-distributed-tracing/package.json +++ b/dev-packages/e2e-tests/test-applications/nestjs-distributed-tracing/package.json @@ -18,6 +18,7 @@ "@nestjs/common": "^10.0.0", "@nestjs/core": "^10.0.0", "@nestjs/platform-express": "^10.0.0", + "@nestjs/event-emitter": "^2.0.0", "@sentry/nestjs": "latest || *", "@sentry/types": "latest || *", "reflect-metadata": "^0.2.0", diff --git a/dev-packages/e2e-tests/test-applications/nestjs-distributed-tracing/src/events.controller.ts b/dev-packages/e2e-tests/test-applications/nestjs-distributed-tracing/src/events.controller.ts new file mode 100644 index 000000000000..cb5ddebcc3ae --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/nestjs-distributed-tracing/src/events.controller.ts @@ -0,0 +1,14 @@ +import { Controller, Get } from '@nestjs/common'; +import { EventsService } from './events.service'; + +@Controller('events') +export class EventsController { + constructor(private readonly eventsService: EventsService) {} + + @Get('emit') + async emitEvents() { + await this.eventsService.emitEvents(); + + return { message: 'Events emitted' }; + } +} diff --git a/dev-packages/e2e-tests/test-applications/nestjs-distributed-tracing/src/events.module.ts b/dev-packages/e2e-tests/test-applications/nestjs-distributed-tracing/src/events.module.ts new file mode 100644 index 000000000000..df09b062c446 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/nestjs-distributed-tracing/src/events.module.ts @@ -0,0 +1,21 @@ +import { Module } from '@nestjs/common'; +import { EventEmitterModule } from '@nestjs/event-emitter'; +import { SentryGlobalFilter, SentryModule } from '@sentry/nestjs/setup'; +import { TestEventListener } from './listeners/test-event.listener'; +import { EventsController } from './events.controller'; +import { APP_FILTER } from '@nestjs/core'; +import { EventsService } from './events.service'; + +@Module({ + imports: [SentryModule.forRoot(), EventEmitterModule.forRoot()], + controllers: [EventsController], + providers: [ + { + provide: APP_FILTER, + useClass: SentryGlobalFilter, + }, + EventsService, + TestEventListener, + ], +}) +export class EventsModule {} diff --git a/dev-packages/e2e-tests/test-applications/nestjs-distributed-tracing/src/events.service.ts b/dev-packages/e2e-tests/test-applications/nestjs-distributed-tracing/src/events.service.ts new file mode 100644 index 000000000000..4a9f36ddaf5c --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/nestjs-distributed-tracing/src/events.service.ts @@ -0,0 +1,14 @@ +import { Injectable } from '@nestjs/common'; +import { EventEmitter2 } from '@nestjs/event-emitter'; + +@Injectable() +export class EventsService { + constructor(private readonly eventEmitter: EventEmitter2) {} + + async emitEvents() { + await this.eventEmitter.emit('myEvent.pass', { data: 'test' }); + await this.eventEmitter.emit('myEvent.throw'); + + return { message: 'Events emitted' }; + } +} diff --git a/dev-packages/e2e-tests/test-applications/nestjs-distributed-tracing/src/listeners/test-event.listener.ts b/dev-packages/e2e-tests/test-applications/nestjs-distributed-tracing/src/listeners/test-event.listener.ts new file mode 100644 index 000000000000..c1a3237f1f0c --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/nestjs-distributed-tracing/src/listeners/test-event.listener.ts @@ -0,0 +1,16 @@ +import { Injectable } from '@nestjs/common'; +import { OnEvent } from '@nestjs/event-emitter'; + +@Injectable() +export class TestEventListener { + @OnEvent('myEvent.pass') + async handlePassEvent(payload: any): Promise { + await new Promise(resolve => setTimeout(resolve, 100)); + } + + @OnEvent('myEvent.throw') + async handleThrowEvent(): Promise { + await new Promise(resolve => setTimeout(resolve, 100)); + throw new Error('Test error from event handler'); + } +} diff --git a/dev-packages/e2e-tests/test-applications/nestjs-distributed-tracing/src/main.ts b/dev-packages/e2e-tests/test-applications/nestjs-distributed-tracing/src/main.ts index 5aad5748b244..38a0b66f3e28 100644 --- a/dev-packages/e2e-tests/test-applications/nestjs-distributed-tracing/src/main.ts +++ b/dev-packages/e2e-tests/test-applications/nestjs-distributed-tracing/src/main.ts @@ -5,9 +5,11 @@ import './instrument'; import { NestFactory } from '@nestjs/core'; import { TraceInitiatorModule } from './trace-initiator.module'; import { TraceReceiverModule } from './trace-receiver.module'; +import { EventsModule } from './events.module'; const TRACE_INITIATOR_PORT = 3030; const TRACE_RECEIVER_PORT = 3040; +const EVENTS_PORT = 3050; async function bootstrap() { const trace_initiator_app = await NestFactory.create(TraceInitiatorModule); @@ -15,6 +17,9 @@ async function bootstrap() { const trace_receiver_app = await NestFactory.create(TraceReceiverModule); await trace_receiver_app.listen(TRACE_RECEIVER_PORT); + + const events_app = await NestFactory.create(EventsModule); + await events_app.listen(EVENTS_PORT); } bootstrap(); diff --git a/dev-packages/e2e-tests/test-applications/nestjs-distributed-tracing/tests/events.test.ts b/dev-packages/e2e-tests/test-applications/nestjs-distributed-tracing/tests/events.test.ts new file mode 100644 index 000000000000..b09eabb38980 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/nestjs-distributed-tracing/tests/events.test.ts @@ -0,0 +1,43 @@ +import { expect, test } from '@playwright/test'; +import { waitForError, waitForTransaction } from '@sentry-internal/test-utils'; + +test('Event emitter', async () => { + const eventErrorPromise = waitForError('nestjs-distributed-tracing', errorEvent => { + return errorEvent.exception.values[0].value === 'Test error from event handler'; + }); + const successEventTransactionPromise = waitForTransaction('nestjs-distributed-tracing', transactionEvent => { + return transactionEvent.transaction === 'event myEvent.pass'; + }); + + const eventsUrl = `http://localhost:3050/events/emit`; + await fetch(eventsUrl); + + const eventError = await eventErrorPromise; + const successEventTransaction = await successEventTransactionPromise; + + expect(eventError.exception).toEqual({ + values: [ + { + type: 'Error', + value: 'Test error from event handler', + stacktrace: expect.any(Object), + mechanism: expect.any(Object), + }, + ], + }); + + expect(successEventTransaction.contexts.trace).toEqual({ + parent_span_id: expect.any(String), + span_id: expect.any(String), + trace_id: expect.any(String), + data: { + 'sentry.source': 'custom', + 'sentry.sample_rate': 1, + 'sentry.op': 'event.nestjs', + 'sentry.origin': 'auto.event.nestjs', + }, + origin: 'auto.event.nestjs', + op: 'event.nestjs', + status: 'ok', + }); +}); From e644734c6ff315667e1bd3c168f62f30c4125fbd Mon Sep 17 00:00:00 2001 From: Charly Gomez Date: Fri, 15 Nov 2024 09:56:29 +0100 Subject: [PATCH 6/8] lint --- .../nestjs-distributed-tracing/src/events.module.ts | 4 ++-- .../test-applications/nestjs-distributed-tracing/src/main.ts | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/dev-packages/e2e-tests/test-applications/nestjs-distributed-tracing/src/events.module.ts b/dev-packages/e2e-tests/test-applications/nestjs-distributed-tracing/src/events.module.ts index df09b062c446..b92995e323eb 100644 --- a/dev-packages/e2e-tests/test-applications/nestjs-distributed-tracing/src/events.module.ts +++ b/dev-packages/e2e-tests/test-applications/nestjs-distributed-tracing/src/events.module.ts @@ -1,10 +1,10 @@ import { Module } from '@nestjs/common'; +import { APP_FILTER } from '@nestjs/core'; import { EventEmitterModule } from '@nestjs/event-emitter'; import { SentryGlobalFilter, SentryModule } from '@sentry/nestjs/setup'; -import { TestEventListener } from './listeners/test-event.listener'; import { EventsController } from './events.controller'; -import { APP_FILTER } from '@nestjs/core'; import { EventsService } from './events.service'; +import { TestEventListener } from './listeners/test-event.listener'; @Module({ imports: [SentryModule.forRoot(), EventEmitterModule.forRoot()], diff --git a/dev-packages/e2e-tests/test-applications/nestjs-distributed-tracing/src/main.ts b/dev-packages/e2e-tests/test-applications/nestjs-distributed-tracing/src/main.ts index 38a0b66f3e28..a18877460852 100644 --- a/dev-packages/e2e-tests/test-applications/nestjs-distributed-tracing/src/main.ts +++ b/dev-packages/e2e-tests/test-applications/nestjs-distributed-tracing/src/main.ts @@ -3,9 +3,9 @@ import './instrument'; // Import other modules import { NestFactory } from '@nestjs/core'; +import { EventsModule } from './events.module'; import { TraceInitiatorModule } from './trace-initiator.module'; import { TraceReceiverModule } from './trace-receiver.module'; -import { EventsModule } from './events.module'; const TRACE_INITIATOR_PORT = 3030; const TRACE_RECEIVER_PORT = 3040; From dcf2cce87caaf58ed524531cebbf2708c9a9d8e8 Mon Sep 17 00:00:00 2001 From: Charly Gomez Date: Fri, 15 Nov 2024 10:18:42 +0100 Subject: [PATCH 7/8] more biome --- packages/node/src/integrations/tracing/nest/nest.ts | 2 +- .../tracing/nest/sentry-nest-event-instrumentation.ts | 4 ++-- packages/node/test/integrations/tracing/nest.test.ts | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/node/src/integrations/tracing/nest/nest.ts b/packages/node/src/integrations/tracing/nest/nest.ts index 91ab9e9068e2..2520367d1361 100644 --- a/packages/node/src/integrations/tracing/nest/nest.ts +++ b/packages/node/src/integrations/tracing/nest/nest.ts @@ -12,9 +12,9 @@ import { import type { IntegrationFn, Span } from '@sentry/types'; import { logger } from '@sentry/utils'; import { generateInstrumentOnce } from '../../../otel/instrument'; +import { SentryNestEventInstrumentation } from './sentry-nest-event-instrumentation'; import { SentryNestInstrumentation } from './sentry-nest-instrumentation'; import type { MinimalNestJsApp, NestJsErrorFilter } from './types'; -import { SentryNestEventInstrumentation } from './sentry-nest-event-instrumentation'; const INTEGRATION_NAME = 'Nest'; diff --git a/packages/node/src/integrations/tracing/nest/sentry-nest-event-instrumentation.ts b/packages/node/src/integrations/tracing/nest/sentry-nest-event-instrumentation.ts index cb3070bfc8b8..16333c7fc6c3 100644 --- a/packages/node/src/integrations/tracing/nest/sentry-nest-event-instrumentation.ts +++ b/packages/node/src/integrations/tracing/nest/sentry-nest-event-instrumentation.ts @@ -5,10 +5,10 @@ import { InstrumentationNodeModuleDefinition, InstrumentationNodeModuleFile, } from '@opentelemetry/instrumentation'; -import { SDK_VERSION } from '@sentry/utils'; -import type { OnEventTarget } from './types'; import { captureException, startSpan } from '@sentry/core'; +import { SDK_VERSION } from '@sentry/utils'; import { getEventSpanOptions } from './helpers'; +import type { OnEventTarget } from './types'; const supportedVersions = ['>=2.0.0']; diff --git a/packages/node/test/integrations/tracing/nest.test.ts b/packages/node/test/integrations/tracing/nest.test.ts index 7ab98fceaad5..7f592a93f341 100644 --- a/packages/node/test/integrations/tracing/nest.test.ts +++ b/packages/node/test/integrations/tracing/nest.test.ts @@ -1,7 +1,7 @@ +import * as core from '@sentry/core'; import { isPatched } from '../../../src/integrations/tracing/nest/helpers'; -import type { InjectableTarget } from '../../../src/integrations/tracing/nest/types'; import { SentryNestEventInstrumentation } from '../../../src/integrations/tracing/nest/sentry-nest-event-instrumentation'; -import * as core from '@sentry/core'; +import type { InjectableTarget } from '../../../src/integrations/tracing/nest/types'; import type { OnEventTarget } from '../../../src/integrations/tracing/nest/types'; describe('Nest', () => { From 62e34d382ffb145ea18f78000cac5a5112b1428d Mon Sep 17 00:00:00 2001 From: Charly Gomez Date: Fri, 15 Nov 2024 11:36:26 +0100 Subject: [PATCH 8/8] remove unused dep --- packages/nestjs/package.json | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/packages/nestjs/package.json b/packages/nestjs/package.json index 2ba8e6194053..76028b27e40f 100644 --- a/packages/nestjs/package.json +++ b/packages/nestjs/package.json @@ -51,8 +51,7 @@ }, "devDependencies": { "@nestjs/common": "^8.0.0 || ^9.0.0 || ^10.0.0", - "@nestjs/core": "^8.0.0 || ^9.0.0 || ^10.0.0", - "@nestjs/event-emitter": "^2.0.0" + "@nestjs/core": "^8.0.0 || ^9.0.0 || ^10.0.0" }, "peerDependencies": { "@nestjs/common": "^8.0.0 || ^9.0.0 || ^10.0.0",