From 5963dbeecd76cb3a1b9c70b8a2af9da4580bdba2 Mon Sep 17 00:00:00 2001 From: nicohrubec Date: Tue, 9 Jul 2024 15:39:04 +0200 Subject: [PATCH 01/60] Add nest error handler setup to nest SDK --- packages/nestjs/src/index.ts | 1 + packages/nestjs/src/setup.ts | 136 +++++++++++++++++++++++++++++++++++ 2 files changed, 137 insertions(+) create mode 100644 packages/nestjs/src/setup.ts diff --git a/packages/nestjs/src/index.ts b/packages/nestjs/src/index.ts index 00519cf49b9e..d38a2aff2993 100644 --- a/packages/nestjs/src/index.ts +++ b/packages/nestjs/src/index.ts @@ -1,6 +1,7 @@ export * from '@sentry/node'; export { init } from './sdk'; +export { nestIntegration, setupNestErrorHandler } from './setup'; export { SentryTraced } from './span-decorator'; export { SentryCron } from './cron-decorator'; diff --git a/packages/nestjs/src/setup.ts b/packages/nestjs/src/setup.ts new file mode 100644 index 000000000000..18e321e29eeb --- /dev/null +++ b/packages/nestjs/src/setup.ts @@ -0,0 +1,136 @@ +import { NestInstrumentation } from '@opentelemetry/instrumentation-nestjs-core'; +import { + SEMANTIC_ATTRIBUTE_SENTRY_OP, + SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN, + captureException, + defineIntegration, + getClient, + getDefaultIsolationScope, + getIsolationScope, + spanToJSON, +} from '@sentry/core'; +import type { IntegrationFn, Span } from '@sentry/types'; +import { logger } from '@sentry/utils'; +import { generateInstrumentOnce } from '@sentry/node/build/types/otel/instrument'; + +interface MinimalNestJsExecutionContext { + getType: () => string; + + switchToHttp: () => { + // minimal request object + // according to official types, all properties are required but + // let's play it safe and assume they're optional + getRequest: () => { + route?: { + path?: string; + }; + method?: string; + }; + }; +} + +interface NestJsErrorFilter { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + catch(exception: any, host: any): void; +} + +interface MinimalNestJsApp { + useGlobalFilters: (arg0: NestJsErrorFilter) => void; + useGlobalInterceptors: (interceptor: { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + intercept: (context: MinimalNestJsExecutionContext, next: { handle: () => any }) => any; + }) => void; +} + +const INTEGRATION_NAME = 'Nest'; + +export const instrumentNest = generateInstrumentOnce(INTEGRATION_NAME, () => new NestInstrumentation()); + +const _nestIntegration = (() => { + return { + name: INTEGRATION_NAME, + setupOnce() { + instrumentNest(); + }, + }; +}) satisfies IntegrationFn; + +/** + * Nest framework integration + * + * Capture tracing data for nest. + */ +export const nestIntegration = defineIntegration(_nestIntegration); + +/** + * Setup an error handler for Nest. + */ +export function setupNestErrorHandler(app: MinimalNestJsApp, baseFilter: NestJsErrorFilter): void { + // Sadly, NestInstrumentation has no requestHook, so we need to add the attributes here + // We register this hook in this method, because if we register it in the integration `setup`, + // it would always run even for users that are not even using Nest.js + const client = getClient(); + if (client) { + client.on('spanStart', span => { + addNestSpanAttributes(span); + }); + } + + app.useGlobalInterceptors({ + intercept(context, next) { + if (getIsolationScope() === getDefaultIsolationScope()) { + logger.warn('Isolation scope is still the default isolation scope, skipping setting transactionName.'); + return next.handle(); + } + + if (context.getType() === 'http') { + const req = context.switchToHttp().getRequest(); + if (req.route) { + getIsolationScope().setTransactionName(`${req.method?.toUpperCase() || 'GET'} ${req.route.path}`); + } + } + + return next.handle(); + }, + }); + + const wrappedFilter = new Proxy(baseFilter, { + get(target, prop, receiver) { + if (prop === 'catch') { + const originalCatch = Reflect.get(target, prop, receiver); + + return (exception: unknown, host: unknown) => { + const status_code = (exception as { status?: number }).status; + + // don't report expected errors + if (status_code !== undefined && status_code >= 400 && status_code < 500) { + return originalCatch.apply(target, [exception, host]); + } + + captureException(exception); + return originalCatch.apply(target, [exception, host]); + }; + } + return Reflect.get(target, prop, receiver); + }, + }); + + app.useGlobalFilters(wrappedFilter); +} + +function addNestSpanAttributes(span: Span): void { + const attributes = spanToJSON(span).data || {}; + + // this is one of: app_creation, request_context, handler + const type = attributes['nestjs.type']; + + // If this is already set, or we have no nest.js span, no need to process again... + if (attributes[SEMANTIC_ATTRIBUTE_SENTRY_OP] || !type) { + return; + } + + span.setAttributes({ + [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.http.otel.nestjs', + [SEMANTIC_ATTRIBUTE_SENTRY_OP]: `${type}.nestjs`, + }); +} From d4ca10adc2afedf0b9d9e43d7caa4fcc0d264cb6 Mon Sep 17 00:00:00 2001 From: nicohrubec Date: Tue, 9 Jul 2024 15:46:57 +0200 Subject: [PATCH 02/60] Add otel dependency to nest --- packages/nestjs/package.json | 3 ++- packages/nestjs/src/setup.ts | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/packages/nestjs/package.json b/packages/nestjs/package.json index 322c44d937f9..d7af6ae5c8da 100644 --- a/packages/nestjs/package.json +++ b/packages/nestjs/package.json @@ -40,7 +40,8 @@ }, "dependencies": { "@sentry/core": "8.15.0", - "@sentry/node": "8.15.0" + "@sentry/node": "8.15.0", + "@opentelemetry/instrumentation-nestjs-core": "0.38.0" }, "scripts": { "build": "run-p build:transpile build:types", diff --git a/packages/nestjs/src/setup.ts b/packages/nestjs/src/setup.ts index 18e321e29eeb..9d2b33bd45f5 100644 --- a/packages/nestjs/src/setup.ts +++ b/packages/nestjs/src/setup.ts @@ -9,9 +9,9 @@ import { getIsolationScope, spanToJSON, } from '@sentry/core'; +import { generateInstrumentOnce } from '@sentry/node/build/types/otel/instrument'; import type { IntegrationFn, Span } from '@sentry/types'; import { logger } from '@sentry/utils'; -import { generateInstrumentOnce } from '@sentry/node/build/types/otel/instrument'; interface MinimalNestJsExecutionContext { getType: () => string; From d9588ef3e602a42b679d4af5d0a04f90ef8810f3 Mon Sep 17 00:00:00 2001 From: nicohrubec Date: Tue, 9 Jul 2024 17:17:52 +0200 Subject: [PATCH 03/60] Update import --- packages/nestjs/src/setup.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/nestjs/src/setup.ts b/packages/nestjs/src/setup.ts index 9d2b33bd45f5..97a8d321fdef 100644 --- a/packages/nestjs/src/setup.ts +++ b/packages/nestjs/src/setup.ts @@ -9,7 +9,7 @@ import { getIsolationScope, spanToJSON, } from '@sentry/core'; -import { generateInstrumentOnce } from '@sentry/node/build/types/otel/instrument'; +import { generateInstrumentOnce } from '@sentry/node'; import type { IntegrationFn, Span } from '@sentry/types'; import { logger } from '@sentry/utils'; From edcc9dc465955d01b6ea5432f59e028f967f52e7 Mon Sep 17 00:00:00 2001 From: nicohrubec Date: Tue, 9 Jul 2024 17:30:24 +0200 Subject: [PATCH 04/60] Update sentry version --- packages/nestjs/package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/nestjs/package.json b/packages/nestjs/package.json index d7af6ae5c8da..02a4b05e03f9 100644 --- a/packages/nestjs/package.json +++ b/packages/nestjs/package.json @@ -39,8 +39,8 @@ "access": "public" }, "dependencies": { - "@sentry/core": "8.15.0", - "@sentry/node": "8.15.0", + "@sentry/core": "8.16.0", + "@sentry/node": "8.16.0", "@opentelemetry/instrumentation-nestjs-core": "0.38.0" }, "scripts": { From 3778ea518b0155e354cb21641df932a35f0916e3 Mon Sep 17 00:00:00 2001 From: nicohrubec Date: Wed, 10 Jul 2024 10:04:03 +0200 Subject: [PATCH 05/60] Update dependencies --- packages/nestjs/package.json | 2 ++ packages/nestjs/src/setup.ts | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/nestjs/package.json b/packages/nestjs/package.json index db81beccf0b3..0d24ceed2fd4 100644 --- a/packages/nestjs/package.json +++ b/packages/nestjs/package.json @@ -41,6 +41,8 @@ "dependencies": { "@sentry/core": "8.16.0", "@sentry/node": "8.16.0", + "@sentry/types": "8.16.0", + "@sentry/utils": "8.16.0", "@opentelemetry/instrumentation-nestjs-core": "0.38.0" }, "scripts": { diff --git a/packages/nestjs/src/setup.ts b/packages/nestjs/src/setup.ts index 97a8d321fdef..09205e14411f 100644 --- a/packages/nestjs/src/setup.ts +++ b/packages/nestjs/src/setup.ts @@ -9,9 +9,9 @@ import { getIsolationScope, spanToJSON, } from '@sentry/core'; -import { generateInstrumentOnce } from '@sentry/node'; import type { IntegrationFn, Span } from '@sentry/types'; import { logger } from '@sentry/utils'; +import { generateInstrumentOnce } from '@sentry/node'; interface MinimalNestJsExecutionContext { getType: () => string; From dd5f55809b95313c95d6d8dabdebc3c7baa0b436 Mon Sep 17 00:00:00 2001 From: nicohrubec Date: Wed, 10 Jul 2024 10:56:54 +0200 Subject: [PATCH 06/60] Fix lints --- packages/nestjs/src/setup.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/nestjs/src/setup.ts b/packages/nestjs/src/setup.ts index 09205e14411f..52dc50f52822 100644 --- a/packages/nestjs/src/setup.ts +++ b/packages/nestjs/src/setup.ts @@ -9,9 +9,9 @@ import { getIsolationScope, spanToJSON, } from '@sentry/core'; +import { generateInstrumentOnce } from '@sentry/node'; import type { IntegrationFn, Span } from '@sentry/types'; import { logger } from '@sentry/utils'; -import { generateInstrumentOnce } from '@sentry/node'; interface MinimalNestJsExecutionContext { getType: () => string; @@ -86,6 +86,7 @@ export function setupNestErrorHandler(app: MinimalNestJsApp, baseFilter: NestJsE if (context.getType() === 'http') { const req = context.switchToHttp().getRequest(); if (req.route) { + // eslint-disable-next-line @sentry-internal/sdk/no-optional-chaining getIsolationScope().setTransactionName(`${req.method?.toUpperCase() || 'GET'} ${req.route.path}`); } } From b9d22c23b0c8dfc20501b546caeb3368a293ec2c Mon Sep 17 00:00:00 2001 From: nicohrubec Date: Wed, 10 Jul 2024 11:42:44 +0200 Subject: [PATCH 07/60] Update yarn.lock --- yarn.lock | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/yarn.lock b/yarn.lock index 935083564419..3dc6084e9f55 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6960,6 +6960,14 @@ "@opentelemetry/semantic-conventions" "^1.22.0" "@types/mysql" "2.15.22" +"@opentelemetry/instrumentation-nestjs-core@0.38.0": + version "0.38.0" + resolved "https://registry.yarnpkg.com/@opentelemetry/instrumentation-nestjs-core/-/instrumentation-nestjs-core-0.38.0.tgz#d4296936723f1dfbd11747a84a87d17a3da0bc74" + integrity sha512-M381Df1dM8aqihZz2yK+ugvMFK5vlHG/835dc67Sx2hH4pQEQYDA2PpFPTgc9AYYOydQaj7ClFQunESimjXDgg== + dependencies: + "@opentelemetry/instrumentation" "^0.52.0" + "@opentelemetry/semantic-conventions" "^1.23.0" + "@opentelemetry/instrumentation-nestjs-core@0.39.0": version "0.39.0" resolved "https://registry.yarnpkg.com/@opentelemetry/instrumentation-nestjs-core/-/instrumentation-nestjs-core-0.39.0.tgz#733fef4306c796951d7ea1951b45f9df0aed234d" From d27f68801e859c49f8f2167382916280964d1943 Mon Sep 17 00:00:00 2001 From: nicohrubec Date: Wed, 10 Jul 2024 12:46:53 +0200 Subject: [PATCH 08/60] Update dependency --- packages/nestjs/package.json | 2 +- yarn.lock | 70 ++++++++++++++++++++++++++++-------- 2 files changed, 57 insertions(+), 15 deletions(-) diff --git a/packages/nestjs/package.json b/packages/nestjs/package.json index 0d24ceed2fd4..bf2e539165aa 100644 --- a/packages/nestjs/package.json +++ b/packages/nestjs/package.json @@ -43,7 +43,7 @@ "@sentry/node": "8.16.0", "@sentry/types": "8.16.0", "@sentry/utils": "8.16.0", - "@opentelemetry/instrumentation-nestjs-core": "0.38.0" + "@opentelemetry/instrumentation-nestjs-core": "0.39.0" }, "scripts": { "build": "run-p build:transpile build:types", diff --git a/yarn.lock b/yarn.lock index 53fcdf9ecb26..8afa90c648ff 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6960,14 +6960,6 @@ "@opentelemetry/semantic-conventions" "^1.22.0" "@types/mysql" "2.15.22" -"@opentelemetry/instrumentation-nestjs-core@0.38.0": - version "0.38.0" - resolved "https://registry.yarnpkg.com/@opentelemetry/instrumentation-nestjs-core/-/instrumentation-nestjs-core-0.38.0.tgz#d4296936723f1dfbd11747a84a87d17a3da0bc74" - integrity sha512-M381Df1dM8aqihZz2yK+ugvMFK5vlHG/835dc67Sx2hH4pQEQYDA2PpFPTgc9AYYOydQaj7ClFQunESimjXDgg== - dependencies: - "@opentelemetry/instrumentation" "^0.52.0" - "@opentelemetry/semantic-conventions" "^1.23.0" - "@opentelemetry/instrumentation-nestjs-core@0.39.0": version "0.39.0" resolved "https://registry.yarnpkg.com/@opentelemetry/instrumentation-nestjs-core/-/instrumentation-nestjs-core-0.39.0.tgz#733fef4306c796951d7ea1951b45f9df0aed234d" @@ -9462,7 +9454,17 @@ dependencies: "@types/unist" "*" -"@types/history-4@npm:@types/history@4.7.8", "@types/history-5@npm:@types/history@4.7.8", "@types/history@*": +"@types/history-4@npm:@types/history@4.7.8": + version "4.7.8" + resolved "https://registry.yarnpkg.com/@types/history/-/history-4.7.8.tgz#49348387983075705fe8f4e02fb67f7daaec4934" + integrity sha512-S78QIYirQcUoo6UJZx9CSP0O2ix9IaeAXwQi26Rhr/+mg7qqPy8TzaxHSUut7eGjL8WmLccT7/MXf304WjqHcA== + +"@types/history-5@npm:@types/history@4.7.8": + version "4.7.8" + resolved "https://registry.yarnpkg.com/@types/history/-/history-4.7.8.tgz#49348387983075705fe8f4e02fb67f7daaec4934" + integrity sha512-S78QIYirQcUoo6UJZx9CSP0O2ix9IaeAXwQi26Rhr/+mg7qqPy8TzaxHSUut7eGjL8WmLccT7/MXf304WjqHcA== + +"@types/history@*": version "4.7.8" resolved "https://registry.yarnpkg.com/@types/history/-/history-4.7.8.tgz#49348387983075705fe8f4e02fb67f7daaec4934" integrity sha512-S78QIYirQcUoo6UJZx9CSP0O2ix9IaeAXwQi26Rhr/+mg7qqPy8TzaxHSUut7eGjL8WmLccT7/MXf304WjqHcA== @@ -9783,7 +9785,15 @@ "@types/history" "^3" "@types/react" "*" -"@types/react-router-4@npm:@types/react-router@5.1.14", "@types/react-router-5@npm:@types/react-router@5.1.14": +"@types/react-router-4@npm:@types/react-router@5.1.14": + version "5.1.14" + resolved "https://registry.yarnpkg.com/@types/react-router/-/react-router-5.1.14.tgz#e0442f4eb4c446541ad7435d44a97f8fe6df40da" + integrity sha512-LAJpqYUaCTMT2anZheoidiIymt8MuX286zoVFPM3DVb23aQBH0mAkFvzpd4LKqiolV8bBtZWT5Qp7hClCNDENw== + dependencies: + "@types/history" "*" + "@types/react" "*" + +"@types/react-router-5@npm:@types/react-router@5.1.14": version "5.1.14" resolved "https://registry.yarnpkg.com/@types/react-router/-/react-router-5.1.14.tgz#e0442f4eb4c446541ad7435d44a97f8fe6df40da" integrity sha512-LAJpqYUaCTMT2anZheoidiIymt8MuX286zoVFPM3DVb23aQBH0mAkFvzpd4LKqiolV8bBtZWT5Qp7hClCNDENw== @@ -28980,7 +28990,7 @@ react-is@^18.0.0: dependencies: "@remix-run/router" "1.0.2" -"react-router-6@npm:react-router@6.3.0", react-router@6.3.0: +"react-router-6@npm:react-router@6.3.0": version "6.3.0" resolved "https://registry.yarnpkg.com/react-router/-/react-router-6.3.0.tgz#3970cc64b4cb4eae0c1ea5203a80334fdd175557" integrity sha512-7Wh1DzVQ+tlFjkeo+ujvjSqSJmkt1+8JO+T5xklPlgrh70y7ogx75ODRW0ThWhY7S+6yEDks8TYrtQe/aoboBQ== @@ -28995,6 +29005,13 @@ react-router-dom@^6.2.2: history "^5.2.0" react-router "6.3.0" +react-router@6.3.0: + version "6.3.0" + resolved "https://registry.yarnpkg.com/react-router/-/react-router-6.3.0.tgz#3970cc64b4cb4eae0c1ea5203a80334fdd175557" + integrity sha512-7Wh1DzVQ+tlFjkeo+ujvjSqSJmkt1+8JO+T5xklPlgrh70y7ogx75ODRW0ThWhY7S+6yEDks8TYrtQe/aoboBQ== + dependencies: + history "^5.2.0" + react@^18.0.0: version "18.0.0" resolved "https://registry.yarnpkg.com/react/-/react-18.0.0.tgz#b468736d1f4a5891f38585ba8e8fb29f91c3cb96" @@ -31527,7 +31544,16 @@ string-template@~0.2.1: resolved "https://registry.yarnpkg.com/string-template/-/string-template-0.2.1.tgz#42932e598a352d01fc22ec3367d9d84eec6c9add" integrity sha1-QpMuWYo1LQH8IuwzZ9nYTuxsmt0= -"string-width-cjs@npm:string-width@^4.2.0", string-width@4.2.3, string-width@^4.2.0, string-width@^4.2.2, string-width@^4.2.3: +"string-width-cjs@npm:string-width@^4.2.0": + version "4.2.3" + resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" + integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== + dependencies: + emoji-regex "^8.0.0" + is-fullwidth-code-point "^3.0.0" + strip-ansi "^6.0.1" + +string-width@4.2.3, string-width@^4.2.0, string-width@^4.2.2, string-width@^4.2.3: version "4.2.3" resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== @@ -31639,7 +31665,14 @@ stringify-object@^3.2.1: is-obj "^1.0.1" is-regexp "^1.0.0" -"strip-ansi-cjs@npm:strip-ansi@^6.0.1", strip-ansi@6.0.1, strip-ansi@^6.0.0, strip-ansi@^6.0.1: +"strip-ansi-cjs@npm:strip-ansi@^6.0.1": + version "6.0.1" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" + integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== + dependencies: + ansi-regex "^5.0.1" + +strip-ansi@6.0.1, strip-ansi@^6.0.0, strip-ansi@^6.0.1: version "6.0.1" resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== @@ -34732,7 +34765,16 @@ workerpool@^6.4.0: resolved "https://registry.yarnpkg.com/workerpool/-/workerpool-6.4.0.tgz#f8d5cfb45fde32fa3b7af72ad617c3369567a462" integrity sha512-i3KR1mQMNwY2wx20ozq2EjISGtQWDIfV56We+yGJ5yDs8jTwQiLLaqHlkBHITlCuJnYlVRmXegxFxZg7gqI++A== -"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0", wrap-ansi@7.0.0, wrap-ansi@^7.0.0: +"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0": + version "7.0.0" + resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" + integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== + dependencies: + ansi-styles "^4.0.0" + string-width "^4.1.0" + strip-ansi "^6.0.0" + +wrap-ansi@7.0.0, wrap-ansi@^7.0.0: version "7.0.0" resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== From 380657e1ffce7cfdebf54af8b89e9fb48739acc0 Mon Sep 17 00:00:00 2001 From: nicohrubec Date: Wed, 10 Jul 2024 13:33:23 +0200 Subject: [PATCH 09/60] Revert yarn.lock --- packages/nestjs/package.json | 2 +- yarn.lock | 62 ++++-------------------------------- 2 files changed, 7 insertions(+), 57 deletions(-) diff --git a/packages/nestjs/package.json b/packages/nestjs/package.json index bf2e539165aa..0d24ceed2fd4 100644 --- a/packages/nestjs/package.json +++ b/packages/nestjs/package.json @@ -43,7 +43,7 @@ "@sentry/node": "8.16.0", "@sentry/types": "8.16.0", "@sentry/utils": "8.16.0", - "@opentelemetry/instrumentation-nestjs-core": "0.39.0" + "@opentelemetry/instrumentation-nestjs-core": "0.38.0" }, "scripts": { "build": "run-p build:transpile build:types", diff --git a/yarn.lock b/yarn.lock index 8afa90c648ff..a423fa79953f 100644 --- a/yarn.lock +++ b/yarn.lock @@ -9454,17 +9454,7 @@ dependencies: "@types/unist" "*" -"@types/history-4@npm:@types/history@4.7.8": - version "4.7.8" - resolved "https://registry.yarnpkg.com/@types/history/-/history-4.7.8.tgz#49348387983075705fe8f4e02fb67f7daaec4934" - integrity sha512-S78QIYirQcUoo6UJZx9CSP0O2ix9IaeAXwQi26Rhr/+mg7qqPy8TzaxHSUut7eGjL8WmLccT7/MXf304WjqHcA== - -"@types/history-5@npm:@types/history@4.7.8": - version "4.7.8" - resolved "https://registry.yarnpkg.com/@types/history/-/history-4.7.8.tgz#49348387983075705fe8f4e02fb67f7daaec4934" - integrity sha512-S78QIYirQcUoo6UJZx9CSP0O2ix9IaeAXwQi26Rhr/+mg7qqPy8TzaxHSUut7eGjL8WmLccT7/MXf304WjqHcA== - -"@types/history@*": +"@types/history-4@npm:@types/history@4.7.8", "@types/history-5@npm:@types/history@4.7.8", "@types/history@*": version "4.7.8" resolved "https://registry.yarnpkg.com/@types/history/-/history-4.7.8.tgz#49348387983075705fe8f4e02fb67f7daaec4934" integrity sha512-S78QIYirQcUoo6UJZx9CSP0O2ix9IaeAXwQi26Rhr/+mg7qqPy8TzaxHSUut7eGjL8WmLccT7/MXf304WjqHcA== @@ -9785,15 +9775,7 @@ "@types/history" "^3" "@types/react" "*" -"@types/react-router-4@npm:@types/react-router@5.1.14": - version "5.1.14" - resolved "https://registry.yarnpkg.com/@types/react-router/-/react-router-5.1.14.tgz#e0442f4eb4c446541ad7435d44a97f8fe6df40da" - integrity sha512-LAJpqYUaCTMT2anZheoidiIymt8MuX286zoVFPM3DVb23aQBH0mAkFvzpd4LKqiolV8bBtZWT5Qp7hClCNDENw== - dependencies: - "@types/history" "*" - "@types/react" "*" - -"@types/react-router-5@npm:@types/react-router@5.1.14": +"@types/react-router-4@npm:@types/react-router@5.1.14", "@types/react-router-5@npm:@types/react-router@5.1.14": version "5.1.14" resolved "https://registry.yarnpkg.com/@types/react-router/-/react-router-5.1.14.tgz#e0442f4eb4c446541ad7435d44a97f8fe6df40da" integrity sha512-LAJpqYUaCTMT2anZheoidiIymt8MuX286zoVFPM3DVb23aQBH0mAkFvzpd4LKqiolV8bBtZWT5Qp7hClCNDENw== @@ -28990,7 +28972,7 @@ react-is@^18.0.0: dependencies: "@remix-run/router" "1.0.2" -"react-router-6@npm:react-router@6.3.0": +"react-router-6@npm:react-router@6.3.0", react-router@6.3.0: version "6.3.0" resolved "https://registry.yarnpkg.com/react-router/-/react-router-6.3.0.tgz#3970cc64b4cb4eae0c1ea5203a80334fdd175557" integrity sha512-7Wh1DzVQ+tlFjkeo+ujvjSqSJmkt1+8JO+T5xklPlgrh70y7ogx75ODRW0ThWhY7S+6yEDks8TYrtQe/aoboBQ== @@ -29005,13 +28987,6 @@ react-router-dom@^6.2.2: history "^5.2.0" react-router "6.3.0" -react-router@6.3.0: - version "6.3.0" - resolved "https://registry.yarnpkg.com/react-router/-/react-router-6.3.0.tgz#3970cc64b4cb4eae0c1ea5203a80334fdd175557" - integrity sha512-7Wh1DzVQ+tlFjkeo+ujvjSqSJmkt1+8JO+T5xklPlgrh70y7ogx75ODRW0ThWhY7S+6yEDks8TYrtQe/aoboBQ== - dependencies: - history "^5.2.0" - react@^18.0.0: version "18.0.0" resolved "https://registry.yarnpkg.com/react/-/react-18.0.0.tgz#b468736d1f4a5891f38585ba8e8fb29f91c3cb96" @@ -31544,16 +31519,7 @@ string-template@~0.2.1: resolved "https://registry.yarnpkg.com/string-template/-/string-template-0.2.1.tgz#42932e598a352d01fc22ec3367d9d84eec6c9add" integrity sha1-QpMuWYo1LQH8IuwzZ9nYTuxsmt0= -"string-width-cjs@npm:string-width@^4.2.0": - version "4.2.3" - resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" - integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== - dependencies: - emoji-regex "^8.0.0" - is-fullwidth-code-point "^3.0.0" - strip-ansi "^6.0.1" - -string-width@4.2.3, string-width@^4.2.0, string-width@^4.2.2, string-width@^4.2.3: +"string-width-cjs@npm:string-width@^4.2.0", string-width@4.2.3, string-width@^4.2.0, string-width@^4.2.2, string-width@^4.2.3: version "4.2.3" resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== @@ -31665,14 +31631,7 @@ stringify-object@^3.2.1: is-obj "^1.0.1" is-regexp "^1.0.0" -"strip-ansi-cjs@npm:strip-ansi@^6.0.1": - version "6.0.1" - resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" - integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== - dependencies: - ansi-regex "^5.0.1" - -strip-ansi@6.0.1, strip-ansi@^6.0.0, strip-ansi@^6.0.1: +"strip-ansi-cjs@npm:strip-ansi@^6.0.1", strip-ansi@6.0.1, strip-ansi@^6.0.0, strip-ansi@^6.0.1: version "6.0.1" resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== @@ -34765,16 +34724,7 @@ workerpool@^6.4.0: resolved "https://registry.yarnpkg.com/workerpool/-/workerpool-6.4.0.tgz#f8d5cfb45fde32fa3b7af72ad617c3369567a462" integrity sha512-i3KR1mQMNwY2wx20ozq2EjISGtQWDIfV56We+yGJ5yDs8jTwQiLLaqHlkBHITlCuJnYlVRmXegxFxZg7gqI++A== -"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0": - version "7.0.0" - resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" - integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== - dependencies: - ansi-styles "^4.0.0" - string-width "^4.1.0" - strip-ansi "^6.0.0" - -wrap-ansi@7.0.0, wrap-ansi@^7.0.0: +"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0", wrap-ansi@7.0.0, wrap-ansi@^7.0.0: version "7.0.0" resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== From ae5dc1f7ff2058fa5e93e26ce97a26488609565a Mon Sep 17 00:00:00 2001 From: nicohrubec Date: Wed, 10 Jul 2024 13:34:29 +0200 Subject: [PATCH 10/60] Update yarn.lock --- yarn.lock | 70 ++++++++++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 64 insertions(+), 6 deletions(-) diff --git a/yarn.lock b/yarn.lock index a423fa79953f..252d00bdcd99 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6960,6 +6960,14 @@ "@opentelemetry/semantic-conventions" "^1.22.0" "@types/mysql" "2.15.22" +"@opentelemetry/instrumentation-nestjs-core@0.38.0": + version "0.38.0" + resolved "https://registry.yarnpkg.com/@opentelemetry/instrumentation-nestjs-core/-/instrumentation-nestjs-core-0.38.0.tgz#d4296936723f1dfbd11747a84a87d17a3da0bc74" + integrity sha512-M381Df1dM8aqihZz2yK+ugvMFK5vlHG/835dc67Sx2hH4pQEQYDA2PpFPTgc9AYYOydQaj7ClFQunESimjXDgg== + dependencies: + "@opentelemetry/instrumentation" "^0.52.0" + "@opentelemetry/semantic-conventions" "^1.23.0" + "@opentelemetry/instrumentation-nestjs-core@0.39.0": version "0.39.0" resolved "https://registry.yarnpkg.com/@opentelemetry/instrumentation-nestjs-core/-/instrumentation-nestjs-core-0.39.0.tgz#733fef4306c796951d7ea1951b45f9df0aed234d" @@ -9454,7 +9462,17 @@ dependencies: "@types/unist" "*" -"@types/history-4@npm:@types/history@4.7.8", "@types/history-5@npm:@types/history@4.7.8", "@types/history@*": +"@types/history-4@npm:@types/history@4.7.8": + version "4.7.8" + resolved "https://registry.yarnpkg.com/@types/history/-/history-4.7.8.tgz#49348387983075705fe8f4e02fb67f7daaec4934" + integrity sha512-S78QIYirQcUoo6UJZx9CSP0O2ix9IaeAXwQi26Rhr/+mg7qqPy8TzaxHSUut7eGjL8WmLccT7/MXf304WjqHcA== + +"@types/history-5@npm:@types/history@4.7.8": + version "4.7.8" + resolved "https://registry.yarnpkg.com/@types/history/-/history-4.7.8.tgz#49348387983075705fe8f4e02fb67f7daaec4934" + integrity sha512-S78QIYirQcUoo6UJZx9CSP0O2ix9IaeAXwQi26Rhr/+mg7qqPy8TzaxHSUut7eGjL8WmLccT7/MXf304WjqHcA== + +"@types/history@*": version "4.7.8" resolved "https://registry.yarnpkg.com/@types/history/-/history-4.7.8.tgz#49348387983075705fe8f4e02fb67f7daaec4934" integrity sha512-S78QIYirQcUoo6UJZx9CSP0O2ix9IaeAXwQi26Rhr/+mg7qqPy8TzaxHSUut7eGjL8WmLccT7/MXf304WjqHcA== @@ -9775,7 +9793,15 @@ "@types/history" "^3" "@types/react" "*" -"@types/react-router-4@npm:@types/react-router@5.1.14", "@types/react-router-5@npm:@types/react-router@5.1.14": +"@types/react-router-4@npm:@types/react-router@5.1.14": + version "5.1.14" + resolved "https://registry.yarnpkg.com/@types/react-router/-/react-router-5.1.14.tgz#e0442f4eb4c446541ad7435d44a97f8fe6df40da" + integrity sha512-LAJpqYUaCTMT2anZheoidiIymt8MuX286zoVFPM3DVb23aQBH0mAkFvzpd4LKqiolV8bBtZWT5Qp7hClCNDENw== + dependencies: + "@types/history" "*" + "@types/react" "*" + +"@types/react-router-5@npm:@types/react-router@5.1.14": version "5.1.14" resolved "https://registry.yarnpkg.com/@types/react-router/-/react-router-5.1.14.tgz#e0442f4eb4c446541ad7435d44a97f8fe6df40da" integrity sha512-LAJpqYUaCTMT2anZheoidiIymt8MuX286zoVFPM3DVb23aQBH0mAkFvzpd4LKqiolV8bBtZWT5Qp7hClCNDENw== @@ -28972,7 +28998,7 @@ react-is@^18.0.0: dependencies: "@remix-run/router" "1.0.2" -"react-router-6@npm:react-router@6.3.0", react-router@6.3.0: +"react-router-6@npm:react-router@6.3.0": version "6.3.0" resolved "https://registry.yarnpkg.com/react-router/-/react-router-6.3.0.tgz#3970cc64b4cb4eae0c1ea5203a80334fdd175557" integrity sha512-7Wh1DzVQ+tlFjkeo+ujvjSqSJmkt1+8JO+T5xklPlgrh70y7ogx75ODRW0ThWhY7S+6yEDks8TYrtQe/aoboBQ== @@ -28987,6 +29013,13 @@ react-router-dom@^6.2.2: history "^5.2.0" react-router "6.3.0" +react-router@6.3.0: + version "6.3.0" + resolved "https://registry.yarnpkg.com/react-router/-/react-router-6.3.0.tgz#3970cc64b4cb4eae0c1ea5203a80334fdd175557" + integrity sha512-7Wh1DzVQ+tlFjkeo+ujvjSqSJmkt1+8JO+T5xklPlgrh70y7ogx75ODRW0ThWhY7S+6yEDks8TYrtQe/aoboBQ== + dependencies: + history "^5.2.0" + react@^18.0.0: version "18.0.0" resolved "https://registry.yarnpkg.com/react/-/react-18.0.0.tgz#b468736d1f4a5891f38585ba8e8fb29f91c3cb96" @@ -31519,7 +31552,16 @@ string-template@~0.2.1: resolved "https://registry.yarnpkg.com/string-template/-/string-template-0.2.1.tgz#42932e598a352d01fc22ec3367d9d84eec6c9add" integrity sha1-QpMuWYo1LQH8IuwzZ9nYTuxsmt0= -"string-width-cjs@npm:string-width@^4.2.0", string-width@4.2.3, string-width@^4.2.0, string-width@^4.2.2, string-width@^4.2.3: +"string-width-cjs@npm:string-width@^4.2.0": + version "4.2.3" + resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" + integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== + dependencies: + emoji-regex "^8.0.0" + is-fullwidth-code-point "^3.0.0" + strip-ansi "^6.0.1" + +string-width@4.2.3, string-width@^4.2.0, string-width@^4.2.2, string-width@^4.2.3: version "4.2.3" resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== @@ -31631,7 +31673,14 @@ stringify-object@^3.2.1: is-obj "^1.0.1" is-regexp "^1.0.0" -"strip-ansi-cjs@npm:strip-ansi@^6.0.1", strip-ansi@6.0.1, strip-ansi@^6.0.0, strip-ansi@^6.0.1: +"strip-ansi-cjs@npm:strip-ansi@^6.0.1": + version "6.0.1" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" + integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== + dependencies: + ansi-regex "^5.0.1" + +strip-ansi@6.0.1, strip-ansi@^6.0.0, strip-ansi@^6.0.1: version "6.0.1" resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== @@ -34724,7 +34773,16 @@ workerpool@^6.4.0: resolved "https://registry.yarnpkg.com/workerpool/-/workerpool-6.4.0.tgz#f8d5cfb45fde32fa3b7af72ad617c3369567a462" integrity sha512-i3KR1mQMNwY2wx20ozq2EjISGtQWDIfV56We+yGJ5yDs8jTwQiLLaqHlkBHITlCuJnYlVRmXegxFxZg7gqI++A== -"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0", wrap-ansi@7.0.0, wrap-ansi@^7.0.0: +"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0": + version "7.0.0" + resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" + integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== + dependencies: + ansi-styles "^4.0.0" + string-width "^4.1.0" + strip-ansi "^6.0.0" + +wrap-ansi@7.0.0, wrap-ansi@^7.0.0: version "7.0.0" resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== From fd1e084b88ef2b17bb7b396420f3bbaab17b0e40 Mon Sep 17 00:00:00 2001 From: nicohrubec Date: Wed, 10 Jul 2024 13:50:58 +0200 Subject: [PATCH 11/60] Revert yarn.lock --- yarn.lock | 70 +++++-------------------------------------------------- 1 file changed, 6 insertions(+), 64 deletions(-) diff --git a/yarn.lock b/yarn.lock index 252d00bdcd99..a423fa79953f 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6960,14 +6960,6 @@ "@opentelemetry/semantic-conventions" "^1.22.0" "@types/mysql" "2.15.22" -"@opentelemetry/instrumentation-nestjs-core@0.38.0": - version "0.38.0" - resolved "https://registry.yarnpkg.com/@opentelemetry/instrumentation-nestjs-core/-/instrumentation-nestjs-core-0.38.0.tgz#d4296936723f1dfbd11747a84a87d17a3da0bc74" - integrity sha512-M381Df1dM8aqihZz2yK+ugvMFK5vlHG/835dc67Sx2hH4pQEQYDA2PpFPTgc9AYYOydQaj7ClFQunESimjXDgg== - dependencies: - "@opentelemetry/instrumentation" "^0.52.0" - "@opentelemetry/semantic-conventions" "^1.23.0" - "@opentelemetry/instrumentation-nestjs-core@0.39.0": version "0.39.0" resolved "https://registry.yarnpkg.com/@opentelemetry/instrumentation-nestjs-core/-/instrumentation-nestjs-core-0.39.0.tgz#733fef4306c796951d7ea1951b45f9df0aed234d" @@ -9462,17 +9454,7 @@ dependencies: "@types/unist" "*" -"@types/history-4@npm:@types/history@4.7.8": - version "4.7.8" - resolved "https://registry.yarnpkg.com/@types/history/-/history-4.7.8.tgz#49348387983075705fe8f4e02fb67f7daaec4934" - integrity sha512-S78QIYirQcUoo6UJZx9CSP0O2ix9IaeAXwQi26Rhr/+mg7qqPy8TzaxHSUut7eGjL8WmLccT7/MXf304WjqHcA== - -"@types/history-5@npm:@types/history@4.7.8": - version "4.7.8" - resolved "https://registry.yarnpkg.com/@types/history/-/history-4.7.8.tgz#49348387983075705fe8f4e02fb67f7daaec4934" - integrity sha512-S78QIYirQcUoo6UJZx9CSP0O2ix9IaeAXwQi26Rhr/+mg7qqPy8TzaxHSUut7eGjL8WmLccT7/MXf304WjqHcA== - -"@types/history@*": +"@types/history-4@npm:@types/history@4.7.8", "@types/history-5@npm:@types/history@4.7.8", "@types/history@*": version "4.7.8" resolved "https://registry.yarnpkg.com/@types/history/-/history-4.7.8.tgz#49348387983075705fe8f4e02fb67f7daaec4934" integrity sha512-S78QIYirQcUoo6UJZx9CSP0O2ix9IaeAXwQi26Rhr/+mg7qqPy8TzaxHSUut7eGjL8WmLccT7/MXf304WjqHcA== @@ -9793,15 +9775,7 @@ "@types/history" "^3" "@types/react" "*" -"@types/react-router-4@npm:@types/react-router@5.1.14": - version "5.1.14" - resolved "https://registry.yarnpkg.com/@types/react-router/-/react-router-5.1.14.tgz#e0442f4eb4c446541ad7435d44a97f8fe6df40da" - integrity sha512-LAJpqYUaCTMT2anZheoidiIymt8MuX286zoVFPM3DVb23aQBH0mAkFvzpd4LKqiolV8bBtZWT5Qp7hClCNDENw== - dependencies: - "@types/history" "*" - "@types/react" "*" - -"@types/react-router-5@npm:@types/react-router@5.1.14": +"@types/react-router-4@npm:@types/react-router@5.1.14", "@types/react-router-5@npm:@types/react-router@5.1.14": version "5.1.14" resolved "https://registry.yarnpkg.com/@types/react-router/-/react-router-5.1.14.tgz#e0442f4eb4c446541ad7435d44a97f8fe6df40da" integrity sha512-LAJpqYUaCTMT2anZheoidiIymt8MuX286zoVFPM3DVb23aQBH0mAkFvzpd4LKqiolV8bBtZWT5Qp7hClCNDENw== @@ -28998,7 +28972,7 @@ react-is@^18.0.0: dependencies: "@remix-run/router" "1.0.2" -"react-router-6@npm:react-router@6.3.0": +"react-router-6@npm:react-router@6.3.0", react-router@6.3.0: version "6.3.0" resolved "https://registry.yarnpkg.com/react-router/-/react-router-6.3.0.tgz#3970cc64b4cb4eae0c1ea5203a80334fdd175557" integrity sha512-7Wh1DzVQ+tlFjkeo+ujvjSqSJmkt1+8JO+T5xklPlgrh70y7ogx75ODRW0ThWhY7S+6yEDks8TYrtQe/aoboBQ== @@ -29013,13 +28987,6 @@ react-router-dom@^6.2.2: history "^5.2.0" react-router "6.3.0" -react-router@6.3.0: - version "6.3.0" - resolved "https://registry.yarnpkg.com/react-router/-/react-router-6.3.0.tgz#3970cc64b4cb4eae0c1ea5203a80334fdd175557" - integrity sha512-7Wh1DzVQ+tlFjkeo+ujvjSqSJmkt1+8JO+T5xklPlgrh70y7ogx75ODRW0ThWhY7S+6yEDks8TYrtQe/aoboBQ== - dependencies: - history "^5.2.0" - react@^18.0.0: version "18.0.0" resolved "https://registry.yarnpkg.com/react/-/react-18.0.0.tgz#b468736d1f4a5891f38585ba8e8fb29f91c3cb96" @@ -31552,16 +31519,7 @@ string-template@~0.2.1: resolved "https://registry.yarnpkg.com/string-template/-/string-template-0.2.1.tgz#42932e598a352d01fc22ec3367d9d84eec6c9add" integrity sha1-QpMuWYo1LQH8IuwzZ9nYTuxsmt0= -"string-width-cjs@npm:string-width@^4.2.0": - version "4.2.3" - resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" - integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== - dependencies: - emoji-regex "^8.0.0" - is-fullwidth-code-point "^3.0.0" - strip-ansi "^6.0.1" - -string-width@4.2.3, string-width@^4.2.0, string-width@^4.2.2, string-width@^4.2.3: +"string-width-cjs@npm:string-width@^4.2.0", string-width@4.2.3, string-width@^4.2.0, string-width@^4.2.2, string-width@^4.2.3: version "4.2.3" resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== @@ -31673,14 +31631,7 @@ stringify-object@^3.2.1: is-obj "^1.0.1" is-regexp "^1.0.0" -"strip-ansi-cjs@npm:strip-ansi@^6.0.1": - version "6.0.1" - resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" - integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== - dependencies: - ansi-regex "^5.0.1" - -strip-ansi@6.0.1, strip-ansi@^6.0.0, strip-ansi@^6.0.1: +"strip-ansi-cjs@npm:strip-ansi@^6.0.1", strip-ansi@6.0.1, strip-ansi@^6.0.0, strip-ansi@^6.0.1: version "6.0.1" resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== @@ -34773,16 +34724,7 @@ workerpool@^6.4.0: resolved "https://registry.yarnpkg.com/workerpool/-/workerpool-6.4.0.tgz#f8d5cfb45fde32fa3b7af72ad617c3369567a462" integrity sha512-i3KR1mQMNwY2wx20ozq2EjISGtQWDIfV56We+yGJ5yDs8jTwQiLLaqHlkBHITlCuJnYlVRmXegxFxZg7gqI++A== -"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0": - version "7.0.0" - resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" - integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== - dependencies: - ansi-styles "^4.0.0" - string-width "^4.1.0" - strip-ansi "^6.0.0" - -wrap-ansi@7.0.0, wrap-ansi@^7.0.0: +"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0", wrap-ansi@7.0.0, wrap-ansi@^7.0.0: version "7.0.0" resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== From 5a134ba5702fc03391365d279092dd3b75991ab2 Mon Sep 17 00:00:00 2001 From: nicohrubec Date: Wed, 10 Jul 2024 13:52:20 +0200 Subject: [PATCH 12/60] Update dependency --- packages/nestjs/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/nestjs/package.json b/packages/nestjs/package.json index 0d24ceed2fd4..bf2e539165aa 100644 --- a/packages/nestjs/package.json +++ b/packages/nestjs/package.json @@ -43,7 +43,7 @@ "@sentry/node": "8.16.0", "@sentry/types": "8.16.0", "@sentry/utils": "8.16.0", - "@opentelemetry/instrumentation-nestjs-core": "0.38.0" + "@opentelemetry/instrumentation-nestjs-core": "0.39.0" }, "scripts": { "build": "run-p build:transpile build:types", From dbf33fcbbbdd68c00cad7c57e231e105a2a63f0a Mon Sep 17 00:00:00 2001 From: nicohrubec Date: Thu, 11 Jul 2024 15:29:58 +0200 Subject: [PATCH 13/60] Bsaeline --- .../test-applications/nestjs/src/main.ts | 7 +- packages/nestjs/package.json | 8 +++ packages/nestjs/src/setup.ts | 68 ++++++++++++------- yarn.lock | 26 +++++++ 4 files changed, 82 insertions(+), 27 deletions(-) diff --git a/dev-packages/e2e-tests/test-applications/nestjs/src/main.ts b/dev-packages/e2e-tests/test-applications/nestjs/src/main.ts index c2682662154d..1e1ebb6e3117 100644 --- a/dev-packages/e2e-tests/test-applications/nestjs/src/main.ts +++ b/dev-packages/e2e-tests/test-applications/nestjs/src/main.ts @@ -2,7 +2,7 @@ import './instrument'; // Import other modules -import { BaseExceptionFilter, HttpAdapterHost, NestFactory } from '@nestjs/core'; +import { NestFactory } from '@nestjs/core'; import * as Sentry from '@sentry/nestjs'; import { AppModule1, AppModule2 } from './app.module'; @@ -10,10 +10,9 @@ const app1Port = 3030; const app2Port = 3040; async function bootstrap() { - const app1 = await NestFactory.create(AppModule1); + let app1 = await NestFactory.create(AppModule1); - const { httpAdapter } = app1.get(HttpAdapterHost); - Sentry.setupNestErrorHandler(app1, new BaseExceptionFilter(httpAdapter)); + Sentry.setupNestErrorHandler(app1); await app1.listen(app1Port); diff --git a/packages/nestjs/package.json b/packages/nestjs/package.json index 06c5e9f39fea..acf758d4e9d3 100644 --- a/packages/nestjs/package.json +++ b/packages/nestjs/package.json @@ -45,6 +45,14 @@ "@sentry/utils": "8.17.0", "@opentelemetry/instrumentation-nestjs-core": "0.39.0" }, + "devDependencies": { + "@nestjs/core": "^10.3.10", + "@nestjs/common": "^10.3.10" + }, + "peerDependencies": { + "@nestjs/core": "^10.3.10", + "@nestjs/common": "^10.3.10" + }, "scripts": { "build": "run-p build:transpile build:types", "build:dev": "yarn build", diff --git a/packages/nestjs/src/setup.ts b/packages/nestjs/src/setup.ts index 52dc50f52822..d8cd406e5015 100644 --- a/packages/nestjs/src/setup.ts +++ b/packages/nestjs/src/setup.ts @@ -1,3 +1,5 @@ +import type { ArgumentsHost } from '@nestjs/common'; +import { DiscoveryService } from '@nestjs/core'; import { NestInstrumentation } from '@opentelemetry/instrumentation-nestjs-core'; import { SEMANTIC_ATTRIBUTE_SENTRY_OP, @@ -40,9 +42,23 @@ interface MinimalNestJsApp { // eslint-disable-next-line @typescript-eslint/no-explicit-any intercept: (context: MinimalNestJsExecutionContext, next: { handle: () => any }) => any; }) => void; + + get: (type: new (...args: any[]) => T) => T; +} + +interface ExceptionFilterInstance { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + catch: (exception: unknown, host: ArgumentsHost) => any; +} + +interface Provider { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + metatype: any; + instance: ExceptionFilterInstance; } const INTEGRATION_NAME = 'Nest'; +const CATCH_WATERMARK = '__catch__'; export const instrumentNest = generateInstrumentOnce(INTEGRATION_NAME, () => new NestInstrumentation()); @@ -65,7 +81,7 @@ export const nestIntegration = defineIntegration(_nestIntegration); /** * Setup an error handler for Nest. */ -export function setupNestErrorHandler(app: MinimalNestJsApp, baseFilter: NestJsErrorFilter): void { +export function setupNestErrorHandler(app: MinimalNestJsApp): void { // Sadly, NestInstrumentation has no requestHook, so we need to add the attributes here // We register this hook in this method, because if we register it in the integration `setup`, // it would always run even for users that are not even using Nest.js @@ -95,28 +111,7 @@ export function setupNestErrorHandler(app: MinimalNestJsApp, baseFilter: NestJsE }, }); - const wrappedFilter = new Proxy(baseFilter, { - get(target, prop, receiver) { - if (prop === 'catch') { - const originalCatch = Reflect.get(target, prop, receiver); - - return (exception: unknown, host: unknown) => { - const status_code = (exception as { status?: number }).status; - - // don't report expected errors - if (status_code !== undefined && status_code >= 400 && status_code < 500) { - return originalCatch.apply(target, [exception, host]); - } - - captureException(exception); - return originalCatch.apply(target, [exception, host]); - }; - } - return Reflect.get(target, prop, receiver); - }, - }); - - app.useGlobalFilters(wrappedFilter); + checkinExceptionFilters(app); } function addNestSpanAttributes(span: Span): void { @@ -135,3 +130,30 @@ function addNestSpanAttributes(span: Span): void { [SEMANTIC_ATTRIBUTE_SENTRY_OP]: `${type}.nestjs`, }); } + +function checkinExceptionFilters(app: MinimalNestJsApp): void { + const discoveryService = app.get(DiscoveryService); + const providers = discoveryService.getProviders() as Provider[]; + const exceptionFilters = providers.filter( + ({ metatype }) => metatype && Reflect.getMetadata(CATCH_WATERMARK, metatype), + ); + + exceptionFilters.map(mod => { + const instance = mod.instance; + const originalCatch = instance.catch; + + instance.catch = function (exception: unknown, host) { + const status_code = (exception as { status?: number }).status; + + // don't report expected errors + if (status_code !== undefined && status_code >= 400 && status_code < 500) { + return originalCatch.apply(target, [exception, host]); + } + + captureException(exception); + return originalCatch.apply(this, [exception, host]); + }; + + return mod; + }); +} diff --git a/yarn.lock b/yarn.lock index a31caa49a1ac..b7db13e3779a 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5882,6 +5882,15 @@ semver "^7.3.5" tar "^6.1.11" +"@nestjs/common@^10.3.10": + version "10.3.10" + resolved "https://registry.yarnpkg.com/@nestjs/common/-/common-10.3.10.tgz#d8825d55a50a04e33080c9188e6a5b03235d19f2" + integrity sha512-H8k0jZtxk1IdtErGDmxFRy0PfcOAUg41Prrqpx76DQusGGJjsaovs1zjXVD1rZWaVYchfT1uczJ6L4Kio10VNg== + dependencies: + uid "2.0.2" + iterare "1.2.1" + tslib "2.6.3" + "@nestjs/common@^10.3.7": version "10.3.7" resolved "https://registry.yarnpkg.com/@nestjs/common/-/common-10.3.7.tgz#38ab5ff92277cf1f26f4749c264524e76962cfff" @@ -5891,6 +5900,18 @@ iterare "1.2.1" tslib "2.6.2" +"@nestjs/core@^10.3.10": + version "10.3.10" + resolved "https://registry.yarnpkg.com/@nestjs/core/-/core-10.3.10.tgz#508090c3ca36488a8e24a9e5939c2f37426e48f4" + integrity sha512-ZbQ4jovQyzHtCGCrzK5NdtW1SYO2fHSsgSY1+/9WdruYCUra+JDkWEXgZ4M3Hv480Dl3OXehAmY1wCOojeMyMQ== + dependencies: + uid "2.0.2" + "@nuxtjs/opencollective" "0.3.2" + fast-safe-stringify "2.1.1" + iterare "1.2.1" + path-to-regexp "3.2.0" + tslib "2.6.3" + "@nestjs/core@^10.3.3": version "10.3.3" resolved "https://registry.yarnpkg.com/@nestjs/core/-/core-10.3.3.tgz#f957068ddda59252b7c36fcdb07a0fb323b52bcf" @@ -32677,6 +32698,11 @@ tslib@2.6.2, tslib@^2.6.2: resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.6.2.tgz#703ac29425e7b37cd6fd456e92404d46d1f3e4ae" integrity sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q== +tslib@2.6.3: + version "2.6.3" + resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.6.3.tgz#0438f810ad7a9edcde7a241c3d80db693c8cbfe0" + integrity sha512-xNvxJEOUiWPGhUuUdQgAJPKOOJfGnIyKySOc09XkKsgdUV/3E2zvwZYdejjmRgPCgcym1juLH3226yA7sEFJKQ== + tslib@^1.11.1, tslib@^1.8.1, tslib@^1.9.0: version "1.14.1" resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00" From 98363a3b19f0388e4cc5ae083dea334c868b7270 Mon Sep 17 00:00:00 2001 From: nicohrubec Date: Thu, 11 Jul 2024 15:31:54 +0200 Subject: [PATCH 14/60] . --- packages/nestjs/src/setup.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/nestjs/src/setup.ts b/packages/nestjs/src/setup.ts index d8cd406e5015..55065880c3c1 100644 --- a/packages/nestjs/src/setup.ts +++ b/packages/nestjs/src/setup.ts @@ -147,7 +147,7 @@ function checkinExceptionFilters(app: MinimalNestJsApp): void { // don't report expected errors if (status_code !== undefined && status_code >= 400 && status_code < 500) { - return originalCatch.apply(target, [exception, host]); + return originalCatch.apply(this, [exception, host]); } captureException(exception); From 75e8da303a729ae223d933311dfe37faf7e5c646 Mon Sep 17 00:00:00 2001 From: nicohrubec Date: Thu, 11 Jul 2024 16:29:07 +0200 Subject: [PATCH 15/60] Fix tests --- .../test-applications/nestjs/tests/errors.test.ts | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/dev-packages/e2e-tests/test-applications/nestjs/tests/errors.test.ts b/dev-packages/e2e-tests/test-applications/nestjs/tests/errors.test.ts index ffb48f4e5e70..885a0aa230c3 100644 --- a/dev-packages/e2e-tests/test-applications/nestjs/tests/errors.test.ts +++ b/dev-packages/e2e-tests/test-applications/nestjs/tests/errors.test.ts @@ -55,13 +55,17 @@ test('Does not send expected exception to Sentry', async ({ baseURL }) => { }); test('Does not handle expected exception if exception is thrown in module', async ({ baseURL }) => { + let errorEventOccurred = false; + const errorEventPromise = waitForError('nestjs', event => { return !event.type && event.exception?.values?.[0]?.value === 'Something went wrong in the test module!'; }); const response = await fetch(`${baseURL}/test-module`); - expect(response.status).toBe(500); // should be 400 + expect(response.status).toBe(400); // should be 400 + // TODO: needs to be adjusted if that works now + /* // should never arrive, but does because the exception is not handled properly const errorEvent = await errorEventPromise; @@ -81,4 +85,6 @@ test('Does not handle expected exception if exception is thrown in module', asyn trace_id: expect.any(String), span_id: expect.any(String), }); + + */ }); From eab3357c540ff5d2e5d74dfa702fd76726fda2e2 Mon Sep 17 00:00:00 2001 From: nicohrubec Date: Thu, 11 Jul 2024 16:40:07 +0200 Subject: [PATCH 16/60] Update --- .../nestjs/tests/errors.test.ts | 38 +++++++------------ 1 file changed, 14 insertions(+), 24 deletions(-) diff --git a/dev-packages/e2e-tests/test-applications/nestjs/tests/errors.test.ts b/dev-packages/e2e-tests/test-applications/nestjs/tests/errors.test.ts index 885a0aa230c3..8bbcfa16ce07 100644 --- a/dev-packages/e2e-tests/test-applications/nestjs/tests/errors.test.ts +++ b/dev-packages/e2e-tests/test-applications/nestjs/tests/errors.test.ts @@ -54,37 +54,27 @@ test('Does not send expected exception to Sentry', async ({ baseURL }) => { expect(errorEventOccurred).toBe(false); }); -test('Does not handle expected exception if exception is thrown in module', async ({ baseURL }) => { +test('Handles exception correctly and does not send to Sentry if exception is thrown in module', async ({ baseURL }) => { let errorEventOccurred = false; - const errorEventPromise = waitForError('nestjs', event => { - return !event.type && event.exception?.values?.[0]?.value === 'Something went wrong in the test module!'; + waitForError('nestjs', event => { + if (!event.type && event.exception?.values?.[0]?.value === 'Something went wrong in the test module!') { + errorEventOccurred = true; + } + + return event?.transaction === 'GET /test-module'; + }); + + const transactionEventPromise = waitForTransaction('nestjs', transactionEvent => { + return transactionEvent?.transaction === 'GET /test-module'; }); const response = await fetch(`${baseURL}/test-module`); expect(response.status).toBe(400); // should be 400 - // TODO: needs to be adjusted if that works now - /* - // should never arrive, but does because the exception is not handled properly - const errorEvent = await errorEventPromise; - - expect(errorEvent.exception?.values).toHaveLength(1); - expect(errorEvent.exception?.values?.[0]?.value).toBe('Something went wrong in the test module!'); - - expect(errorEvent.request).toEqual({ - method: 'GET', - cookies: {}, - headers: expect.any(Object), - url: 'http://localhost:3030/test-module', - }); - - expect(errorEvent.transaction).toEqual('GET /test-module'); + await transactionEventPromise; - expect(errorEvent.contexts?.trace).toEqual({ - trace_id: expect.any(String), - span_id: expect.any(String), - }); + await new Promise(resolve => setTimeout(resolve, 10000)); - */ + expect(errorEventOccurred).toBe(false); }); From 683dce74586c302ca1688a1f209f0b80d6ee0980 Mon Sep 17 00:00:00 2001 From: nicohrubec Date: Thu, 11 Jul 2024 16:41:09 +0200 Subject: [PATCH 17/60] . --- .../e2e-tests/test-applications/nestjs/tests/errors.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dev-packages/e2e-tests/test-applications/nestjs/tests/errors.test.ts b/dev-packages/e2e-tests/test-applications/nestjs/tests/errors.test.ts index 8bbcfa16ce07..fd63494dea76 100644 --- a/dev-packages/e2e-tests/test-applications/nestjs/tests/errors.test.ts +++ b/dev-packages/e2e-tests/test-applications/nestjs/tests/errors.test.ts @@ -70,7 +70,7 @@ test('Handles exception correctly and does not send to Sentry if exception is th }); const response = await fetch(`${baseURL}/test-module`); - expect(response.status).toBe(400); // should be 400 + expect(response.status).toBe(400); await transactionEventPromise; From ff07588a3292946b5d6316c49ffcc3947c3691aa Mon Sep 17 00:00:00 2001 From: nicohrubec Date: Thu, 11 Jul 2024 16:57:06 +0200 Subject: [PATCH 18/60] Updates --- .../test-applications/nestjs/src/main.ts | 7 +++--- .../nestjs/tests/errors.test.ts | 2 ++ packages/nestjs/src/setup.ts | 25 ++++++++++++++++++- 3 files changed, 30 insertions(+), 4 deletions(-) diff --git a/dev-packages/e2e-tests/test-applications/nestjs/src/main.ts b/dev-packages/e2e-tests/test-applications/nestjs/src/main.ts index 1e1ebb6e3117..c2682662154d 100644 --- a/dev-packages/e2e-tests/test-applications/nestjs/src/main.ts +++ b/dev-packages/e2e-tests/test-applications/nestjs/src/main.ts @@ -2,7 +2,7 @@ import './instrument'; // Import other modules -import { NestFactory } from '@nestjs/core'; +import { BaseExceptionFilter, HttpAdapterHost, NestFactory } from '@nestjs/core'; import * as Sentry from '@sentry/nestjs'; import { AppModule1, AppModule2 } from './app.module'; @@ -10,9 +10,10 @@ const app1Port = 3030; const app2Port = 3040; async function bootstrap() { - let app1 = await NestFactory.create(AppModule1); + const app1 = await NestFactory.create(AppModule1); - Sentry.setupNestErrorHandler(app1); + const { httpAdapter } = app1.get(HttpAdapterHost); + Sentry.setupNestErrorHandler(app1, new BaseExceptionFilter(httpAdapter)); await app1.listen(app1Port); diff --git a/dev-packages/e2e-tests/test-applications/nestjs/tests/errors.test.ts b/dev-packages/e2e-tests/test-applications/nestjs/tests/errors.test.ts index fd63494dea76..f66d5a3ff35f 100644 --- a/dev-packages/e2e-tests/test-applications/nestjs/tests/errors.test.ts +++ b/dev-packages/e2e-tests/test-applications/nestjs/tests/errors.test.ts @@ -69,8 +69,10 @@ test('Handles exception correctly and does not send to Sentry if exception is th return transactionEvent?.transaction === 'GET /test-module'; }); + console.log("fetch"); const response = await fetch(`${baseURL}/test-module`); expect(response.status).toBe(400); + console.log("waiting for response"); await transactionEventPromise; diff --git a/packages/nestjs/src/setup.ts b/packages/nestjs/src/setup.ts index 55065880c3c1..247da2ff4d84 100644 --- a/packages/nestjs/src/setup.ts +++ b/packages/nestjs/src/setup.ts @@ -81,7 +81,7 @@ export const nestIntegration = defineIntegration(_nestIntegration); /** * Setup an error handler for Nest. */ -export function setupNestErrorHandler(app: MinimalNestJsApp): void { +export function setupNestErrorHandler(app: MinimalNestJsApp, baseFilter: NestJsErrorFilter): void { // Sadly, NestInstrumentation has no requestHook, so we need to add the attributes here // We register this hook in this method, because if we register it in the integration `setup`, // it would always run even for users that are not even using Nest.js @@ -111,6 +111,29 @@ export function setupNestErrorHandler(app: MinimalNestJsApp): void { }, }); + const wrappedFilter = new Proxy(baseFilter, { + get(target, prop, receiver) { + if (prop === 'catch') { + const originalCatch = Reflect.get(target, prop, receiver); + + return (exception: unknown, host: unknown) => { + const status_code = (exception as { status?: number }).status; + + // don't report expected errors + if (status_code !== undefined && status_code >= 400 && status_code < 500) { + return originalCatch.apply(target, [exception, host]); + } + + captureException(exception); + return originalCatch.apply(target, [exception, host]); + }; + } + return Reflect.get(target, prop, receiver); + }, + }); + + app.useGlobalFilters(wrappedFilter); + checkinExceptionFilters(app); } From c89c13c0d07167a8c769a97b4ae9a7c8dfcec52e Mon Sep 17 00:00:00 2001 From: nicohrubec Date: Mon, 15 Jul 2024 15:22:38 +0200 Subject: [PATCH 19/60] Create root module: Broken so far --- .../nestjs/src/app.module.ts | 3 +- .../test-applications/nestjs/src/main.ts | 4 +- .../nestjs/tests/errors.test.ts | 8 +- packages/nestjs/src/index.ts | 2 +- packages/nestjs/src/setup.ts | 174 +++++++++--------- packages/nestjs/tsconfig.json | 4 +- 6 files changed, 100 insertions(+), 95 deletions(-) diff --git a/dev-packages/e2e-tests/test-applications/nestjs/src/app.module.ts b/dev-packages/e2e-tests/test-applications/nestjs/src/app.module.ts index 932d1af99611..9ffe53c04756 100644 --- a/dev-packages/e2e-tests/test-applications/nestjs/src/app.module.ts +++ b/dev-packages/e2e-tests/test-applications/nestjs/src/app.module.ts @@ -3,9 +3,10 @@ import { ScheduleModule } from '@nestjs/schedule'; import { AppController1, AppController2 } from './app.controller'; import { AppService1, AppService2 } from './app.service'; import { TestModule } from './test-module/test.module'; +import { SentryIntegrationModule } from "@sentry/nestjs"; @Module({ - imports: [ScheduleModule.forRoot(), TestModule], + imports: [ScheduleModule.forRoot(), TestModule, SentryIntegrationModule.forRoot()], controllers: [AppController1], providers: [AppService1], }) diff --git a/dev-packages/e2e-tests/test-applications/nestjs/src/main.ts b/dev-packages/e2e-tests/test-applications/nestjs/src/main.ts index c2682662154d..c3c0ad189ba4 100644 --- a/dev-packages/e2e-tests/test-applications/nestjs/src/main.ts +++ b/dev-packages/e2e-tests/test-applications/nestjs/src/main.ts @@ -12,8 +12,8 @@ const app2Port = 3040; async function bootstrap() { const app1 = await NestFactory.create(AppModule1); - const { httpAdapter } = app1.get(HttpAdapterHost); - Sentry.setupNestErrorHandler(app1, new BaseExceptionFilter(httpAdapter)); + // const { httpAdapter } = app1.get(HttpAdapterHost); + // Sentry.setupNestErrorHandler(app1, new BaseExceptionFilter(httpAdapter)); await app1.listen(app1Port); diff --git a/dev-packages/e2e-tests/test-applications/nestjs/tests/errors.test.ts b/dev-packages/e2e-tests/test-applications/nestjs/tests/errors.test.ts index f66d5a3ff35f..cad2a085d1b6 100644 --- a/dev-packages/e2e-tests/test-applications/nestjs/tests/errors.test.ts +++ b/dev-packages/e2e-tests/test-applications/nestjs/tests/errors.test.ts @@ -54,7 +54,9 @@ test('Does not send expected exception to Sentry', async ({ baseURL }) => { expect(errorEventOccurred).toBe(false); }); -test('Handles exception correctly and does not send to Sentry if exception is thrown in module', async ({ baseURL }) => { +test('Handles exception correctly and does not send to Sentry if exception is thrown in module', async ({ + baseURL, +}) => { let errorEventOccurred = false; waitForError('nestjs', event => { @@ -69,10 +71,10 @@ test('Handles exception correctly and does not send to Sentry if exception is th return transactionEvent?.transaction === 'GET /test-module'; }); - console.log("fetch"); + console.log('fetch'); const response = await fetch(`${baseURL}/test-module`); expect(response.status).toBe(400); - console.log("waiting for response"); + console.log('waiting for response'); await transactionEventPromise; diff --git a/packages/nestjs/src/index.ts b/packages/nestjs/src/index.ts index d38a2aff2993..74dd330d237e 100644 --- a/packages/nestjs/src/index.ts +++ b/packages/nestjs/src/index.ts @@ -1,7 +1,7 @@ export * from '@sentry/node'; export { init } from './sdk'; -export { nestIntegration, setupNestErrorHandler } from './setup'; +export { nestIntegration, SentryIntegrationModule } from './setup'; export { SentryTraced } from './span-decorator'; export { SentryCron } from './cron-decorator'; diff --git a/packages/nestjs/src/setup.ts b/packages/nestjs/src/setup.ts index 247da2ff4d84..49c72aa26ebc 100644 --- a/packages/nestjs/src/setup.ts +++ b/packages/nestjs/src/setup.ts @@ -1,5 +1,8 @@ -import type { ArgumentsHost } from '@nestjs/common'; -import { DiscoveryService } from '@nestjs/core'; +import { Inject, Logger } from '@nestjs/common'; +import type { DynamicModule, OnModuleInit } from '@nestjs/common'; +import { Injectable } from '@nestjs/common'; +import { Global, Module } from '@nestjs/common'; +import {BaseExceptionFilter, ModuleRef} from '@nestjs/core'; import { NestInstrumentation } from '@opentelemetry/instrumentation-nestjs-core'; import { SEMANTIC_ATTRIBUTE_SENTRY_OP, @@ -46,19 +49,7 @@ interface MinimalNestJsApp { get: (type: new (...args: any[]) => T) => T; } -interface ExceptionFilterInstance { - // eslint-disable-next-line @typescript-eslint/no-explicit-any - catch: (exception: unknown, host: ArgumentsHost) => any; -} - -interface Provider { - // eslint-disable-next-line @typescript-eslint/no-explicit-any - metatype: any; - instance: ExceptionFilterInstance; -} - const INTEGRATION_NAME = 'Nest'; -const CATCH_WATERMARK = '__catch__'; export const instrumentNest = generateInstrumentOnce(INTEGRATION_NAME, () => new NestInstrumentation()); @@ -79,62 +70,98 @@ const _nestIntegration = (() => { export const nestIntegration = defineIntegration(_nestIntegration); /** - * Setup an error handler for Nest. + * Set up a nest service that provides error handling and performance tracing. */ -export function setupNestErrorHandler(app: MinimalNestJsApp, baseFilter: NestJsErrorFilter): void { - // Sadly, NestInstrumentation has no requestHook, so we need to add the attributes here - // We register this hook in this method, because if we register it in the integration `setup`, - // it would always run even for users that are not even using Nest.js - const client = getClient(); - if (client) { - client.on('spanStart', span => { - addNestSpanAttributes(span); - }); - } - - app.useGlobalInterceptors({ - intercept(context, next) { - if (getIsolationScope() === getDefaultIsolationScope()) { - logger.warn('Isolation scope is still the default isolation scope, skipping setting transactionName.'); - return next.handle(); - } +@Injectable() +export class SentryIntegrationService implements OnModuleInit { + // eslint-disable-next-line @sentry-internal/sdk/no-class-field-initializers + private readonly _logger = new Logger(SentryIntegrationService.name); + + public constructor( + private readonly _moduleRef: ModuleRef + ) {} + + /** + * Called when the SentryModuleIntegration gets initialized. + */ + public onModuleInit(): void { + const app: MinimalNestJsApp = this._moduleRef.get('NestApplication'); + const baseFilter: NestJsErrorFilter = new BaseExceptionFilter(); + + if (!app) { + this._logger.warn('Failed to retrieve the application instance.'); + return; + } + + const client = getClient(); + if (client) { + client.on('spanStart', span => { + addNestSpanAttributes(span); + }); + } + + app.useGlobalInterceptors({ + intercept(context, next) { + if (getIsolationScope() === getDefaultIsolationScope()) { + logger.warn('Isolation scope is still the default isolation scope, skipping setting transactionName.'); + return next.handle(); + } - if (context.getType() === 'http') { - const req = context.switchToHttp().getRequest(); - if (req.route) { - // eslint-disable-next-line @sentry-internal/sdk/no-optional-chaining - getIsolationScope().setTransactionName(`${req.method?.toUpperCase() || 'GET'} ${req.route.path}`); + if (context.getType() === 'http') { + const req = context.switchToHttp().getRequest(); + if (req.route) { + // eslint-disable-next-line @sentry-internal/sdk/no-optional-chaining + getIsolationScope().setTransactionName(`${req.method?.toUpperCase() || 'GET'} ${req.route.path}`); + } } - } - return next.handle(); - }, - }); + return next.handle(); + }, + }); - const wrappedFilter = new Proxy(baseFilter, { - get(target, prop, receiver) { - if (prop === 'catch') { - const originalCatch = Reflect.get(target, prop, receiver); + const wrappedFilter = new Proxy(baseFilter, { + get(target, prop, receiver) { + if (prop === 'catch') { + const originalCatch = Reflect.get(target, prop, receiver); - return (exception: unknown, host: unknown) => { - const status_code = (exception as { status?: number }).status; + return (exception: unknown, host: unknown) => { + const status_code = (exception as { status?: number }).status; - // don't report expected errors - if (status_code !== undefined && status_code >= 400 && status_code < 500) { - return originalCatch.apply(target, [exception, host]); - } + if (status_code !== undefined && status_code >= 400 && status_code < 500) { + return originalCatch.apply(target, [exception, host]); + } - captureException(exception); - return originalCatch.apply(target, [exception, host]); - }; - } - return Reflect.get(target, prop, receiver); - }, - }); + captureException(exception); + return originalCatch.apply(target, [exception, host]); + }; + } + return Reflect.get(target, prop, receiver); + }, + }); - app.useGlobalFilters(wrappedFilter); + app.useGlobalFilters(wrappedFilter); + } +} - checkinExceptionFilters(app); +/** + * Set up a root module that can be injected in nest applications. + */ +@Global() +@Module({ + providers: [SentryIntegrationService], + exports: [SentryIntegrationService], +}) +export class SentryIntegrationModule { + /** + * Called by the user to set the module as root module in a nest application. + */ + public static forRoot(): DynamicModule { + return { + module: SentryIntegrationModule, + providers: [SentryIntegrationService], + exports: [SentryIntegrationService], + }; + } } function addNestSpanAttributes(span: Span): void { @@ -153,30 +180,3 @@ function addNestSpanAttributes(span: Span): void { [SEMANTIC_ATTRIBUTE_SENTRY_OP]: `${type}.nestjs`, }); } - -function checkinExceptionFilters(app: MinimalNestJsApp): void { - const discoveryService = app.get(DiscoveryService); - const providers = discoveryService.getProviders() as Provider[]; - const exceptionFilters = providers.filter( - ({ metatype }) => metatype && Reflect.getMetadata(CATCH_WATERMARK, metatype), - ); - - exceptionFilters.map(mod => { - const instance = mod.instance; - const originalCatch = instance.catch; - - instance.catch = function (exception: unknown, host) { - const status_code = (exception as { status?: number }).status; - - // don't report expected errors - if (status_code !== undefined && status_code >= 400 && status_code < 500) { - return originalCatch.apply(this, [exception, host]); - } - - captureException(exception); - return originalCatch.apply(this, [exception, host]); - }; - - return mod; - }); -} diff --git a/packages/nestjs/tsconfig.json b/packages/nestjs/tsconfig.json index b0eb9ecb6476..a368e55c8542 100644 --- a/packages/nestjs/tsconfig.json +++ b/packages/nestjs/tsconfig.json @@ -3,5 +3,7 @@ "include": ["src/**/*"], - "compilerOptions": {} + "compilerOptions": { + "experimentalDecorators": true + } } From 149f1ae4bae0fe0cd254573a679e62e9a672c83e Mon Sep 17 00:00:00 2001 From: nicohrubec Date: Mon, 15 Jul 2024 16:20:42 +0200 Subject: [PATCH 20/60] Build works now --- packages/nestjs/src/setup.ts | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/packages/nestjs/src/setup.ts b/packages/nestjs/src/setup.ts index 49c72aa26ebc..79c767f2d4a5 100644 --- a/packages/nestjs/src/setup.ts +++ b/packages/nestjs/src/setup.ts @@ -1,8 +1,9 @@ -import { Inject, Logger } from '@nestjs/common'; +import { Logger } from '@nestjs/common'; import type { DynamicModule, OnModuleInit } from '@nestjs/common'; import { Injectable } from '@nestjs/common'; import { Global, Module } from '@nestjs/common'; -import {BaseExceptionFilter, ModuleRef} from '@nestjs/core'; +import type { ModuleRef } from '@nestjs/core'; +import {BaseExceptionFilter} from '@nestjs/core'; import { NestInstrumentation } from '@opentelemetry/instrumentation-nestjs-core'; import { SEMANTIC_ATTRIBUTE_SENTRY_OP, @@ -72,7 +73,6 @@ export const nestIntegration = defineIntegration(_nestIntegration); /** * Set up a nest service that provides error handling and performance tracing. */ -@Injectable() export class SentryIntegrationService implements OnModuleInit { // eslint-disable-next-line @sentry-internal/sdk/no-class-field-initializers private readonly _logger = new Logger(SentryIntegrationService.name); @@ -146,11 +146,6 @@ export class SentryIntegrationService implements OnModuleInit { /** * Set up a root module that can be injected in nest applications. */ -@Global() -@Module({ - providers: [SentryIntegrationService], - exports: [SentryIntegrationService], -}) export class SentryIntegrationModule { /** * Called by the user to set the module as root module in a nest application. @@ -180,3 +175,10 @@ function addNestSpanAttributes(span: Span): void { [SEMANTIC_ATTRIBUTE_SENTRY_OP]: `${type}.nestjs`, }); } + +Injectable()(SentryIntegrationService); +Global()(SentryIntegrationModule); +Module({ + providers: [SentryIntegrationService], + exports: [SentryIntegrationService], +})(SentryIntegrationModule); From 27fad2266de76c58022a869ba2f835cd750c0f04 Mon Sep 17 00:00:00 2001 From: nicohrubec Date: Mon, 15 Jul 2024 16:59:53 +0200 Subject: [PATCH 21/60] Move interceptor to separate class --- packages/nestjs/src/setup.ts | 130 ++++++++++------------------------- 1 file changed, 37 insertions(+), 93 deletions(-) diff --git a/packages/nestjs/src/setup.ts b/packages/nestjs/src/setup.ts index 79c767f2d4a5..47b32e1a453f 100644 --- a/packages/nestjs/src/setup.ts +++ b/packages/nestjs/src/setup.ts @@ -1,14 +1,10 @@ -import { Logger } from '@nestjs/common'; -import type { DynamicModule, OnModuleInit } from '@nestjs/common'; +import type {CallHandler, DynamicModule, ExecutionContext, NestInterceptor, OnModuleInit} from '@nestjs/common'; import { Injectable } from '@nestjs/common'; import { Global, Module } from '@nestjs/common'; -import type { ModuleRef } from '@nestjs/core'; -import {BaseExceptionFilter} from '@nestjs/core'; import { NestInstrumentation } from '@opentelemetry/instrumentation-nestjs-core'; import { SEMANTIC_ATTRIBUTE_SENTRY_OP, SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN, - captureException, defineIntegration, getClient, getDefaultIsolationScope, @@ -18,37 +14,8 @@ import { import { generateInstrumentOnce } from '@sentry/node'; import type { IntegrationFn, Span } from '@sentry/types'; import { logger } from '@sentry/utils'; - -interface MinimalNestJsExecutionContext { - getType: () => string; - - switchToHttp: () => { - // minimal request object - // according to official types, all properties are required but - // let's play it safe and assume they're optional - getRequest: () => { - route?: { - path?: string; - }; - method?: string; - }; - }; -} - -interface NestJsErrorFilter { - // eslint-disable-next-line @typescript-eslint/no-explicit-any - catch(exception: any, host: any): void; -} - -interface MinimalNestJsApp { - useGlobalFilters: (arg0: NestJsErrorFilter) => void; - useGlobalInterceptors: (interceptor: { - // eslint-disable-next-line @typescript-eslint/no-explicit-any - intercept: (context: MinimalNestJsExecutionContext, next: { handle: () => any }) => any; - }) => void; - - get: (type: new (...args: any[]) => T) => T; -} +import type { Observable } from 'rxjs'; +import {APP_INTERCEPTOR} from '@nestjs/core'; const INTEGRATION_NAME = 'Nest'; @@ -71,75 +38,45 @@ const _nestIntegration = (() => { export const nestIntegration = defineIntegration(_nestIntegration); /** - * Set up a nest service that provides error handling and performance tracing. + * */ -export class SentryIntegrationService implements OnModuleInit { - // eslint-disable-next-line @sentry-internal/sdk/no-class-field-initializers - private readonly _logger = new Logger(SentryIntegrationService.name); +export class SentryTracingInterceptor implements NestInterceptor { + /** + * + */ + public intercept(context: ExecutionContext, next: CallHandler): Observable { + if (getIsolationScope() === getDefaultIsolationScope()) { + logger.warn('Isolation scope is still the default isolation scope, skipping setting transactionName.'); + return next.handle(); + } - public constructor( - private readonly _moduleRef: ModuleRef - ) {} + if (context.getType() === 'http') { + const req = context.switchToHttp().getRequest(); + // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access + if (req.route) { + // eslint-disable-next-line @sentry-internal/sdk/no-optional-chaining,@typescript-eslint/no-unsafe-member-access + getIsolationScope().setTransactionName(`${req.method?.toUpperCase() || 'GET'} ${req.route.path}`); + } + } + + return next.handle(); + } +} +/** + * Set up a nest service that provides error handling and performance tracing. + */ +export class SentryIntegrationService implements OnModuleInit { /** * Called when the SentryModuleIntegration gets initialized. */ public onModuleInit(): void { - const app: MinimalNestJsApp = this._moduleRef.get('NestApplication'); - const baseFilter: NestJsErrorFilter = new BaseExceptionFilter(); - - if (!app) { - this._logger.warn('Failed to retrieve the application instance.'); - return; - } - const client = getClient(); if (client) { client.on('spanStart', span => { addNestSpanAttributes(span); }); } - - app.useGlobalInterceptors({ - intercept(context, next) { - if (getIsolationScope() === getDefaultIsolationScope()) { - logger.warn('Isolation scope is still the default isolation scope, skipping setting transactionName.'); - return next.handle(); - } - - if (context.getType() === 'http') { - const req = context.switchToHttp().getRequest(); - if (req.route) { - // eslint-disable-next-line @sentry-internal/sdk/no-optional-chaining - getIsolationScope().setTransactionName(`${req.method?.toUpperCase() || 'GET'} ${req.route.path}`); - } - } - - return next.handle(); - }, - }); - - const wrappedFilter = new Proxy(baseFilter, { - get(target, prop, receiver) { - if (prop === 'catch') { - const originalCatch = Reflect.get(target, prop, receiver); - - return (exception: unknown, host: unknown) => { - const status_code = (exception as { status?: number }).status; - - if (status_code !== undefined && status_code >= 400 && status_code < 500) { - return originalCatch.apply(target, [exception, host]); - } - - captureException(exception); - return originalCatch.apply(target, [exception, host]); - }; - } - return Reflect.get(target, prop, receiver); - }, - }); - - app.useGlobalFilters(wrappedFilter); } } @@ -153,7 +90,13 @@ export class SentryIntegrationModule { public static forRoot(): DynamicModule { return { module: SentryIntegrationModule, - providers: [SentryIntegrationService], + providers: [ + SentryIntegrationService, + { + provide: APP_INTERCEPTOR, + useClass: SentryTracingInterceptor + } + ], exports: [SentryIntegrationService], }; } @@ -176,6 +119,7 @@ function addNestSpanAttributes(span: Span): void { }); } +Injectable()(SentryTracingInterceptor); Injectable()(SentryIntegrationService); Global()(SentryIntegrationModule); Module({ From 49fe07d70d6cee21a062e34f2e49396f7bcc4dd5 Mon Sep 17 00:00:00 2001 From: nicohrubec Date: Mon, 15 Jul 2024 17:00:56 +0200 Subject: [PATCH 22/60] Move interceptor to separate class now fr --- packages/nestjs/src/setup.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/nestjs/src/setup.ts b/packages/nestjs/src/setup.ts index 47b32e1a453f..0c7b5351c572 100644 --- a/packages/nestjs/src/setup.ts +++ b/packages/nestjs/src/setup.ts @@ -38,7 +38,7 @@ const _nestIntegration = (() => { export const nestIntegration = defineIntegration(_nestIntegration); /** - * + * */ export class SentryTracingInterceptor implements NestInterceptor { /** From c56b363197e8b3aa014618d1de142a204dab017b Mon Sep 17 00:00:00 2001 From: nicohrubec Date: Mon, 15 Jul 2024 17:11:48 +0200 Subject: [PATCH 23/60] Revert test --- .../nestjs/tests/errors.test.ts | 42 +++++++++---------- 1 file changed, 21 insertions(+), 21 deletions(-) diff --git a/dev-packages/e2e-tests/test-applications/nestjs/tests/errors.test.ts b/dev-packages/e2e-tests/test-applications/nestjs/tests/errors.test.ts index cad2a085d1b6..ffb48f4e5e70 100644 --- a/dev-packages/e2e-tests/test-applications/nestjs/tests/errors.test.ts +++ b/dev-packages/e2e-tests/test-applications/nestjs/tests/errors.test.ts @@ -54,31 +54,31 @@ test('Does not send expected exception to Sentry', async ({ baseURL }) => { expect(errorEventOccurred).toBe(false); }); -test('Handles exception correctly and does not send to Sentry if exception is thrown in module', async ({ - baseURL, -}) => { - let errorEventOccurred = false; - - waitForError('nestjs', event => { - if (!event.type && event.exception?.values?.[0]?.value === 'Something went wrong in the test module!') { - errorEventOccurred = true; - } - - return event?.transaction === 'GET /test-module'; - }); - - const transactionEventPromise = waitForTransaction('nestjs', transactionEvent => { - return transactionEvent?.transaction === 'GET /test-module'; +test('Does not handle expected exception if exception is thrown in module', async ({ baseURL }) => { + const errorEventPromise = waitForError('nestjs', event => { + return !event.type && event.exception?.values?.[0]?.value === 'Something went wrong in the test module!'; }); - console.log('fetch'); const response = await fetch(`${baseURL}/test-module`); - expect(response.status).toBe(400); - console.log('waiting for response'); + expect(response.status).toBe(500); // should be 400 - await transactionEventPromise; + // should never arrive, but does because the exception is not handled properly + const errorEvent = await errorEventPromise; - await new Promise(resolve => setTimeout(resolve, 10000)); + expect(errorEvent.exception?.values).toHaveLength(1); + expect(errorEvent.exception?.values?.[0]?.value).toBe('Something went wrong in the test module!'); - expect(errorEventOccurred).toBe(false); + expect(errorEvent.request).toEqual({ + method: 'GET', + cookies: {}, + headers: expect.any(Object), + url: 'http://localhost:3030/test-module', + }); + + expect(errorEvent.transaction).toEqual('GET /test-module'); + + expect(errorEvent.contexts?.trace).toEqual({ + trace_id: expect.any(String), + span_id: expect.any(String), + }); }); From 53e40b3069081b058f60dadab6a9ce262e00cec4 Mon Sep 17 00:00:00 2001 From: nicohrubec Date: Mon, 15 Jul 2024 17:13:41 +0200 Subject: [PATCH 24/60] Lint --- .../e2e-tests/test-applications/nestjs/src/app.module.ts | 2 +- .../e2e-tests/test-applications/nestjs/src/main.ts | 7 +------ packages/nestjs/src/setup.ts | 8 ++++---- 3 files changed, 6 insertions(+), 11 deletions(-) diff --git a/dev-packages/e2e-tests/test-applications/nestjs/src/app.module.ts b/dev-packages/e2e-tests/test-applications/nestjs/src/app.module.ts index 9ffe53c04756..e4259d65c193 100644 --- a/dev-packages/e2e-tests/test-applications/nestjs/src/app.module.ts +++ b/dev-packages/e2e-tests/test-applications/nestjs/src/app.module.ts @@ -1,9 +1,9 @@ import { Module } from '@nestjs/common'; import { ScheduleModule } from '@nestjs/schedule'; +import { SentryIntegrationModule } from '@sentry/nestjs'; import { AppController1, AppController2 } from './app.controller'; import { AppService1, AppService2 } from './app.service'; import { TestModule } from './test-module/test.module'; -import { SentryIntegrationModule } from "@sentry/nestjs"; @Module({ imports: [ScheduleModule.forRoot(), TestModule, SentryIntegrationModule.forRoot()], diff --git a/dev-packages/e2e-tests/test-applications/nestjs/src/main.ts b/dev-packages/e2e-tests/test-applications/nestjs/src/main.ts index c3c0ad189ba4..065ed4974ac8 100644 --- a/dev-packages/e2e-tests/test-applications/nestjs/src/main.ts +++ b/dev-packages/e2e-tests/test-applications/nestjs/src/main.ts @@ -2,8 +2,7 @@ import './instrument'; // Import other modules -import { BaseExceptionFilter, HttpAdapterHost, NestFactory } from '@nestjs/core'; -import * as Sentry from '@sentry/nestjs'; +import { NestFactory } from '@nestjs/core'; import { AppModule1, AppModule2 } from './app.module'; const app1Port = 3030; @@ -11,10 +10,6 @@ const app2Port = 3040; async function bootstrap() { const app1 = await NestFactory.create(AppModule1); - - // const { httpAdapter } = app1.get(HttpAdapterHost); - // Sentry.setupNestErrorHandler(app1, new BaseExceptionFilter(httpAdapter)); - await app1.listen(app1Port); const app2 = await NestFactory.create(AppModule2); diff --git a/packages/nestjs/src/setup.ts b/packages/nestjs/src/setup.ts index 0c7b5351c572..5eb0e76e4d11 100644 --- a/packages/nestjs/src/setup.ts +++ b/packages/nestjs/src/setup.ts @@ -1,6 +1,7 @@ -import type {CallHandler, DynamicModule, ExecutionContext, NestInterceptor, OnModuleInit} from '@nestjs/common'; +import type { CallHandler, DynamicModule, ExecutionContext, NestInterceptor, OnModuleInit } from '@nestjs/common'; import { Injectable } from '@nestjs/common'; import { Global, Module } from '@nestjs/common'; +import { APP_INTERCEPTOR } from '@nestjs/core'; import { NestInstrumentation } from '@opentelemetry/instrumentation-nestjs-core'; import { SEMANTIC_ATTRIBUTE_SENTRY_OP, @@ -15,7 +16,6 @@ import { generateInstrumentOnce } from '@sentry/node'; import type { IntegrationFn, Span } from '@sentry/types'; import { logger } from '@sentry/utils'; import type { Observable } from 'rxjs'; -import {APP_INTERCEPTOR} from '@nestjs/core'; const INTEGRATION_NAME = 'Nest'; @@ -94,8 +94,8 @@ export class SentryIntegrationModule { SentryIntegrationService, { provide: APP_INTERCEPTOR, - useClass: SentryTracingInterceptor - } + useClass: SentryTracingInterceptor, + }, ], exports: [SentryIntegrationService], }; From 72aead34084c3562bac527284f6b10dfbf882198 Mon Sep 17 00:00:00 2001 From: nicohrubec Date: Mon, 15 Jul 2024 17:37:13 +0200 Subject: [PATCH 25/60] Update decorators --- packages/nestjs/src/setup.ts | 49 ++++++++++++++++++++++++++++++++++-- 1 file changed, 47 insertions(+), 2 deletions(-) diff --git a/packages/nestjs/src/setup.ts b/packages/nestjs/src/setup.ts index 5eb0e76e4d11..c3f69465d287 100644 --- a/packages/nestjs/src/setup.ts +++ b/packages/nestjs/src/setup.ts @@ -1,11 +1,21 @@ -import type { CallHandler, DynamicModule, ExecutionContext, NestInterceptor, OnModuleInit } from '@nestjs/common'; +import type { + ArgumentsHost, + CallHandler, + DynamicModule, + ExecutionContext, + NestInterceptor, + OnModuleInit, +} from '@nestjs/common'; +import { Catch } from '@nestjs/common'; import { Injectable } from '@nestjs/common'; import { Global, Module } from '@nestjs/common'; +import { APP_FILTER, BaseExceptionFilter } from '@nestjs/core'; import { APP_INTERCEPTOR } from '@nestjs/core'; import { NestInstrumentation } from '@opentelemetry/instrumentation-nestjs-core'; import { SEMANTIC_ATTRIBUTE_SENTRY_OP, SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN, + captureException, defineIntegration, getClient, getDefaultIsolationScope, @@ -63,6 +73,26 @@ export class SentryTracingInterceptor implements NestInterceptor { } } +/** + * + */ +export class SentryGlobalFilter extends BaseExceptionFilter { + /** + * + */ + public catch(exception: unknown, host: ArgumentsHost): void { + const status_code = (exception as { status?: number }).status; + + // don't report expected errors + if (status_code !== undefined && status_code >= 400 && status_code < 500) { + captureException(exception); + super.catch(exception, host); + } + + super.catch(exception, host); + } +} + /** * Set up a nest service that provides error handling and performance tracing. */ @@ -92,6 +122,10 @@ export class SentryIntegrationModule { module: SentryIntegrationModule, providers: [ SentryIntegrationService, + { + provide: APP_FILTER, + useClass: SentryGlobalFilter, + }, { provide: APP_INTERCEPTOR, useClass: SentryTracingInterceptor, @@ -119,10 +153,21 @@ function addNestSpanAttributes(span: Span): void { }); } +Catch()(SentryGlobalFilter); Injectable()(SentryTracingInterceptor); Injectable()(SentryIntegrationService); Global()(SentryIntegrationModule); Module({ - providers: [SentryIntegrationService], + providers: [ + SentryIntegrationService, + { + provide: APP_FILTER, + useClass: SentryGlobalFilter, + }, + { + provide: APP_INTERCEPTOR, + useClass: SentryTracingInterceptor, + }, + ], exports: [SentryIntegrationService], })(SentryIntegrationModule); From 8804071b85f83107f4e4f6e158e5bd65e85dc983 Mon Sep 17 00:00:00 2001 From: nicohrubec Date: Tue, 16 Jul 2024 09:30:56 +0200 Subject: [PATCH 26/60] Fix capture exception --- .../test-applications/nestjs/src/app.module.ts | 2 +- .../test-applications/nestjs/tests/errors.test.ts | 10 ++++++++++ packages/nestjs/src/setup.ts | 6 +++++- 3 files changed, 16 insertions(+), 2 deletions(-) diff --git a/dev-packages/e2e-tests/test-applications/nestjs/src/app.module.ts b/dev-packages/e2e-tests/test-applications/nestjs/src/app.module.ts index e4259d65c193..cb6d124f1eac 100644 --- a/dev-packages/e2e-tests/test-applications/nestjs/src/app.module.ts +++ b/dev-packages/e2e-tests/test-applications/nestjs/src/app.module.ts @@ -6,7 +6,7 @@ import { AppService1, AppService2 } from './app.service'; import { TestModule } from './test-module/test.module'; @Module({ - imports: [ScheduleModule.forRoot(), TestModule, SentryIntegrationModule.forRoot()], + imports: [SentryIntegrationModule.forRoot(), ScheduleModule.forRoot(), TestModule], controllers: [AppController1], providers: [AppService1], }) diff --git a/dev-packages/e2e-tests/test-applications/nestjs/tests/errors.test.ts b/dev-packages/e2e-tests/test-applications/nestjs/tests/errors.test.ts index ffb48f4e5e70..ce484309a143 100644 --- a/dev-packages/e2e-tests/test-applications/nestjs/tests/errors.test.ts +++ b/dev-packages/e2e-tests/test-applications/nestjs/tests/errors.test.ts @@ -2,18 +2,26 @@ import { expect, test } from '@playwright/test'; import { waitForError, waitForTransaction } from '@sentry-internal/test-utils'; test('Sends exception to Sentry', async ({ baseURL }) => { + console.log("Start test 1"); const errorEventPromise = waitForError('nestjs', event => { + console.log(event); return !event.type && event.exception?.values?.[0]?.value === 'This is an exception with id 123'; }); const response = await fetch(`${baseURL}/test-exception/123`); expect(response.status).toBe(500); + console.log("Waiting for error"); + const errorEvent = await errorEventPromise; + console.log("Error found"); + expect(errorEvent.exception?.values).toHaveLength(1); expect(errorEvent.exception?.values?.[0]?.value).toBe('This is an exception with id 123'); + console.log("Waiting for error"); + expect(errorEvent.request).toEqual({ method: 'GET', cookies: {}, @@ -30,6 +38,7 @@ test('Sends exception to Sentry', async ({ baseURL }) => { }); test('Does not send expected exception to Sentry', async ({ baseURL }) => { + console.log("Start test 2"); let errorEventOccurred = false; waitForError('nestjs', event => { @@ -55,6 +64,7 @@ test('Does not send expected exception to Sentry', async ({ baseURL }) => { }); test('Does not handle expected exception if exception is thrown in module', async ({ baseURL }) => { + console.log("Start test 3"); const errorEventPromise = waitForError('nestjs', event => { return !event.type && event.exception?.values?.[0]?.value === 'Something went wrong in the test module!'; }); diff --git a/packages/nestjs/src/setup.ts b/packages/nestjs/src/setup.ts index c3f69465d287..78aa2ddbd13a 100644 --- a/packages/nestjs/src/setup.ts +++ b/packages/nestjs/src/setup.ts @@ -81,14 +81,17 @@ export class SentryGlobalFilter extends BaseExceptionFilter { * */ public catch(exception: unknown, host: ArgumentsHost): void { + console.log('Calling catch in global filter'); const status_code = (exception as { status?: number }).status; + console.log('Status code: ', status_code); // don't report expected errors if (status_code !== undefined && status_code >= 400 && status_code < 500) { - captureException(exception); super.catch(exception, host); } + console.log("Capture exception"); + captureException(exception); super.catch(exception, host); } } @@ -102,6 +105,7 @@ export class SentryIntegrationService implements OnModuleInit { */ public onModuleInit(): void { const client = getClient(); + console.log('Module initialised'); if (client) { client.on('spanStart', span => { addNestSpanAttributes(span); From 14e5439745ee82ec3c2ee3e11b522632b5062aa2 Mon Sep 17 00:00:00 2001 From: nicohrubec Date: Tue, 16 Jul 2024 09:37:34 +0200 Subject: [PATCH 27/60] Fix does not send error test --- .../test-applications/nestjs/tests/errors.test.ts | 12 ++++++------ packages/nestjs/src/setup.ts | 6 +++--- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/dev-packages/e2e-tests/test-applications/nestjs/tests/errors.test.ts b/dev-packages/e2e-tests/test-applications/nestjs/tests/errors.test.ts index ce484309a143..ebfe6276b67a 100644 --- a/dev-packages/e2e-tests/test-applications/nestjs/tests/errors.test.ts +++ b/dev-packages/e2e-tests/test-applications/nestjs/tests/errors.test.ts @@ -2,7 +2,7 @@ import { expect, test } from '@playwright/test'; import { waitForError, waitForTransaction } from '@sentry-internal/test-utils'; test('Sends exception to Sentry', async ({ baseURL }) => { - console.log("Start test 1"); + console.log('Start test 1'); const errorEventPromise = waitForError('nestjs', event => { console.log(event); return !event.type && event.exception?.values?.[0]?.value === 'This is an exception with id 123'; @@ -11,16 +11,16 @@ test('Sends exception to Sentry', async ({ baseURL }) => { const response = await fetch(`${baseURL}/test-exception/123`); expect(response.status).toBe(500); - console.log("Waiting for error"); + console.log('Waiting for error'); const errorEvent = await errorEventPromise; - console.log("Error found"); + console.log('Error found'); expect(errorEvent.exception?.values).toHaveLength(1); expect(errorEvent.exception?.values?.[0]?.value).toBe('This is an exception with id 123'); - console.log("Waiting for error"); + console.log('Waiting for error'); expect(errorEvent.request).toEqual({ method: 'GET', @@ -38,7 +38,7 @@ test('Sends exception to Sentry', async ({ baseURL }) => { }); test('Does not send expected exception to Sentry', async ({ baseURL }) => { - console.log("Start test 2"); + console.log('Start test 2'); let errorEventOccurred = false; waitForError('nestjs', event => { @@ -64,7 +64,7 @@ test('Does not send expected exception to Sentry', async ({ baseURL }) => { }); test('Does not handle expected exception if exception is thrown in module', async ({ baseURL }) => { - console.log("Start test 3"); + console.log('Start test 3'); const errorEventPromise = waitForError('nestjs', event => { return !event.type && event.exception?.values?.[0]?.value === 'Something went wrong in the test module!'; }); diff --git a/packages/nestjs/src/setup.ts b/packages/nestjs/src/setup.ts index 78aa2ddbd13a..e4cd6c6fe5e9 100644 --- a/packages/nestjs/src/setup.ts +++ b/packages/nestjs/src/setup.ts @@ -87,12 +87,12 @@ export class SentryGlobalFilter extends BaseExceptionFilter { // don't report expected errors if (status_code !== undefined && status_code >= 400 && status_code < 500) { - super.catch(exception, host); + return super.catch(exception, host); } - console.log("Capture exception"); + console.log('Capture exception'); captureException(exception); - super.catch(exception, host); + return super.catch(exception, host); } } From ca4ba134cce4c9b20770f86b9ef0c3d58f52ad9c Mon Sep 17 00:00:00 2001 From: nicohrubec Date: Tue, 16 Jul 2024 09:41:29 +0200 Subject: [PATCH 28/60] Remove some logs --- .../test-applications/nestjs/src/app.module.ts | 2 +- .../test-applications/nestjs/tests/errors.test.ts | 10 ---------- packages/nestjs/src/setup.ts | 4 ---- 3 files changed, 1 insertion(+), 15 deletions(-) diff --git a/dev-packages/e2e-tests/test-applications/nestjs/src/app.module.ts b/dev-packages/e2e-tests/test-applications/nestjs/src/app.module.ts index cb6d124f1eac..6c69578154ce 100644 --- a/dev-packages/e2e-tests/test-applications/nestjs/src/app.module.ts +++ b/dev-packages/e2e-tests/test-applications/nestjs/src/app.module.ts @@ -6,7 +6,7 @@ import { AppService1, AppService2 } from './app.service'; import { TestModule } from './test-module/test.module'; @Module({ - imports: [SentryIntegrationModule.forRoot(), ScheduleModule.forRoot(), TestModule], + imports: [SentryIntegrationModule.forRoot(), ScheduleModule.forRoot(), TestModule], // TODO: check if the order of registration matters controllers: [AppController1], providers: [AppService1], }) diff --git a/dev-packages/e2e-tests/test-applications/nestjs/tests/errors.test.ts b/dev-packages/e2e-tests/test-applications/nestjs/tests/errors.test.ts index ebfe6276b67a..ffb48f4e5e70 100644 --- a/dev-packages/e2e-tests/test-applications/nestjs/tests/errors.test.ts +++ b/dev-packages/e2e-tests/test-applications/nestjs/tests/errors.test.ts @@ -2,26 +2,18 @@ import { expect, test } from '@playwright/test'; import { waitForError, waitForTransaction } from '@sentry-internal/test-utils'; test('Sends exception to Sentry', async ({ baseURL }) => { - console.log('Start test 1'); const errorEventPromise = waitForError('nestjs', event => { - console.log(event); return !event.type && event.exception?.values?.[0]?.value === 'This is an exception with id 123'; }); const response = await fetch(`${baseURL}/test-exception/123`); expect(response.status).toBe(500); - console.log('Waiting for error'); - const errorEvent = await errorEventPromise; - console.log('Error found'); - expect(errorEvent.exception?.values).toHaveLength(1); expect(errorEvent.exception?.values?.[0]?.value).toBe('This is an exception with id 123'); - console.log('Waiting for error'); - expect(errorEvent.request).toEqual({ method: 'GET', cookies: {}, @@ -38,7 +30,6 @@ test('Sends exception to Sentry', async ({ baseURL }) => { }); test('Does not send expected exception to Sentry', async ({ baseURL }) => { - console.log('Start test 2'); let errorEventOccurred = false; waitForError('nestjs', event => { @@ -64,7 +55,6 @@ test('Does not send expected exception to Sentry', async ({ baseURL }) => { }); test('Does not handle expected exception if exception is thrown in module', async ({ baseURL }) => { - console.log('Start test 3'); const errorEventPromise = waitForError('nestjs', event => { return !event.type && event.exception?.values?.[0]?.value === 'Something went wrong in the test module!'; }); diff --git a/packages/nestjs/src/setup.ts b/packages/nestjs/src/setup.ts index e4cd6c6fe5e9..a7798a053051 100644 --- a/packages/nestjs/src/setup.ts +++ b/packages/nestjs/src/setup.ts @@ -81,16 +81,13 @@ export class SentryGlobalFilter extends BaseExceptionFilter { * */ public catch(exception: unknown, host: ArgumentsHost): void { - console.log('Calling catch in global filter'); const status_code = (exception as { status?: number }).status; - console.log('Status code: ', status_code); // don't report expected errors if (status_code !== undefined && status_code >= 400 && status_code < 500) { return super.catch(exception, host); } - console.log('Capture exception'); captureException(exception); return super.catch(exception, host); } @@ -105,7 +102,6 @@ export class SentryIntegrationService implements OnModuleInit { */ public onModuleInit(): void { const client = getClient(); - console.log('Module initialised'); if (client) { client.on('spanStart', span => { addNestSpanAttributes(span); From b09527996b8a804ffbe2c9324cfa4ee7c384e431 Mon Sep 17 00:00:00 2001 From: nicohrubec Date: Tue, 16 Jul 2024 09:59:26 +0200 Subject: [PATCH 29/60] Fix test: Does not handle expected exception if exception is thrown in module --- .../nestjs/tests/errors.test.ts | 40 +++++++++---------- 1 file changed, 19 insertions(+), 21 deletions(-) diff --git a/dev-packages/e2e-tests/test-applications/nestjs/tests/errors.test.ts b/dev-packages/e2e-tests/test-applications/nestjs/tests/errors.test.ts index ffb48f4e5e70..a17423c3f86c 100644 --- a/dev-packages/e2e-tests/test-applications/nestjs/tests/errors.test.ts +++ b/dev-packages/e2e-tests/test-applications/nestjs/tests/errors.test.ts @@ -54,31 +54,29 @@ test('Does not send expected exception to Sentry', async ({ baseURL }) => { expect(errorEventOccurred).toBe(false); }); -test('Does not handle expected exception if exception is thrown in module', async ({ baseURL }) => { - const errorEventPromise = waitForError('nestjs', event => { - return !event.type && event.exception?.values?.[0]?.value === 'Something went wrong in the test module!'; - }); - - const response = await fetch(`${baseURL}/test-module`); - expect(response.status).toBe(500); // should be 400 +test('Handles exception correctly and does not send to Sentry if exception is thrown in module', async ({ + baseURL, +}) => { + let errorEventOccurred = false; - // should never arrive, but does because the exception is not handled properly - const errorEvent = await errorEventPromise; + waitForError('nestjs', event => { + if (!event.type && event.exception?.values?.[0]?.value === 'Something went wrong in the test module!') { + errorEventOccurred = true; + } - expect(errorEvent.exception?.values).toHaveLength(1); - expect(errorEvent.exception?.values?.[0]?.value).toBe('Something went wrong in the test module!'); + return event?.transaction === 'GET /test-module'; + }); - expect(errorEvent.request).toEqual({ - method: 'GET', - cookies: {}, - headers: expect.any(Object), - url: 'http://localhost:3030/test-module', + const transactionEventPromise = waitForTransaction('nestjs', transactionEvent => { + return transactionEvent?.transaction === 'GET /test-module'; }); - expect(errorEvent.transaction).toEqual('GET /test-module'); + const response = await fetch(`${baseURL}/test-module`); + expect(response.status).toBe(400); - expect(errorEvent.contexts?.trace).toEqual({ - trace_id: expect.any(String), - span_id: expect.any(String), - }); + await transactionEventPromise; + + await new Promise(resolve => setTimeout(resolve, 10000)); + + expect(errorEventOccurred).toBe(false); }); From da0fffd7b7cb4740cd401bd67fdf2c7c7189a33c Mon Sep 17 00:00:00 2001 From: nicohrubec Date: Tue, 16 Jul 2024 10:35:15 +0200 Subject: [PATCH 30/60] Rename test module to example module in nest test application --- .../test-applications/nestjs/src/app.module.ts | 4 ++-- .../example.controller.ts} | 6 +++--- .../example.exception.ts} | 2 +- .../example.filter.ts} | 8 ++++---- .../nestjs/src/example-module/example.module.ts | 16 ++++++++++++++++ .../nestjs/src/test-module/test.module.ts | 16 ---------------- 6 files changed, 26 insertions(+), 26 deletions(-) rename dev-packages/e2e-tests/test-applications/nestjs/src/{test-module/test.controller.ts => example-module/example.controller.ts} (52%) rename dev-packages/e2e-tests/test-applications/nestjs/src/{test-module/test.exception.ts => example-module/example.exception.ts} (63%) rename dev-packages/e2e-tests/test-applications/nestjs/src/{test-module/test.filter.ts => example-module/example.filter.ts} (61%) create mode 100644 dev-packages/e2e-tests/test-applications/nestjs/src/example-module/example.module.ts delete mode 100644 dev-packages/e2e-tests/test-applications/nestjs/src/test-module/test.module.ts diff --git a/dev-packages/e2e-tests/test-applications/nestjs/src/app.module.ts b/dev-packages/e2e-tests/test-applications/nestjs/src/app.module.ts index 6c69578154ce..b211a6edaae2 100644 --- a/dev-packages/e2e-tests/test-applications/nestjs/src/app.module.ts +++ b/dev-packages/e2e-tests/test-applications/nestjs/src/app.module.ts @@ -3,10 +3,10 @@ import { ScheduleModule } from '@nestjs/schedule'; import { SentryIntegrationModule } from '@sentry/nestjs'; import { AppController1, AppController2 } from './app.controller'; import { AppService1, AppService2 } from './app.service'; -import { TestModule } from './test-module/test.module'; +import { ExampleModule } from './example-module/example.module'; @Module({ - imports: [SentryIntegrationModule.forRoot(), ScheduleModule.forRoot(), TestModule], // TODO: check if the order of registration matters + imports: [SentryIntegrationModule.forRoot(), ScheduleModule.forRoot(), ExampleModule], // TODO: check if the order of registration matters controllers: [AppController1], providers: [AppService1], }) diff --git a/dev-packages/e2e-tests/test-applications/nestjs/src/test-module/test.controller.ts b/dev-packages/e2e-tests/test-applications/nestjs/src/example-module/example.controller.ts similarity index 52% rename from dev-packages/e2e-tests/test-applications/nestjs/src/test-module/test.controller.ts rename to dev-packages/e2e-tests/test-applications/nestjs/src/example-module/example.controller.ts index 150fb0e07546..a6ad26d7b8e6 100644 --- a/dev-packages/e2e-tests/test-applications/nestjs/src/test-module/test.controller.ts +++ b/dev-packages/e2e-tests/test-applications/nestjs/src/example-module/example.controller.ts @@ -1,12 +1,12 @@ import { Controller, Get } from '@nestjs/common'; -import { TestException } from './test.exception'; +import { ExampleException } from './example.exception'; @Controller('test-module') -export class TestController { +export class ExampleController { constructor() {} @Get() getTest(): string { - throw new TestException(); + throw new ExampleException(); } } diff --git a/dev-packages/e2e-tests/test-applications/nestjs/src/test-module/test.exception.ts b/dev-packages/e2e-tests/test-applications/nestjs/src/example-module/example.exception.ts similarity index 63% rename from dev-packages/e2e-tests/test-applications/nestjs/src/test-module/test.exception.ts rename to dev-packages/e2e-tests/test-applications/nestjs/src/example-module/example.exception.ts index b736596b6717..fed4684568b9 100644 --- a/dev-packages/e2e-tests/test-applications/nestjs/src/test-module/test.exception.ts +++ b/dev-packages/e2e-tests/test-applications/nestjs/src/example-module/example.exception.ts @@ -1,4 +1,4 @@ -export class TestException extends Error { +export class ExampleException extends Error { constructor() { super('Something went wrong in the test module!'); } diff --git a/dev-packages/e2e-tests/test-applications/nestjs/src/test-module/test.filter.ts b/dev-packages/e2e-tests/test-applications/nestjs/src/example-module/example.filter.ts similarity index 61% rename from dev-packages/e2e-tests/test-applications/nestjs/src/test-module/test.filter.ts rename to dev-packages/e2e-tests/test-applications/nestjs/src/example-module/example.filter.ts index 87a4ca0920e5..848441caf855 100644 --- a/dev-packages/e2e-tests/test-applications/nestjs/src/test-module/test.filter.ts +++ b/dev-packages/e2e-tests/test-applications/nestjs/src/example-module/example.filter.ts @@ -1,11 +1,11 @@ import { ArgumentsHost, BadRequestException, Catch } from '@nestjs/common'; import { BaseExceptionFilter } from '@nestjs/core'; -import { TestException } from './test.exception'; +import { ExampleException } from './example.exception'; -@Catch(TestException) -export class TestExceptionFilter extends BaseExceptionFilter { +@Catch(ExampleException) +export class ExampleExceptionFilter extends BaseExceptionFilter { catch(exception: unknown, host: ArgumentsHost) { - if (exception instanceof TestException) { + if (exception instanceof ExampleException) { return super.catch(new BadRequestException(exception.message), host); } return super.catch(exception, host); diff --git a/dev-packages/e2e-tests/test-applications/nestjs/src/example-module/example.module.ts b/dev-packages/e2e-tests/test-applications/nestjs/src/example-module/example.module.ts new file mode 100644 index 000000000000..fabd71c4df90 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/nestjs/src/example-module/example.module.ts @@ -0,0 +1,16 @@ +import { Module } from '@nestjs/common'; +import { APP_FILTER } from '@nestjs/core'; +import { ExampleController } from './example.controller'; +import { ExampleExceptionFilter } from './example.filter'; + +@Module({ + imports: [], + controllers: [ExampleController], + providers: [ + { + provide: APP_FILTER, + useClass: ExampleExceptionFilter, + }, + ], +}) +export class ExampleModule {} diff --git a/dev-packages/e2e-tests/test-applications/nestjs/src/test-module/test.module.ts b/dev-packages/e2e-tests/test-applications/nestjs/src/test-module/test.module.ts deleted file mode 100644 index 37b6dbe7e819..000000000000 --- a/dev-packages/e2e-tests/test-applications/nestjs/src/test-module/test.module.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { Module } from '@nestjs/common'; -import { APP_FILTER } from '@nestjs/core'; -import { TestController } from './test.controller'; -import { TestExceptionFilter } from './test.filter'; - -@Module({ - imports: [], - controllers: [TestController], - providers: [ - { - provide: APP_FILTER, - useClass: TestExceptionFilter, - }, - ], -}) -export class TestModule {} From 6eb6fc53324e18c0fd59ab776287f726a1b7b620 Mon Sep 17 00:00:00 2001 From: nicohrubec Date: Tue, 16 Jul 2024 10:42:34 +0200 Subject: [PATCH 31/60] Put comment back in that got lost --- packages/nestjs/src/setup.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/packages/nestjs/src/setup.ts b/packages/nestjs/src/setup.ts index a7798a053051..0be949ac19ac 100644 --- a/packages/nestjs/src/setup.ts +++ b/packages/nestjs/src/setup.ts @@ -101,6 +101,9 @@ export class SentryIntegrationService implements OnModuleInit { * Called when the SentryModuleIntegration gets initialized. */ public onModuleInit(): void { + // Sadly, NestInstrumentation has no requestHook, so we need to add the attributes here + // We register this hook in this method, because if we register it in the integration `setup`, + // it would always run even for users that are not even using Nest.js const client = getClient(); if (client) { client.on('spanStart', span => { From ae1d6d6e9fb120db5567498f9fbdab2f0ddab924 Mon Sep 17 00:00:00 2001 From: nicohrubec Date: Tue, 16 Jul 2024 11:23:49 +0200 Subject: [PATCH 32/60] . --- .../e2e-tests/test-applications/nestjs/src/app.module.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dev-packages/e2e-tests/test-applications/nestjs/src/app.module.ts b/dev-packages/e2e-tests/test-applications/nestjs/src/app.module.ts index b211a6edaae2..98138a7c13a3 100644 --- a/dev-packages/e2e-tests/test-applications/nestjs/src/app.module.ts +++ b/dev-packages/e2e-tests/test-applications/nestjs/src/app.module.ts @@ -6,7 +6,7 @@ import { AppService1, AppService2 } from './app.service'; import { ExampleModule } from './example-module/example.module'; @Module({ - imports: [SentryIntegrationModule.forRoot(), ScheduleModule.forRoot(), ExampleModule], // TODO: check if the order of registration matters + imports: [SentryIntegrationModule.forRoot(), ScheduleModule.forRoot(), ExampleModule], controllers: [AppController1], providers: [AppService1], }) From b50c3cb7291905a60c5d534763e960acc6283977 Mon Sep 17 00:00:00 2001 From: nicohrubec Date: Tue, 16 Jul 2024 11:34:00 +0200 Subject: [PATCH 33/60] Add debug logs --- .../nestjs/tests/transactions.test.ts | 14 ++++++++++++++ packages/nestjs/src/setup.ts | 3 +++ 2 files changed, 17 insertions(+) diff --git a/dev-packages/e2e-tests/test-applications/nestjs/tests/transactions.test.ts b/dev-packages/e2e-tests/test-applications/nestjs/tests/transactions.test.ts index ae5d8b63b16d..1553586d8bf3 100644 --- a/dev-packages/e2e-tests/test-applications/nestjs/tests/transactions.test.ts +++ b/dev-packages/e2e-tests/test-applications/nestjs/tests/transactions.test.ts @@ -9,10 +9,22 @@ test('Sends an API route transaction', async ({ baseURL }) => { ); }); + console.log('fetch'); + await fetch(`${baseURL}/test-transaction`); + console.log('waiting for transaction event'); + const transactionEvent = await pageloadTransactionEventPromise; + console.log('transaction spans: '); + console.log(transactionEvent.spans); + + console.log('other data:'); + console.log(transactionEvent.transaction); + console.log(transactionEvent.type); + console.log(transactionEvent.transaction_info); + expect(transactionEvent.contexts?.trace).toEqual({ data: { 'sentry.source': 'route', @@ -46,6 +58,8 @@ test('Sends an API route transaction', async ({ baseURL }) => { origin: 'auto.http.otel.http', }); + console.log('trace ook'); + expect(transactionEvent).toEqual( expect.objectContaining({ spans: expect.arrayContaining([ diff --git a/packages/nestjs/src/setup.ts b/packages/nestjs/src/setup.ts index 0be949ac19ac..fc054e055930 100644 --- a/packages/nestjs/src/setup.ts +++ b/packages/nestjs/src/setup.ts @@ -150,6 +150,9 @@ function addNestSpanAttributes(span: Span): void { return; } + console.log('setting span attributes: ') + console.log(span); + span.setAttributes({ [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.http.otel.nestjs', [SEMANTIC_ATTRIBUTE_SENTRY_OP]: `${type}.nestjs`, From f6f2ff5278e0625e46d59302130ba18516fcfdf0 Mon Sep 17 00:00:00 2001 From: nicohrubec Date: Tue, 16 Jul 2024 13:59:41 +0200 Subject: [PATCH 34/60] Use node nestIntegration --- .../nestjs/src/instrument.ts | 1 + .../nestjs/tests/transactions.test.ts | 5 ---- packages/nestjs/package.json | 3 +- packages/nestjs/src/index.ts | 2 +- packages/nestjs/src/setup.ts | 28 +------------------ 5 files changed, 4 insertions(+), 35 deletions(-) diff --git a/dev-packages/e2e-tests/test-applications/nestjs/src/instrument.ts b/dev-packages/e2e-tests/test-applications/nestjs/src/instrument.ts index b5ca047e497c..68e4eb76b4b0 100644 --- a/dev-packages/e2e-tests/test-applications/nestjs/src/instrument.ts +++ b/dev-packages/e2e-tests/test-applications/nestjs/src/instrument.ts @@ -6,4 +6,5 @@ Sentry.init({ tunnel: `http://localhost:3031/`, // proxy server tracesSampleRate: 1, tracePropagationTargets: ['http://localhost:3030', '/external-allowed'], + debug: true, }); diff --git a/dev-packages/e2e-tests/test-applications/nestjs/tests/transactions.test.ts b/dev-packages/e2e-tests/test-applications/nestjs/tests/transactions.test.ts index 1553586d8bf3..64bb12b0517d 100644 --- a/dev-packages/e2e-tests/test-applications/nestjs/tests/transactions.test.ts +++ b/dev-packages/e2e-tests/test-applications/nestjs/tests/transactions.test.ts @@ -20,11 +20,6 @@ test('Sends an API route transaction', async ({ baseURL }) => { console.log('transaction spans: '); console.log(transactionEvent.spans); - console.log('other data:'); - console.log(transactionEvent.transaction); - console.log(transactionEvent.type); - console.log(transactionEvent.transaction_info); - expect(transactionEvent.contexts?.trace).toEqual({ data: { 'sentry.source': 'route', diff --git a/packages/nestjs/package.json b/packages/nestjs/package.json index acf758d4e9d3..223b48fd3d05 100644 --- a/packages/nestjs/package.json +++ b/packages/nestjs/package.json @@ -42,8 +42,7 @@ "@sentry/core": "8.17.0", "@sentry/node": "8.17.0", "@sentry/types": "8.17.0", - "@sentry/utils": "8.17.0", - "@opentelemetry/instrumentation-nestjs-core": "0.39.0" + "@sentry/utils": "8.17.0" }, "devDependencies": { "@nestjs/core": "^10.3.10", diff --git a/packages/nestjs/src/index.ts b/packages/nestjs/src/index.ts index 74dd330d237e..f5c882e911cc 100644 --- a/packages/nestjs/src/index.ts +++ b/packages/nestjs/src/index.ts @@ -1,7 +1,7 @@ export * from '@sentry/node'; export { init } from './sdk'; -export { nestIntegration, SentryIntegrationModule } from './setup'; +export { SentryIntegrationModule } from './setup'; export { SentryTraced } from './span-decorator'; export { SentryCron } from './cron-decorator'; diff --git a/packages/nestjs/src/setup.ts b/packages/nestjs/src/setup.ts index fc054e055930..497fa45e6f9e 100644 --- a/packages/nestjs/src/setup.ts +++ b/packages/nestjs/src/setup.ts @@ -11,42 +11,19 @@ import { Injectable } from '@nestjs/common'; import { Global, Module } from '@nestjs/common'; import { APP_FILTER, BaseExceptionFilter } from '@nestjs/core'; import { APP_INTERCEPTOR } from '@nestjs/core'; -import { NestInstrumentation } from '@opentelemetry/instrumentation-nestjs-core'; import { SEMANTIC_ATTRIBUTE_SENTRY_OP, SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN, captureException, - defineIntegration, getClient, getDefaultIsolationScope, getIsolationScope, spanToJSON, } from '@sentry/core'; -import { generateInstrumentOnce } from '@sentry/node'; -import type { IntegrationFn, Span } from '@sentry/types'; +import type { Span } from '@sentry/types'; import { logger } from '@sentry/utils'; import type { Observable } from 'rxjs'; -const INTEGRATION_NAME = 'Nest'; - -export const instrumentNest = generateInstrumentOnce(INTEGRATION_NAME, () => new NestInstrumentation()); - -const _nestIntegration = (() => { - return { - name: INTEGRATION_NAME, - setupOnce() { - instrumentNest(); - }, - }; -}) satisfies IntegrationFn; - -/** - * Nest framework integration - * - * Capture tracing data for nest. - */ -export const nestIntegration = defineIntegration(_nestIntegration); - /** * */ @@ -150,9 +127,6 @@ function addNestSpanAttributes(span: Span): void { return; } - console.log('setting span attributes: ') - console.log(span); - span.setAttributes({ [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.http.otel.nestjs', [SEMANTIC_ATTRIBUTE_SENTRY_OP]: `${type}.nestjs`, From 215deee34b92acd9b5836ee268ad3df1a4309474 Mon Sep 17 00:00:00 2001 From: nicohrubec Date: Wed, 17 Jul 2024 10:47:07 +0200 Subject: [PATCH 35/60] More debug logs --- packages/nestjs/src/setup.ts | 11 +++++++++++ packages/node/src/integrations/tracing/nest.ts | 1 + packages/node/src/otel/instrument.ts | 2 ++ 3 files changed, 14 insertions(+) diff --git a/packages/nestjs/src/setup.ts b/packages/nestjs/src/setup.ts index 497fa45e6f9e..07329ab9e959 100644 --- a/packages/nestjs/src/setup.ts +++ b/packages/nestjs/src/setup.ts @@ -32,11 +32,14 @@ export class SentryTracingInterceptor implements NestInterceptor { * */ public intercept(context: ExecutionContext, next: CallHandler): Observable { + logger.log('intercept'); if (getIsolationScope() === getDefaultIsolationScope()) { logger.warn('Isolation scope is still the default isolation scope, skipping setting transactionName.'); return next.handle(); } + logger.log('intercept after scope check'); + if (context.getType() === 'http') { const req = context.switchToHttp().getRequest(); // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access @@ -78,11 +81,15 @@ export class SentryIntegrationService implements OnModuleInit { * Called when the SentryModuleIntegration gets initialized. */ public onModuleInit(): void { + logger.log('on module init'); + // Sadly, NestInstrumentation has no requestHook, so we need to add the attributes here // We register this hook in this method, because if we register it in the integration `setup`, // it would always run even for users that are not even using Nest.js const client = getClient(); + logger.log('client: ', client); if (client) { + logger.log('set nest attributes on client'); client.on('spanStart', span => { addNestSpanAttributes(span); }); @@ -98,6 +105,7 @@ export class SentryIntegrationModule { * Called by the user to set the module as root module in a nest application. */ public static forRoot(): DynamicModule { + logger.log('for root'); return { module: SentryIntegrationModule, providers: [ @@ -127,6 +135,9 @@ function addNestSpanAttributes(span: Span): void { return; } + logger.info('span: '); + logger.info(span); + span.setAttributes({ [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.http.otel.nestjs', [SEMANTIC_ATTRIBUTE_SENTRY_OP]: `${type}.nestjs`, diff --git a/packages/node/src/integrations/tracing/nest.ts b/packages/node/src/integrations/tracing/nest.ts index ab6a66fdb895..dec698f0fc06 100644 --- a/packages/node/src/integrations/tracing/nest.ts +++ b/packages/node/src/integrations/tracing/nest.ts @@ -47,6 +47,7 @@ const INTEGRATION_NAME = 'Nest'; export const instrumentNest = generateInstrumentOnce(INTEGRATION_NAME, () => new NestInstrumentation()); const _nestIntegration = (() => { + logger.log('nest integration'); return { name: INTEGRATION_NAME, setupOnce() { diff --git a/packages/node/src/otel/instrument.ts b/packages/node/src/otel/instrument.ts index 5db7960e4019..cc717046f4d4 100644 --- a/packages/node/src/otel/instrument.ts +++ b/packages/node/src/otel/instrument.ts @@ -1,5 +1,6 @@ import type { Instrumentation } from '@opentelemetry/instrumentation'; import { addOpenTelemetryInstrumentation } from '@sentry/opentelemetry'; +import {logger} from '@sentry/utils'; const INSTRUMENTED: Record = {}; @@ -11,6 +12,7 @@ export function generateInstrumentOnce( name: string, creator: (options?: Options) => Instrumentation, ): ((options?: Options) => void) & { id: string } { + logger.log('calling generateInstrumentOnce for: ', name); return Object.assign( (options?: Options) => { const instrumented = INSTRUMENTED[name]; From aae09ba8b4e62e5b6e8315f136f8b48289282133 Mon Sep 17 00:00:00 2001 From: nicohrubec Date: Wed, 17 Jul 2024 17:45:25 +0200 Subject: [PATCH 36/60] Fix e2e tests I think --- .../nestjs/src/app.module.ts | 2 +- packages/nestjs/.gitignore | 2 ++ packages/nestjs/package.json | 27 +++++++++++-------- packages/nestjs/rollup.npm.config.mjs | 6 ++++- packages/nestjs/src/index.ts | 1 - packages/nestjs/tsconfig.setup-types.json | 15 +++++++++++ packages/nestjs/tsconfig.types.json | 5 +++- packages/node/src/otel/instrument.ts | 2 +- 8 files changed, 44 insertions(+), 16 deletions(-) create mode 100644 packages/nestjs/.gitignore create mode 100644 packages/nestjs/tsconfig.setup-types.json diff --git a/dev-packages/e2e-tests/test-applications/nestjs/src/app.module.ts b/dev-packages/e2e-tests/test-applications/nestjs/src/app.module.ts index 98138a7c13a3..46ba0bfb9e6d 100644 --- a/dev-packages/e2e-tests/test-applications/nestjs/src/app.module.ts +++ b/dev-packages/e2e-tests/test-applications/nestjs/src/app.module.ts @@ -1,6 +1,6 @@ import { Module } from '@nestjs/common'; import { ScheduleModule } from '@nestjs/schedule'; -import { SentryIntegrationModule } from '@sentry/nestjs'; +import { SentryIntegrationModule } from '@sentry/nestjs/setup'; import { AppController1, AppController2 } from './app.controller'; import { AppService1, AppService2 } from './app.service'; import { ExampleModule } from './example-module/example.module'; diff --git a/packages/nestjs/.gitignore b/packages/nestjs/.gitignore new file mode 100644 index 000000000000..3e89751310a5 --- /dev/null +++ b/packages/nestjs/.gitignore @@ -0,0 +1,2 @@ +/*.d.ts +/*.d.ts.map diff --git a/packages/nestjs/package.json b/packages/nestjs/package.json index 223b48fd3d05..f9c1449e3a63 100644 --- a/packages/nestjs/package.json +++ b/packages/nestjs/package.json @@ -10,7 +10,9 @@ "node": ">=16" }, "files": [ - "/build" + "/build", + "/*.d.ts", + "/*.d.ts.map" ], "main": "build/cjs/nestjs/index.js", "module": "build/esm/nestjs/index.js", @@ -26,13 +28,16 @@ "types": "./build/types/index.d.ts", "default": "./build/cjs/index.js" } - } - }, - "typesVersions": { - "<4.9": { - "build/types/index.d.ts": [ - "build/types-ts3.8/index.d.ts" - ] + }, + "./setup": { + "import": { + "types": "./setup.d.ts", + "default": "./build/esm/setup.js" + }, + "require": { + "types": "./setup.d.ts", + "default": "./build/cjs/setup.js" + } } }, "publishConfig": { @@ -56,15 +61,15 @@ "build": "run-p build:transpile build:types", "build:dev": "yarn build", "build:transpile": "rollup -c rollup.npm.config.mjs", - "build:types": "run-s build:types:core build:types:downlevel", + "build:types": "run-s build:types:core build:types:setup", "build:types:core": "tsc -p tsconfig.types.json", - "build:types:downlevel": "yarn downlevel-dts build/types build/types-ts3.8 --to ts3.8", + "build:types:setup": "tsc -p tsconfig.setup-types.json", "build:watch": "run-p build:transpile:watch build:types:watch", "build:dev:watch": "yarn build:watch", "build:transpile:watch": "rollup -c rollup.npm.config.mjs --watch", "build:types:watch": "tsc -p tsconfig.types.json --watch", "build:tarball": "npm pack", - "circularDepCheck": "madge --circular src/index.ts", + "circularDepCheck": "madge --circular src/index.ts && madge --circular src/setup.ts", "clean": "rimraf build coverage sentry-node-*.tgz", "fix": "eslint . --format stylish --fix", "lint": "eslint . --format stylish", diff --git a/packages/nestjs/rollup.npm.config.mjs b/packages/nestjs/rollup.npm.config.mjs index 84a06f2fb64a..0ce71546935c 100644 --- a/packages/nestjs/rollup.npm.config.mjs +++ b/packages/nestjs/rollup.npm.config.mjs @@ -1,3 +1,7 @@ import { makeBaseNPMConfig, makeNPMConfigVariants } from '@sentry-internal/rollup-utils'; -export default makeNPMConfigVariants(makeBaseNPMConfig()); +export default makeNPMConfigVariants( + makeBaseNPMConfig({ + entrypoints: ['src/index.ts', 'src/setup.ts'], + }), +); diff --git a/packages/nestjs/src/index.ts b/packages/nestjs/src/index.ts index f5c882e911cc..00519cf49b9e 100644 --- a/packages/nestjs/src/index.ts +++ b/packages/nestjs/src/index.ts @@ -1,7 +1,6 @@ export * from '@sentry/node'; export { init } from './sdk'; -export { SentryIntegrationModule } from './setup'; export { SentryTraced } from './span-decorator'; export { SentryCron } from './cron-decorator'; diff --git a/packages/nestjs/tsconfig.setup-types.json b/packages/nestjs/tsconfig.setup-types.json new file mode 100644 index 000000000000..b4534f32cf94 --- /dev/null +++ b/packages/nestjs/tsconfig.setup-types.json @@ -0,0 +1,15 @@ +{ + "extends": "./tsconfig.json", + + "compilerOptions": { + "declaration": true, + "declarationMap": true, + "emitDeclarationOnly": true, + "outDir": "./" + }, + + "//": "This type is built separately because it is for a subpath export, which has problems if it is not in the root", + "include": ["src/setup.ts"], + "//": "Without this, we cannot output into the root dir", + "exclude": [] +} diff --git a/packages/nestjs/tsconfig.types.json b/packages/nestjs/tsconfig.types.json index 65455f66bd75..6240cd92efaa 100644 --- a/packages/nestjs/tsconfig.types.json +++ b/packages/nestjs/tsconfig.types.json @@ -6,5 +6,8 @@ "declarationMap": true, "emitDeclarationOnly": true, "outDir": "build/types" - } + }, + + "//": "This is built separately in tsconfig.setup-types.json", + "exclude": ["src/setup.ts"] } diff --git a/packages/node/src/otel/instrument.ts b/packages/node/src/otel/instrument.ts index cc717046f4d4..67b48d419035 100644 --- a/packages/node/src/otel/instrument.ts +++ b/packages/node/src/otel/instrument.ts @@ -1,6 +1,6 @@ import type { Instrumentation } from '@opentelemetry/instrumentation'; import { addOpenTelemetryInstrumentation } from '@sentry/opentelemetry'; -import {logger} from '@sentry/utils'; +import { logger } from '@sentry/utils'; const INSTRUMENTED: Record = {}; From 56d3e8f18dc2be549094173576caf765b48d33d7 Mon Sep 17 00:00:00 2001 From: nicohrubec Date: Wed, 17 Jul 2024 17:55:01 +0200 Subject: [PATCH 37/60] Add eslintignore --- packages/nestjs/.eslintignore | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 packages/nestjs/.eslintignore diff --git a/packages/nestjs/.eslintignore b/packages/nestjs/.eslintignore new file mode 100644 index 000000000000..3e89751310a5 --- /dev/null +++ b/packages/nestjs/.eslintignore @@ -0,0 +1,2 @@ +/*.d.ts +/*.d.ts.map From 11adb8ff52d58828912fe91d8902a40a3b6a1612 Mon Sep 17 00:00:00 2001 From: nicohrubec Date: Wed, 17 Jul 2024 18:07:27 +0200 Subject: [PATCH 38/60] Rename example module back to test module --- .../test-applications/nestjs/src/app.module.ts | 4 ++-- .../nestjs/src/example-module/example.module.ts | 16 ---------------- .../test.controller.ts} | 6 +++--- .../test.exception.ts} | 2 +- .../test.filter.ts} | 8 ++++---- .../nestjs/src/test-module/test.module.ts | 16 ++++++++++++++++ 6 files changed, 26 insertions(+), 26 deletions(-) delete mode 100644 dev-packages/e2e-tests/test-applications/nestjs/src/example-module/example.module.ts rename dev-packages/e2e-tests/test-applications/nestjs/src/{example-module/example.controller.ts => test-module/test.controller.ts} (52%) rename dev-packages/e2e-tests/test-applications/nestjs/src/{example-module/example.exception.ts => test-module/test.exception.ts} (63%) rename dev-packages/e2e-tests/test-applications/nestjs/src/{example-module/example.filter.ts => test-module/test.filter.ts} (61%) create mode 100644 dev-packages/e2e-tests/test-applications/nestjs/src/test-module/test.module.ts diff --git a/dev-packages/e2e-tests/test-applications/nestjs/src/app.module.ts b/dev-packages/e2e-tests/test-applications/nestjs/src/app.module.ts index 46ba0bfb9e6d..a609d417ed5e 100644 --- a/dev-packages/e2e-tests/test-applications/nestjs/src/app.module.ts +++ b/dev-packages/e2e-tests/test-applications/nestjs/src/app.module.ts @@ -3,10 +3,10 @@ import { ScheduleModule } from '@nestjs/schedule'; import { SentryIntegrationModule } from '@sentry/nestjs/setup'; import { AppController1, AppController2 } from './app.controller'; import { AppService1, AppService2 } from './app.service'; -import { ExampleModule } from './example-module/example.module'; +import { TestModule } from './test-module/test.module'; @Module({ - imports: [SentryIntegrationModule.forRoot(), ScheduleModule.forRoot(), ExampleModule], + imports: [SentryIntegrationModule.forRoot(), ScheduleModule.forRoot(), TestModule], controllers: [AppController1], providers: [AppService1], }) diff --git a/dev-packages/e2e-tests/test-applications/nestjs/src/example-module/example.module.ts b/dev-packages/e2e-tests/test-applications/nestjs/src/example-module/example.module.ts deleted file mode 100644 index fabd71c4df90..000000000000 --- a/dev-packages/e2e-tests/test-applications/nestjs/src/example-module/example.module.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { Module } from '@nestjs/common'; -import { APP_FILTER } from '@nestjs/core'; -import { ExampleController } from './example.controller'; -import { ExampleExceptionFilter } from './example.filter'; - -@Module({ - imports: [], - controllers: [ExampleController], - providers: [ - { - provide: APP_FILTER, - useClass: ExampleExceptionFilter, - }, - ], -}) -export class ExampleModule {} diff --git a/dev-packages/e2e-tests/test-applications/nestjs/src/example-module/example.controller.ts b/dev-packages/e2e-tests/test-applications/nestjs/src/test-module/test.controller.ts similarity index 52% rename from dev-packages/e2e-tests/test-applications/nestjs/src/example-module/example.controller.ts rename to dev-packages/e2e-tests/test-applications/nestjs/src/test-module/test.controller.ts index a6ad26d7b8e6..150fb0e07546 100644 --- a/dev-packages/e2e-tests/test-applications/nestjs/src/example-module/example.controller.ts +++ b/dev-packages/e2e-tests/test-applications/nestjs/src/test-module/test.controller.ts @@ -1,12 +1,12 @@ import { Controller, Get } from '@nestjs/common'; -import { ExampleException } from './example.exception'; +import { TestException } from './test.exception'; @Controller('test-module') -export class ExampleController { +export class TestController { constructor() {} @Get() getTest(): string { - throw new ExampleException(); + throw new TestException(); } } diff --git a/dev-packages/e2e-tests/test-applications/nestjs/src/example-module/example.exception.ts b/dev-packages/e2e-tests/test-applications/nestjs/src/test-module/test.exception.ts similarity index 63% rename from dev-packages/e2e-tests/test-applications/nestjs/src/example-module/example.exception.ts rename to dev-packages/e2e-tests/test-applications/nestjs/src/test-module/test.exception.ts index fed4684568b9..b736596b6717 100644 --- a/dev-packages/e2e-tests/test-applications/nestjs/src/example-module/example.exception.ts +++ b/dev-packages/e2e-tests/test-applications/nestjs/src/test-module/test.exception.ts @@ -1,4 +1,4 @@ -export class ExampleException extends Error { +export class TestException extends Error { constructor() { super('Something went wrong in the test module!'); } diff --git a/dev-packages/e2e-tests/test-applications/nestjs/src/example-module/example.filter.ts b/dev-packages/e2e-tests/test-applications/nestjs/src/test-module/test.filter.ts similarity index 61% rename from dev-packages/e2e-tests/test-applications/nestjs/src/example-module/example.filter.ts rename to dev-packages/e2e-tests/test-applications/nestjs/src/test-module/test.filter.ts index 848441caf855..87a4ca0920e5 100644 --- a/dev-packages/e2e-tests/test-applications/nestjs/src/example-module/example.filter.ts +++ b/dev-packages/e2e-tests/test-applications/nestjs/src/test-module/test.filter.ts @@ -1,11 +1,11 @@ import { ArgumentsHost, BadRequestException, Catch } from '@nestjs/common'; import { BaseExceptionFilter } from '@nestjs/core'; -import { ExampleException } from './example.exception'; +import { TestException } from './test.exception'; -@Catch(ExampleException) -export class ExampleExceptionFilter extends BaseExceptionFilter { +@Catch(TestException) +export class TestExceptionFilter extends BaseExceptionFilter { catch(exception: unknown, host: ArgumentsHost) { - if (exception instanceof ExampleException) { + if (exception instanceof TestException) { return super.catch(new BadRequestException(exception.message), host); } return super.catch(exception, host); diff --git a/dev-packages/e2e-tests/test-applications/nestjs/src/test-module/test.module.ts b/dev-packages/e2e-tests/test-applications/nestjs/src/test-module/test.module.ts new file mode 100644 index 000000000000..37b6dbe7e819 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/nestjs/src/test-module/test.module.ts @@ -0,0 +1,16 @@ +import { Module } from '@nestjs/common'; +import { APP_FILTER } from '@nestjs/core'; +import { TestController } from './test.controller'; +import { TestExceptionFilter } from './test.filter'; + +@Module({ + imports: [], + controllers: [TestController], + providers: [ + { + provide: APP_FILTER, + useClass: TestExceptionFilter, + }, + ], +}) +export class TestModule {} From 116f0db49040c9c331b267710435c8eb16432565 Mon Sep 17 00:00:00 2001 From: nicohrubec Date: Wed, 17 Jul 2024 18:12:40 +0200 Subject: [PATCH 39/60] Remove logs --- .../test-applications/nestjs/src/instrument.ts | 1 - .../nestjs/tests/transactions.test.ts | 9 --------- packages/nestjs/src/setup.ts | 11 ----------- packages/node/src/integrations/tracing/nest.ts | 1 - packages/node/src/otel/instrument.ts | 2 -- 5 files changed, 24 deletions(-) diff --git a/dev-packages/e2e-tests/test-applications/nestjs/src/instrument.ts b/dev-packages/e2e-tests/test-applications/nestjs/src/instrument.ts index 68e4eb76b4b0..b5ca047e497c 100644 --- a/dev-packages/e2e-tests/test-applications/nestjs/src/instrument.ts +++ b/dev-packages/e2e-tests/test-applications/nestjs/src/instrument.ts @@ -6,5 +6,4 @@ Sentry.init({ tunnel: `http://localhost:3031/`, // proxy server tracesSampleRate: 1, tracePropagationTargets: ['http://localhost:3030', '/external-allowed'], - debug: true, }); diff --git a/dev-packages/e2e-tests/test-applications/nestjs/tests/transactions.test.ts b/dev-packages/e2e-tests/test-applications/nestjs/tests/transactions.test.ts index 64bb12b0517d..ae5d8b63b16d 100644 --- a/dev-packages/e2e-tests/test-applications/nestjs/tests/transactions.test.ts +++ b/dev-packages/e2e-tests/test-applications/nestjs/tests/transactions.test.ts @@ -9,17 +9,10 @@ test('Sends an API route transaction', async ({ baseURL }) => { ); }); - console.log('fetch'); - await fetch(`${baseURL}/test-transaction`); - console.log('waiting for transaction event'); - const transactionEvent = await pageloadTransactionEventPromise; - console.log('transaction spans: '); - console.log(transactionEvent.spans); - expect(transactionEvent.contexts?.trace).toEqual({ data: { 'sentry.source': 'route', @@ -53,8 +46,6 @@ test('Sends an API route transaction', async ({ baseURL }) => { origin: 'auto.http.otel.http', }); - console.log('trace ook'); - expect(transactionEvent).toEqual( expect.objectContaining({ spans: expect.arrayContaining([ diff --git a/packages/nestjs/src/setup.ts b/packages/nestjs/src/setup.ts index 07329ab9e959..497fa45e6f9e 100644 --- a/packages/nestjs/src/setup.ts +++ b/packages/nestjs/src/setup.ts @@ -32,14 +32,11 @@ export class SentryTracingInterceptor implements NestInterceptor { * */ public intercept(context: ExecutionContext, next: CallHandler): Observable { - logger.log('intercept'); if (getIsolationScope() === getDefaultIsolationScope()) { logger.warn('Isolation scope is still the default isolation scope, skipping setting transactionName.'); return next.handle(); } - logger.log('intercept after scope check'); - if (context.getType() === 'http') { const req = context.switchToHttp().getRequest(); // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access @@ -81,15 +78,11 @@ export class SentryIntegrationService implements OnModuleInit { * Called when the SentryModuleIntegration gets initialized. */ public onModuleInit(): void { - logger.log('on module init'); - // Sadly, NestInstrumentation has no requestHook, so we need to add the attributes here // We register this hook in this method, because if we register it in the integration `setup`, // it would always run even for users that are not even using Nest.js const client = getClient(); - logger.log('client: ', client); if (client) { - logger.log('set nest attributes on client'); client.on('spanStart', span => { addNestSpanAttributes(span); }); @@ -105,7 +98,6 @@ export class SentryIntegrationModule { * Called by the user to set the module as root module in a nest application. */ public static forRoot(): DynamicModule { - logger.log('for root'); return { module: SentryIntegrationModule, providers: [ @@ -135,9 +127,6 @@ function addNestSpanAttributes(span: Span): void { return; } - logger.info('span: '); - logger.info(span); - span.setAttributes({ [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.http.otel.nestjs', [SEMANTIC_ATTRIBUTE_SENTRY_OP]: `${type}.nestjs`, diff --git a/packages/node/src/integrations/tracing/nest.ts b/packages/node/src/integrations/tracing/nest.ts index dec698f0fc06..ab6a66fdb895 100644 --- a/packages/node/src/integrations/tracing/nest.ts +++ b/packages/node/src/integrations/tracing/nest.ts @@ -47,7 +47,6 @@ const INTEGRATION_NAME = 'Nest'; export const instrumentNest = generateInstrumentOnce(INTEGRATION_NAME, () => new NestInstrumentation()); const _nestIntegration = (() => { - logger.log('nest integration'); return { name: INTEGRATION_NAME, setupOnce() { diff --git a/packages/node/src/otel/instrument.ts b/packages/node/src/otel/instrument.ts index 67b48d419035..5db7960e4019 100644 --- a/packages/node/src/otel/instrument.ts +++ b/packages/node/src/otel/instrument.ts @@ -1,6 +1,5 @@ import type { Instrumentation } from '@opentelemetry/instrumentation'; import { addOpenTelemetryInstrumentation } from '@sentry/opentelemetry'; -import { logger } from '@sentry/utils'; const INSTRUMENTED: Record = {}; @@ -12,7 +11,6 @@ export function generateInstrumentOnce( name: string, creator: (options?: Options) => Instrumentation, ): ((options?: Options) => void) & { id: string } { - logger.log('calling generateInstrumentOnce for: ', name); return Object.assign( (options?: Options) => { const instrumented = INSTRUMENTED[name]; From 6bfbdf09e081e6cf529623ea6a5fe36ad39945aa Mon Sep 17 00:00:00 2001 From: nicohrubec Date: Thu, 18 Jul 2024 12:50:56 +0200 Subject: [PATCH 40/60] Update nest setup to root module in tests --- .../test-applications/nestjs-basic/src/app.module.ts | 3 ++- .../e2e-tests/test-applications/nestjs-basic/src/main.ts | 7 +------ .../nestjs-distributed-tracing/src/main.ts | 7 +------ .../src/trace-initiator.module.ts | 3 ++- .../nestjs-with-submodules/src/app.module.ts | 3 ++- .../src/example-module/example.controller.ts | 2 +- .../test-applications/nestjs-with-submodules/src/main.ts | 7 +------ .../nestjs-with-submodules/tests/errors.test.ts | 8 ++++---- 8 files changed, 14 insertions(+), 26 deletions(-) diff --git a/dev-packages/e2e-tests/test-applications/nestjs-basic/src/app.module.ts b/dev-packages/e2e-tests/test-applications/nestjs-basic/src/app.module.ts index ceb7199a99cf..e0b4bd4b43b6 100644 --- a/dev-packages/e2e-tests/test-applications/nestjs-basic/src/app.module.ts +++ b/dev-packages/e2e-tests/test-applications/nestjs-basic/src/app.module.ts @@ -2,9 +2,10 @@ import { Module } from '@nestjs/common'; import { ScheduleModule } from '@nestjs/schedule'; import { AppController } from './app.controller'; import { AppService } from './app.service'; +import {SentryIntegrationModule} from "@sentry/nestjs/setup"; @Module({ - imports: [ScheduleModule.forRoot()], + imports: [SentryIntegrationModule.forRoot(), ScheduleModule.forRoot()], controllers: [AppController], providers: [AppService], }) diff --git a/dev-packages/e2e-tests/test-applications/nestjs-basic/src/main.ts b/dev-packages/e2e-tests/test-applications/nestjs-basic/src/main.ts index 3a7b5ded8645..71ce685f4d61 100644 --- a/dev-packages/e2e-tests/test-applications/nestjs-basic/src/main.ts +++ b/dev-packages/e2e-tests/test-applications/nestjs-basic/src/main.ts @@ -2,18 +2,13 @@ import './instrument'; // Import other modules -import { BaseExceptionFilter, HttpAdapterHost, NestFactory } from '@nestjs/core'; -import * as Sentry from '@sentry/nestjs'; +import { NestFactory } from '@nestjs/core'; import { AppModule } from './app.module'; const PORT = 3030; async function bootstrap() { const app = await NestFactory.create(AppModule); - - const { httpAdapter } = app.get(HttpAdapterHost); - Sentry.setupNestErrorHandler(app, new BaseExceptionFilter(httpAdapter)); - await app.listen(PORT); } 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 83d0b33d687d..5aad5748b244 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 @@ -2,8 +2,7 @@ import './instrument'; // Import other modules -import { BaseExceptionFilter, HttpAdapterHost, NestFactory } from '@nestjs/core'; -import * as Sentry from '@sentry/nestjs'; +import { NestFactory } from '@nestjs/core'; import { TraceInitiatorModule } from './trace-initiator.module'; import { TraceReceiverModule } from './trace-receiver.module'; @@ -12,10 +11,6 @@ const TRACE_RECEIVER_PORT = 3040; async function bootstrap() { const trace_initiator_app = await NestFactory.create(TraceInitiatorModule); - - const { httpAdapter } = trace_initiator_app.get(HttpAdapterHost); - Sentry.setupNestErrorHandler(trace_initiator_app, new BaseExceptionFilter(httpAdapter)); - await trace_initiator_app.listen(TRACE_INITIATOR_PORT); const trace_receiver_app = await NestFactory.create(TraceReceiverModule); diff --git a/dev-packages/e2e-tests/test-applications/nestjs-distributed-tracing/src/trace-initiator.module.ts b/dev-packages/e2e-tests/test-applications/nestjs-distributed-tracing/src/trace-initiator.module.ts index 9256f29928ab..0fa7a7a25175 100644 --- a/dev-packages/e2e-tests/test-applications/nestjs-distributed-tracing/src/trace-initiator.module.ts +++ b/dev-packages/e2e-tests/test-applications/nestjs-distributed-tracing/src/trace-initiator.module.ts @@ -1,9 +1,10 @@ import { Module } from '@nestjs/common'; import { TraceInitiatorController } from './trace-initiator.controller'; import { TraceInitiatorService } from './trace-initiator.service'; +import {SentryIntegrationModule} from "@sentry/nestjs/setup"; @Module({ - imports: [], + imports: [SentryIntegrationModule.forRoot()], controllers: [TraceInitiatorController], providers: [TraceInitiatorService], }) diff --git a/dev-packages/e2e-tests/test-applications/nestjs-with-submodules/src/app.module.ts b/dev-packages/e2e-tests/test-applications/nestjs-with-submodules/src/app.module.ts index 944b84e66d27..d96f80ca49c9 100644 --- a/dev-packages/e2e-tests/test-applications/nestjs-with-submodules/src/app.module.ts +++ b/dev-packages/e2e-tests/test-applications/nestjs-with-submodules/src/app.module.ts @@ -2,9 +2,10 @@ import { Module } from '@nestjs/common'; import { AppController } from './app.controller'; import { AppService } from './app.service'; import { ExampleModule } from './example-module/example.module'; +import { SentryIntegrationModule } from '@sentry/nestjs/setup'; @Module({ - imports: [ExampleModule], + imports: [SentryIntegrationModule.forRoot(), ExampleModule], controllers: [AppController], providers: [AppService], }) diff --git a/dev-packages/e2e-tests/test-applications/nestjs-with-submodules/src/example-module/example.controller.ts b/dev-packages/e2e-tests/test-applications/nestjs-with-submodules/src/example-module/example.controller.ts index b71179c195cb..ab10703510b7 100644 --- a/dev-packages/e2e-tests/test-applications/nestjs-with-submodules/src/example-module/example.controller.ts +++ b/dev-packages/e2e-tests/test-applications/nestjs-with-submodules/src/example-module/example.controller.ts @@ -6,7 +6,7 @@ export class ExampleController { constructor() {} @Get() - getTest(): string { + getExampleException(): string { throw new ExampleException(); } } diff --git a/dev-packages/e2e-tests/test-applications/nestjs-with-submodules/src/main.ts b/dev-packages/e2e-tests/test-applications/nestjs-with-submodules/src/main.ts index 3a7b5ded8645..71ce685f4d61 100644 --- a/dev-packages/e2e-tests/test-applications/nestjs-with-submodules/src/main.ts +++ b/dev-packages/e2e-tests/test-applications/nestjs-with-submodules/src/main.ts @@ -2,18 +2,13 @@ import './instrument'; // Import other modules -import { BaseExceptionFilter, HttpAdapterHost, NestFactory } from '@nestjs/core'; -import * as Sentry from '@sentry/nestjs'; +import { NestFactory } from '@nestjs/core'; import { AppModule } from './app.module'; const PORT = 3030; async function bootstrap() { const app = await NestFactory.create(AppModule); - - const { httpAdapter } = app.get(HttpAdapterHost); - Sentry.setupNestErrorHandler(app, new BaseExceptionFilter(httpAdapter)); - await app.listen(PORT); } diff --git a/dev-packages/e2e-tests/test-applications/nestjs-with-submodules/tests/errors.test.ts b/dev-packages/e2e-tests/test-applications/nestjs-with-submodules/tests/errors.test.ts index 3e10f6e1d26e..4c20d16ee91b 100644 --- a/dev-packages/e2e-tests/test-applications/nestjs-with-submodules/tests/errors.test.ts +++ b/dev-packages/e2e-tests/test-applications/nestjs-with-submodules/tests/errors.test.ts @@ -7,18 +7,18 @@ test('Handles exception correctly and does not send to Sentry if exception is th let errorEventOccurred = false; waitForError('nestjs', event => { - if (!event.type && event.exception?.values?.[0]?.value === 'Something went wrong in the test module!') { + if (!event.type && event.exception?.values?.[0]?.value === 'Something went wrong in the example module!') { errorEventOccurred = true; } - return event?.transaction === 'GET /test-module'; + return event?.transaction === 'GET /example-module'; }); const transactionEventPromise = waitForTransaction('nestjs', transactionEvent => { - return transactionEvent?.transaction === 'GET /test-module'; + return transactionEvent?.transaction === 'GET /example-module'; }); - const response = await fetch(`${baseURL}/test-module`); + const response = await fetch(`${baseURL}/example-module`); expect(response.status).toBe(400); await transactionEventPromise; From 0170f143b009bec4aafa7c7fd33e2c473444cea1 Mon Sep 17 00:00:00 2001 From: nicohrubec Date: Thu, 18 Jul 2024 14:04:09 +0200 Subject: [PATCH 41/60] Put in nestjs sample applications testing old setup --- .github/workflows/build.yml | 2 + .../node-nestjs-basic/.gitignore | 56 +++ .../node-nestjs-basic/.npmrc | 2 + .../node-nestjs-basic/nest-cli.json | 8 + .../node-nestjs-basic/package.json | 48 +++ .../node-nestjs-basic/playwright.config.mjs | 7 + .../node-nestjs-basic/src/app.controller.ts | 37 ++ .../node-nestjs-basic/src/app.module.ts | 11 + .../node-nestjs-basic/src/app.service.ts | 67 ++++ .../node-nestjs-basic/src/instrument.ts | 8 + .../node-nestjs-basic/src/main.ts | 20 + .../node-nestjs-basic/start-event-proxy.mjs | 6 + .../tests/cron-decorator.test.ts | 34 ++ .../node-nestjs-basic/tests/errors.test.ts | 55 +++ .../tests/span-decorator.test.ts | 72 ++++ .../tests/transactions.test.ts | 123 ++++++ .../node-nestjs-basic/tsconfig.build.json | 4 + .../node-nestjs-basic/tsconfig.json | 21 ++ .../.gitignore | 56 +++ .../node-nestjs-distributed-tracing/.npmrc | 2 + .../nest-cli.json | 8 + .../package.json | 47 +++ .../playwright.config.mjs | 7 + .../src/instrument.ts | 9 + .../src/main.ts | 23 ++ .../src/trace-initiator.controller.ts | 42 +++ .../src/trace-initiator.module.ts | 10 + .../src/trace-initiator.service.ts | 47 +++ .../src/trace-receiver.controller.ts | 17 + .../src/trace-receiver.module.ts | 10 + .../src/trace-receiver.service.ts | 18 + .../src/utils.ts | 26 ++ .../start-event-proxy.mjs | 6 + .../tests/propagation.test.ts | 356 ++++++++++++++++++ .../tsconfig.build.json | 4 + .../tsconfig.json | 21 ++ 36 files changed, 1290 insertions(+) create mode 100644 dev-packages/e2e-tests/test-applications/node-nestjs-basic/.gitignore create mode 100644 dev-packages/e2e-tests/test-applications/node-nestjs-basic/.npmrc create mode 100644 dev-packages/e2e-tests/test-applications/node-nestjs-basic/nest-cli.json create mode 100644 dev-packages/e2e-tests/test-applications/node-nestjs-basic/package.json create mode 100644 dev-packages/e2e-tests/test-applications/node-nestjs-basic/playwright.config.mjs create mode 100644 dev-packages/e2e-tests/test-applications/node-nestjs-basic/src/app.controller.ts create mode 100644 dev-packages/e2e-tests/test-applications/node-nestjs-basic/src/app.module.ts create mode 100644 dev-packages/e2e-tests/test-applications/node-nestjs-basic/src/app.service.ts create mode 100644 dev-packages/e2e-tests/test-applications/node-nestjs-basic/src/instrument.ts create mode 100644 dev-packages/e2e-tests/test-applications/node-nestjs-basic/src/main.ts create mode 100644 dev-packages/e2e-tests/test-applications/node-nestjs-basic/start-event-proxy.mjs create mode 100644 dev-packages/e2e-tests/test-applications/node-nestjs-basic/tests/cron-decorator.test.ts create mode 100644 dev-packages/e2e-tests/test-applications/node-nestjs-basic/tests/errors.test.ts create mode 100644 dev-packages/e2e-tests/test-applications/node-nestjs-basic/tests/span-decorator.test.ts create mode 100644 dev-packages/e2e-tests/test-applications/node-nestjs-basic/tests/transactions.test.ts create mode 100644 dev-packages/e2e-tests/test-applications/node-nestjs-basic/tsconfig.build.json create mode 100644 dev-packages/e2e-tests/test-applications/node-nestjs-basic/tsconfig.json create mode 100644 dev-packages/e2e-tests/test-applications/node-nestjs-distributed-tracing/.gitignore create mode 100644 dev-packages/e2e-tests/test-applications/node-nestjs-distributed-tracing/.npmrc create mode 100644 dev-packages/e2e-tests/test-applications/node-nestjs-distributed-tracing/nest-cli.json create mode 100644 dev-packages/e2e-tests/test-applications/node-nestjs-distributed-tracing/package.json create mode 100644 dev-packages/e2e-tests/test-applications/node-nestjs-distributed-tracing/playwright.config.mjs create mode 100644 dev-packages/e2e-tests/test-applications/node-nestjs-distributed-tracing/src/instrument.ts create mode 100644 dev-packages/e2e-tests/test-applications/node-nestjs-distributed-tracing/src/main.ts create mode 100644 dev-packages/e2e-tests/test-applications/node-nestjs-distributed-tracing/src/trace-initiator.controller.ts create mode 100644 dev-packages/e2e-tests/test-applications/node-nestjs-distributed-tracing/src/trace-initiator.module.ts create mode 100644 dev-packages/e2e-tests/test-applications/node-nestjs-distributed-tracing/src/trace-initiator.service.ts create mode 100644 dev-packages/e2e-tests/test-applications/node-nestjs-distributed-tracing/src/trace-receiver.controller.ts create mode 100644 dev-packages/e2e-tests/test-applications/node-nestjs-distributed-tracing/src/trace-receiver.module.ts create mode 100644 dev-packages/e2e-tests/test-applications/node-nestjs-distributed-tracing/src/trace-receiver.service.ts create mode 100644 dev-packages/e2e-tests/test-applications/node-nestjs-distributed-tracing/src/utils.ts create mode 100644 dev-packages/e2e-tests/test-applications/node-nestjs-distributed-tracing/start-event-proxy.mjs create mode 100644 dev-packages/e2e-tests/test-applications/node-nestjs-distributed-tracing/tests/propagation.test.ts create mode 100644 dev-packages/e2e-tests/test-applications/node-nestjs-distributed-tracing/tsconfig.build.json create mode 100644 dev-packages/e2e-tests/test-applications/node-nestjs-distributed-tracing/tsconfig.json diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 4cf711bd42ff..47c4c638ffb4 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -1037,6 +1037,8 @@ jobs: 'generic-ts3.8', 'node-fastify', 'node-hapi', + 'node-nestjs-basic', + 'node-nestjs-distributed-tracing', 'nestjs-basic', 'nestjs-distributed-tracing', 'nestjs-with-submodules', diff --git a/dev-packages/e2e-tests/test-applications/node-nestjs-basic/.gitignore b/dev-packages/e2e-tests/test-applications/node-nestjs-basic/.gitignore new file mode 100644 index 000000000000..4b56acfbebf4 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/node-nestjs-basic/.gitignore @@ -0,0 +1,56 @@ +# compiled output +/dist +/node_modules +/build + +# Logs +logs +*.log +npm-debug.log* +pnpm-debug.log* +yarn-debug.log* +yarn-error.log* +lerna-debug.log* + +# OS +.DS_Store + +# Tests +/coverage +/.nyc_output + +# IDEs and editors +/.idea +.project +.classpath +.c9/ +*.launch +.settings/ +*.sublime-workspace + +# IDE - VSCode +.vscode/* +!.vscode/settings.json +!.vscode/tasks.json +!.vscode/launch.json +!.vscode/extensions.json + +# dotenv environment variable files +.env +.env.development.local +.env.test.local +.env.production.local +.env.local + +# temp directory +.temp +.tmp + +# Runtime data +pids +*.pid +*.seed +*.pid.lock + +# Diagnostic reports (https://nodejs.org/api/report.html) +report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json diff --git a/dev-packages/e2e-tests/test-applications/node-nestjs-basic/.npmrc b/dev-packages/e2e-tests/test-applications/node-nestjs-basic/.npmrc new file mode 100644 index 000000000000..070f80f05092 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/node-nestjs-basic/.npmrc @@ -0,0 +1,2 @@ +@sentry:registry=http://127.0.0.1:4873 +@sentry-internal:registry=http://127.0.0.1:4873 diff --git a/dev-packages/e2e-tests/test-applications/node-nestjs-basic/nest-cli.json b/dev-packages/e2e-tests/test-applications/node-nestjs-basic/nest-cli.json new file mode 100644 index 000000000000..f9aa683b1ad5 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/node-nestjs-basic/nest-cli.json @@ -0,0 +1,8 @@ +{ + "$schema": "https://json.schemastore.org/nest-cli", + "collection": "@nestjs/schematics", + "sourceRoot": "src", + "compilerOptions": { + "deleteOutDir": true + } +} diff --git a/dev-packages/e2e-tests/test-applications/node-nestjs-basic/package.json b/dev-packages/e2e-tests/test-applications/node-nestjs-basic/package.json new file mode 100644 index 000000000000..ec6510ac03ff --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/node-nestjs-basic/package.json @@ -0,0 +1,48 @@ +{ + "name": "node-nestjs-basic", + "version": "0.0.1", + "private": true, + "scripts": { + "build": "nest build", + "format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\"", + "start": "nest start", + "start:dev": "nest start --watch", + "start:debug": "nest start --debug --watch", + "start:prod": "node dist/main", + "clean": "npx rimraf node_modules pnpm-lock.yaml", + "test": "playwright test", + "test:build": "pnpm install", + "test:assert": "pnpm test" + }, + "dependencies": { + "@nestjs/common": "^10.0.0", + "@nestjs/core": "^10.0.0", + "@nestjs/schedule": "^4.1.0", + "@nestjs/platform-express": "^10.0.0", + "@sentry/nestjs": "latest || *", + "@sentry/types": "latest || *", + "reflect-metadata": "^0.2.0", + "rxjs": "^7.8.1" + }, + "devDependencies": { + "@playwright/test": "^1.44.1", + "@sentry-internal/test-utils": "link:../../../test-utils", + "@nestjs/cli": "^10.0.0", + "@nestjs/schematics": "^10.0.0", + "@nestjs/testing": "^10.0.0", + "@types/express": "^4.17.17", + "@types/node": "18.15.1", + "@types/supertest": "^6.0.0", + "@typescript-eslint/eslint-plugin": "^6.0.0", + "@typescript-eslint/parser": "^6.0.0", + "eslint": "^8.42.0", + "eslint-config-prettier": "^9.0.0", + "eslint-plugin-prettier": "^5.0.0", + "prettier": "^3.0.0", + "source-map-support": "^0.5.21", + "supertest": "^6.3.3", + "ts-loader": "^9.4.3", + "tsconfig-paths": "^4.2.0", + "typescript": "^4.9.5" + } +} diff --git a/dev-packages/e2e-tests/test-applications/node-nestjs-basic/playwright.config.mjs b/dev-packages/e2e-tests/test-applications/node-nestjs-basic/playwright.config.mjs new file mode 100644 index 000000000000..31f2b913b58b --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/node-nestjs-basic/playwright.config.mjs @@ -0,0 +1,7 @@ +import { getPlaywrightConfig } from '@sentry-internal/test-utils'; + +const config = getPlaywrightConfig({ + startCommand: `pnpm start`, +}); + +export default config; diff --git a/dev-packages/e2e-tests/test-applications/node-nestjs-basic/src/app.controller.ts b/dev-packages/e2e-tests/test-applications/node-nestjs-basic/src/app.controller.ts new file mode 100644 index 000000000000..b54604d999cb --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/node-nestjs-basic/src/app.controller.ts @@ -0,0 +1,37 @@ +import { Controller, Get, Param } from '@nestjs/common'; +import { AppService } from './app.service'; + +@Controller() +export class AppController { + constructor(private readonly appService: AppService) {} + + @Get('test-transaction') + testTransaction() { + return this.appService.testTransaction(); + } + + @Get('test-exception/:id') + async testException(@Param('id') id: string) { + return this.appService.testException(id); + } + + @Get('test-expected-exception/:id') + async testExpectedException(@Param('id') id: string) { + return this.appService.testExpectedException(id); + } + + @Get('test-span-decorator-async') + async testSpanDecoratorAsync() { + return { result: await this.appService.testSpanDecoratorAsync() }; + } + + @Get('test-span-decorator-sync') + async testSpanDecoratorSync() { + return { result: await this.appService.testSpanDecoratorSync() }; + } + + @Get('kill-test-cron') + async killTestCron() { + this.appService.killTestCron(); + } +} diff --git a/dev-packages/e2e-tests/test-applications/node-nestjs-basic/src/app.module.ts b/dev-packages/e2e-tests/test-applications/node-nestjs-basic/src/app.module.ts new file mode 100644 index 000000000000..ceb7199a99cf --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/node-nestjs-basic/src/app.module.ts @@ -0,0 +1,11 @@ +import { Module } from '@nestjs/common'; +import { ScheduleModule } from '@nestjs/schedule'; +import { AppController } from './app.controller'; +import { AppService } from './app.service'; + +@Module({ + imports: [ScheduleModule.forRoot()], + controllers: [AppController], + providers: [AppService], +}) +export class AppModule {} diff --git a/dev-packages/e2e-tests/test-applications/node-nestjs-basic/src/app.service.ts b/dev-packages/e2e-tests/test-applications/node-nestjs-basic/src/app.service.ts new file mode 100644 index 000000000000..3afb7b5147bd --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/node-nestjs-basic/src/app.service.ts @@ -0,0 +1,67 @@ +import { HttpException, HttpStatus, Injectable } from '@nestjs/common'; +import { Cron, SchedulerRegistry } from '@nestjs/schedule'; +import * as Sentry from '@sentry/nestjs'; +import { SentryCron, SentryTraced } from '@sentry/nestjs'; +import type { MonitorConfig } from '@sentry/types'; + +const monitorConfig: MonitorConfig = { + schedule: { + type: 'crontab', + value: '* * * * *', + }, +}; + +@Injectable() +export class AppService { + constructor(private schedulerRegistry: SchedulerRegistry) {} + + testTransaction() { + Sentry.startSpan({ name: 'test-span' }, () => { + Sentry.startSpan({ name: 'child-span' }, () => {}); + }); + } + + testException(id: string) { + throw new Error(`This is an exception with id ${id}`); + } + + testExpectedException(id: string) { + throw new HttpException(`This is an expected exception with id ${id}`, HttpStatus.FORBIDDEN); + } + + @SentryTraced('wait and return a string') + async wait() { + await new Promise(resolve => setTimeout(resolve, 500)); + return 'test'; + } + + async testSpanDecoratorAsync() { + return await this.wait(); + } + + @SentryTraced('return a string') + getString(): { result: string } { + return { result: 'test' }; + } + + async testSpanDecoratorSync() { + const returned = this.getString(); + // Will fail if getString() is async, because returned will be a Promise<> + return returned.result; + } + + /* + Actual cron schedule differs from schedule defined in config because Sentry + only supports minute granularity, but we don't want to wait (worst case) a + full minute for the tests to finish. + */ + @Cron('*/5 * * * * *', { name: 'test-cron-job' }) + @SentryCron('test-cron-slug', monitorConfig) + async testCron() { + console.log('Test cron!'); + } + + async killTestCron() { + this.schedulerRegistry.deleteCronJob('test-cron-job'); + } +} diff --git a/dev-packages/e2e-tests/test-applications/node-nestjs-basic/src/instrument.ts b/dev-packages/e2e-tests/test-applications/node-nestjs-basic/src/instrument.ts new file mode 100644 index 000000000000..f1f4de865435 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/node-nestjs-basic/src/instrument.ts @@ -0,0 +1,8 @@ +import * as Sentry from '@sentry/nestjs'; + +Sentry.init({ + environment: 'qa', // dynamic sampling bias to keep transactions + dsn: process.env.E2E_TEST_DSN, + tunnel: `http://localhost:3031/`, // proxy server + tracesSampleRate: 1, +}); diff --git a/dev-packages/e2e-tests/test-applications/node-nestjs-basic/src/main.ts b/dev-packages/e2e-tests/test-applications/node-nestjs-basic/src/main.ts new file mode 100644 index 000000000000..0dcf80a4f361 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/node-nestjs-basic/src/main.ts @@ -0,0 +1,20 @@ +// Import this first +import './instrument'; + +// Import other modules +import {BaseExceptionFilter, HttpAdapterHost, NestFactory} from '@nestjs/core'; +import { AppModule } from './app.module'; +import * as Sentry from "@sentry/nestjs"; + +const PORT = 3030; + +async function bootstrap() { + const app = await NestFactory.create(AppModule); + + const { httpAdapter } = app.get(HttpAdapterHost); + Sentry.setupNestErrorHandler(app, new BaseExceptionFilter(httpAdapter)); + + await app.listen(PORT); +} + +bootstrap(); diff --git a/dev-packages/e2e-tests/test-applications/node-nestjs-basic/start-event-proxy.mjs b/dev-packages/e2e-tests/test-applications/node-nestjs-basic/start-event-proxy.mjs new file mode 100644 index 000000000000..e9917b9273da --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/node-nestjs-basic/start-event-proxy.mjs @@ -0,0 +1,6 @@ +import { startEventProxyServer } from '@sentry-internal/test-utils'; + +startEventProxyServer({ + port: 3031, + proxyServerName: 'nestjs', +}); diff --git a/dev-packages/e2e-tests/test-applications/node-nestjs-basic/tests/cron-decorator.test.ts b/dev-packages/e2e-tests/test-applications/node-nestjs-basic/tests/cron-decorator.test.ts new file mode 100644 index 000000000000..c13623337343 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/node-nestjs-basic/tests/cron-decorator.test.ts @@ -0,0 +1,34 @@ +import { expect, test } from '@playwright/test'; +import { waitForEnvelopeItem } from '@sentry-internal/test-utils'; + +test('Cron job triggers send of in_progress envelope', async ({ baseURL }) => { + const inProgressEnvelopePromise = waitForEnvelopeItem('nestjs', envelope => { + return envelope[0].type === 'check_in'; + }); + + const inProgressEnvelope = await inProgressEnvelopePromise; + + expect(inProgressEnvelope[1]).toEqual( + expect.objectContaining({ + check_in_id: expect.any(String), + monitor_slug: 'test-cron-slug', + status: 'in_progress', + environment: 'qa', + monitor_config: { + schedule: { + type: 'crontab', + value: '* * * * *', + }, + }, + contexts: { + trace: { + span_id: expect.any(String), + trace_id: expect.any(String), + }, + }, + }), + ); + + // kill cron so tests don't get stuck + await fetch(`${baseURL}/kill-test-cron`); +}); diff --git a/dev-packages/e2e-tests/test-applications/node-nestjs-basic/tests/errors.test.ts b/dev-packages/e2e-tests/test-applications/node-nestjs-basic/tests/errors.test.ts new file mode 100644 index 000000000000..349b25b0eee9 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/node-nestjs-basic/tests/errors.test.ts @@ -0,0 +1,55 @@ +import { expect, test } from '@playwright/test'; +import { waitForError, waitForTransaction } from '@sentry-internal/test-utils'; + +test('Sends exception to Sentry', async ({ baseURL }) => { + const errorEventPromise = waitForError('nestjs', event => { + return !event.type && event.exception?.values?.[0]?.value === 'This is an exception with id 123'; + }); + + const response = await fetch(`${baseURL}/test-exception/123`); + expect(response.status).toBe(500); + + const errorEvent = await errorEventPromise; + + expect(errorEvent.exception?.values).toHaveLength(1); + expect(errorEvent.exception?.values?.[0]?.value).toBe('This is an exception with id 123'); + + expect(errorEvent.request).toEqual({ + method: 'GET', + cookies: {}, + headers: expect.any(Object), + url: 'http://localhost:3030/test-exception/123', + }); + + expect(errorEvent.transaction).toEqual('GET /test-exception/:id'); + + expect(errorEvent.contexts?.trace).toEqual({ + trace_id: expect.any(String), + span_id: expect.any(String), + }); +}); + +test('Does not send expected exception to Sentry', async ({ baseURL }) => { + let errorEventOccurred = false; + + waitForError('nestjs', event => { + if (!event.type && event.exception?.values?.[0]?.value === 'This is an expected exception with id 123') { + errorEventOccurred = true; + } + + return event?.transaction === 'GET /test-expected-exception/:id'; + }); + + const transactionEventPromise = waitForTransaction('nestjs', transactionEvent => { + return transactionEvent?.transaction === 'GET /test-expected-exception/:id'; + }); + + const response = await fetch(`${baseURL}/test-expected-exception/123`); + expect(response.status).toBe(403); + + await transactionEventPromise; + + await new Promise(resolve => setTimeout(resolve, 10000)); + + expect(errorEventOccurred).toBe(false); +}); diff --git a/dev-packages/e2e-tests/test-applications/node-nestjs-basic/tests/span-decorator.test.ts b/dev-packages/e2e-tests/test-applications/node-nestjs-basic/tests/span-decorator.test.ts new file mode 100644 index 000000000000..28c925727d89 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/node-nestjs-basic/tests/span-decorator.test.ts @@ -0,0 +1,72 @@ +import { expect, test } from '@playwright/test'; +import { waitForTransaction } from '@sentry-internal/test-utils'; + +test('Transaction includes span and correct value for decorated async function', async ({ baseURL }) => { + const transactionEventPromise = waitForTransaction('nestjs', transactionEvent => { + return ( + transactionEvent?.contexts?.trace?.op === 'http.server' && + transactionEvent?.transaction === 'GET /test-span-decorator-async' + ); + }); + + const response = await fetch(`${baseURL}/test-span-decorator-async`); + const body = await response.json(); + + expect(body.result).toEqual('test'); + + const transactionEvent = await transactionEventPromise; + + expect(transactionEvent.spans).toEqual( + expect.arrayContaining([ + expect.objectContaining({ + span_id: expect.any(String), + trace_id: expect.any(String), + data: { + 'sentry.origin': 'manual', + 'sentry.op': 'wait and return a string', + }, + description: 'wait', + parent_span_id: expect.any(String), + start_timestamp: expect.any(Number), + status: 'ok', + op: 'wait and return a string', + origin: 'manual', + }), + ]), + ); +}); + +test('Transaction includes span and correct value for decorated sync function', async ({ baseURL }) => { + const transactionEventPromise = waitForTransaction('nestjs', transactionEvent => { + return ( + transactionEvent?.contexts?.trace?.op === 'http.server' && + transactionEvent?.transaction === 'GET /test-span-decorator-sync' + ); + }); + + const response = await fetch(`${baseURL}/test-span-decorator-sync`); + const body = await response.json(); + + expect(body.result).toEqual('test'); + + const transactionEvent = await transactionEventPromise; + + expect(transactionEvent.spans).toEqual( + expect.arrayContaining([ + expect.objectContaining({ + span_id: expect.any(String), + trace_id: expect.any(String), + data: { + 'sentry.origin': 'manual', + 'sentry.op': 'return a string', + }, + description: 'getString', + parent_span_id: expect.any(String), + start_timestamp: expect.any(Number), + status: 'ok', + op: 'return a string', + origin: 'manual', + }), + ]), + ); +}); diff --git a/dev-packages/e2e-tests/test-applications/node-nestjs-basic/tests/transactions.test.ts b/dev-packages/e2e-tests/test-applications/node-nestjs-basic/tests/transactions.test.ts new file mode 100644 index 000000000000..ae5d8b63b16d --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/node-nestjs-basic/tests/transactions.test.ts @@ -0,0 +1,123 @@ +import { expect, test } from '@playwright/test'; +import { waitForTransaction } from '@sentry-internal/test-utils'; + +test('Sends an API route transaction', async ({ baseURL }) => { + const pageloadTransactionEventPromise = waitForTransaction('nestjs', transactionEvent => { + return ( + transactionEvent?.contexts?.trace?.op === 'http.server' && + transactionEvent?.transaction === 'GET /test-transaction' + ); + }); + + await fetch(`${baseURL}/test-transaction`); + + const transactionEvent = await pageloadTransactionEventPromise; + + expect(transactionEvent.contexts?.trace).toEqual({ + data: { + 'sentry.source': 'route', + 'sentry.origin': 'auto.http.otel.http', + 'sentry.op': 'http.server', + 'sentry.sample_rate': 1, + url: 'http://localhost:3030/test-transaction', + 'otel.kind': 'SERVER', + 'http.response.status_code': 200, + 'http.url': 'http://localhost:3030/test-transaction', + 'http.host': 'localhost:3030', + 'net.host.name': 'localhost', + 'http.method': 'GET', + 'http.scheme': 'http', + 'http.target': '/test-transaction', + 'http.user_agent': 'node', + 'http.flavor': '1.1', + 'net.transport': 'ip_tcp', + 'net.host.ip': expect.any(String), + 'net.host.port': expect.any(Number), + 'net.peer.ip': expect.any(String), + 'net.peer.port': expect.any(Number), + 'http.status_code': 200, + 'http.status_text': 'OK', + 'http.route': '/test-transaction', + }, + op: 'http.server', + span_id: expect.any(String), + status: 'ok', + trace_id: expect.any(String), + origin: 'auto.http.otel.http', + }); + + expect(transactionEvent).toEqual( + expect.objectContaining({ + spans: expect.arrayContaining([ + { + data: { + 'express.name': '/test-transaction', + 'express.type': 'request_handler', + 'http.route': '/test-transaction', + 'sentry.origin': 'auto.http.otel.express', + 'sentry.op': 'request_handler.express', + }, + op: 'request_handler.express', + description: '/test-transaction', + parent_span_id: expect.any(String), + span_id: expect.any(String), + start_timestamp: expect.any(Number), + status: 'ok', + timestamp: expect.any(Number), + trace_id: expect.any(String), + origin: 'auto.http.otel.express', + }, + { + data: { + 'sentry.origin': 'manual', + }, + description: 'test-span', + parent_span_id: expect.any(String), + span_id: expect.any(String), + start_timestamp: expect.any(Number), + status: 'ok', + timestamp: expect.any(Number), + trace_id: expect.any(String), + origin: 'manual', + }, + { + data: { + 'sentry.origin': 'manual', + }, + description: 'child-span', + parent_span_id: expect.any(String), + span_id: expect.any(String), + start_timestamp: expect.any(Number), + status: 'ok', + timestamp: expect.any(Number), + trace_id: expect.any(String), + origin: 'manual', + }, + { + span_id: expect.any(String), + trace_id: expect.any(String), + data: { + 'sentry.origin': 'auto.http.otel.nestjs', + 'sentry.op': 'handler.nestjs', + component: '@nestjs/core', + 'nestjs.version': expect.any(String), + 'nestjs.type': 'handler', + 'nestjs.callback': 'testTransaction', + }, + description: 'testTransaction', + parent_span_id: expect.any(String), + start_timestamp: expect.any(Number), + timestamp: expect.any(Number), + status: 'ok', + origin: 'auto.http.otel.nestjs', + op: 'handler.nestjs', + }, + ]), + transaction: 'GET /test-transaction', + type: 'transaction', + transaction_info: { + source: 'route', + }, + }), + ); +}); diff --git a/dev-packages/e2e-tests/test-applications/node-nestjs-basic/tsconfig.build.json b/dev-packages/e2e-tests/test-applications/node-nestjs-basic/tsconfig.build.json new file mode 100644 index 000000000000..26c30d4eddf2 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/node-nestjs-basic/tsconfig.build.json @@ -0,0 +1,4 @@ +{ + "extends": "./tsconfig.json", + "exclude": ["node_modules", "test", "dist"] +} diff --git a/dev-packages/e2e-tests/test-applications/node-nestjs-basic/tsconfig.json b/dev-packages/e2e-tests/test-applications/node-nestjs-basic/tsconfig.json new file mode 100644 index 000000000000..95f5641cf7f3 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/node-nestjs-basic/tsconfig.json @@ -0,0 +1,21 @@ +{ + "compilerOptions": { + "module": "commonjs", + "declaration": true, + "removeComments": true, + "emitDecoratorMetadata": true, + "experimentalDecorators": true, + "allowSyntheticDefaultImports": true, + "target": "ES2021", + "sourceMap": true, + "outDir": "./dist", + "baseUrl": "./", + "incremental": true, + "skipLibCheck": true, + "strictNullChecks": false, + "noImplicitAny": false, + "strictBindCallApply": false, + "forceConsistentCasingInFileNames": false, + "noFallthroughCasesInSwitch": false + } +} diff --git a/dev-packages/e2e-tests/test-applications/node-nestjs-distributed-tracing/.gitignore b/dev-packages/e2e-tests/test-applications/node-nestjs-distributed-tracing/.gitignore new file mode 100644 index 000000000000..4b56acfbebf4 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/node-nestjs-distributed-tracing/.gitignore @@ -0,0 +1,56 @@ +# compiled output +/dist +/node_modules +/build + +# Logs +logs +*.log +npm-debug.log* +pnpm-debug.log* +yarn-debug.log* +yarn-error.log* +lerna-debug.log* + +# OS +.DS_Store + +# Tests +/coverage +/.nyc_output + +# IDEs and editors +/.idea +.project +.classpath +.c9/ +*.launch +.settings/ +*.sublime-workspace + +# IDE - VSCode +.vscode/* +!.vscode/settings.json +!.vscode/tasks.json +!.vscode/launch.json +!.vscode/extensions.json + +# dotenv environment variable files +.env +.env.development.local +.env.test.local +.env.production.local +.env.local + +# temp directory +.temp +.tmp + +# Runtime data +pids +*.pid +*.seed +*.pid.lock + +# Diagnostic reports (https://nodejs.org/api/report.html) +report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json diff --git a/dev-packages/e2e-tests/test-applications/node-nestjs-distributed-tracing/.npmrc b/dev-packages/e2e-tests/test-applications/node-nestjs-distributed-tracing/.npmrc new file mode 100644 index 000000000000..070f80f05092 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/node-nestjs-distributed-tracing/.npmrc @@ -0,0 +1,2 @@ +@sentry:registry=http://127.0.0.1:4873 +@sentry-internal:registry=http://127.0.0.1:4873 diff --git a/dev-packages/e2e-tests/test-applications/node-nestjs-distributed-tracing/nest-cli.json b/dev-packages/e2e-tests/test-applications/node-nestjs-distributed-tracing/nest-cli.json new file mode 100644 index 000000000000..f9aa683b1ad5 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/node-nestjs-distributed-tracing/nest-cli.json @@ -0,0 +1,8 @@ +{ + "$schema": "https://json.schemastore.org/nest-cli", + "collection": "@nestjs/schematics", + "sourceRoot": "src", + "compilerOptions": { + "deleteOutDir": true + } +} diff --git a/dev-packages/e2e-tests/test-applications/node-nestjs-distributed-tracing/package.json b/dev-packages/e2e-tests/test-applications/node-nestjs-distributed-tracing/package.json new file mode 100644 index 000000000000..ad61f9a77ad4 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/node-nestjs-distributed-tracing/package.json @@ -0,0 +1,47 @@ +{ + "name": "node-nestjs-distributed-tracing", + "version": "0.0.1", + "private": true, + "scripts": { + "build": "nest build", + "format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\"", + "start": "nest start", + "start:dev": "nest start --watch", + "start:debug": "nest start --debug --watch", + "start:prod": "node dist/main", + "clean": "npx rimraf node_modules pnpm-lock.yaml", + "test": "playwright test", + "test:build": "pnpm install", + "test:assert": "pnpm test" + }, + "dependencies": { + "@nestjs/common": "^10.0.0", + "@nestjs/core": "^10.0.0", + "@nestjs/platform-express": "^10.0.0", + "@sentry/nestjs": "latest || *", + "@sentry/types": "latest || *", + "reflect-metadata": "^0.2.0", + "rxjs": "^7.8.1" + }, + "devDependencies": { + "@playwright/test": "^1.44.1", + "@sentry-internal/test-utils": "link:../../../test-utils", + "@nestjs/cli": "^10.0.0", + "@nestjs/schematics": "^10.0.0", + "@nestjs/testing": "^10.0.0", + "@types/express": "^4.17.17", + "@types/node": "18.15.1", + "@types/supertest": "^6.0.0", + "@typescript-eslint/eslint-plugin": "^6.0.0", + "@typescript-eslint/parser": "^6.0.0", + "eslint": "^8.42.0", + "eslint-config-prettier": "^9.0.0", + "eslint-plugin-prettier": "^5.0.0", + "prettier": "^3.0.0", + "source-map-support": "^0.5.21", + "supertest": "^6.3.3", + "ts-loader": "^9.4.3", + "tsconfig-paths": "^4.2.0", + "typescript": "^4.9.5" + } +} diff --git a/dev-packages/e2e-tests/test-applications/node-nestjs-distributed-tracing/playwright.config.mjs b/dev-packages/e2e-tests/test-applications/node-nestjs-distributed-tracing/playwright.config.mjs new file mode 100644 index 000000000000..31f2b913b58b --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/node-nestjs-distributed-tracing/playwright.config.mjs @@ -0,0 +1,7 @@ +import { getPlaywrightConfig } from '@sentry-internal/test-utils'; + +const config = getPlaywrightConfig({ + startCommand: `pnpm start`, +}); + +export default config; diff --git a/dev-packages/e2e-tests/test-applications/node-nestjs-distributed-tracing/src/instrument.ts b/dev-packages/e2e-tests/test-applications/node-nestjs-distributed-tracing/src/instrument.ts new file mode 100644 index 000000000000..b5ca047e497c --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/node-nestjs-distributed-tracing/src/instrument.ts @@ -0,0 +1,9 @@ +import * as Sentry from '@sentry/nestjs'; + +Sentry.init({ + environment: 'qa', // dynamic sampling bias to keep transactions + dsn: process.env.E2E_TEST_DSN, + tunnel: `http://localhost:3031/`, // proxy server + tracesSampleRate: 1, + tracePropagationTargets: ['http://localhost:3030', '/external-allowed'], +}); diff --git a/dev-packages/e2e-tests/test-applications/node-nestjs-distributed-tracing/src/main.ts b/dev-packages/e2e-tests/test-applications/node-nestjs-distributed-tracing/src/main.ts new file mode 100644 index 000000000000..bbdac0d2f1c5 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/node-nestjs-distributed-tracing/src/main.ts @@ -0,0 +1,23 @@ +// Import this first +import './instrument'; + +// Import other modules +import {BaseExceptionFilter, HttpAdapterHost, NestFactory} from '@nestjs/core'; +import { TraceInitiatorModule } from './trace-initiator.module'; +import { TraceReceiverModule } from './trace-receiver.module'; +import * as Sentry from "@sentry/nestjs"; + +const TRACE_INITIATOR_PORT = 3030; +const TRACE_RECEIVER_PORT = 3040; + +async function bootstrap() { + const trace_initiator_app = await NestFactory.create(TraceInitiatorModule); + const { httpAdapter } = trace_initiator_app.get(HttpAdapterHost); + Sentry.setupNestErrorHandler(trace_initiator_app, new BaseExceptionFilter(httpAdapter)); + await trace_initiator_app.listen(TRACE_INITIATOR_PORT); + + const trace_receiver_app = await NestFactory.create(TraceReceiverModule); + await trace_receiver_app.listen(TRACE_RECEIVER_PORT); +} + +bootstrap(); diff --git a/dev-packages/e2e-tests/test-applications/node-nestjs-distributed-tracing/src/trace-initiator.controller.ts b/dev-packages/e2e-tests/test-applications/node-nestjs-distributed-tracing/src/trace-initiator.controller.ts new file mode 100644 index 000000000000..62e0c299a239 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/node-nestjs-distributed-tracing/src/trace-initiator.controller.ts @@ -0,0 +1,42 @@ +import { Controller, Get, Headers, Param } from '@nestjs/common'; +import { TraceInitiatorService } from './trace-initiator.service'; + +@Controller() +export class TraceInitiatorController { + constructor(private readonly traceInitiatorService: TraceInitiatorService) {} + + @Get('test-inbound-headers/:id') + testInboundHeaders(@Headers() headers, @Param('id') id: string) { + return this.traceInitiatorService.testInboundHeaders(headers, id); + } + + @Get('test-outgoing-http/:id') + async testOutgoingHttp(@Param('id') id: string) { + return this.traceInitiatorService.testOutgoingHttp(id); + } + + @Get('test-outgoing-fetch/:id') + async testOutgoingFetch(@Param('id') id: string) { + return this.traceInitiatorService.testOutgoingFetch(id); + } + + @Get('test-outgoing-fetch-external-allowed') + async testOutgoingFetchExternalAllowed() { + return this.traceInitiatorService.testOutgoingFetchExternalAllowed(); + } + + @Get('test-outgoing-fetch-external-disallowed') + async testOutgoingFetchExternalDisallowed() { + return this.traceInitiatorService.testOutgoingFetchExternalDisallowed(); + } + + @Get('test-outgoing-http-external-allowed') + async testOutgoingHttpExternalAllowed() { + return this.traceInitiatorService.testOutgoingHttpExternalAllowed(); + } + + @Get('test-outgoing-http-external-disallowed') + async testOutgoingHttpExternalDisallowed() { + return this.traceInitiatorService.testOutgoingHttpExternalDisallowed(); + } +} diff --git a/dev-packages/e2e-tests/test-applications/node-nestjs-distributed-tracing/src/trace-initiator.module.ts b/dev-packages/e2e-tests/test-applications/node-nestjs-distributed-tracing/src/trace-initiator.module.ts new file mode 100644 index 000000000000..9256f29928ab --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/node-nestjs-distributed-tracing/src/trace-initiator.module.ts @@ -0,0 +1,10 @@ +import { Module } from '@nestjs/common'; +import { TraceInitiatorController } from './trace-initiator.controller'; +import { TraceInitiatorService } from './trace-initiator.service'; + +@Module({ + imports: [], + controllers: [TraceInitiatorController], + providers: [TraceInitiatorService], +}) +export class TraceInitiatorModule {} diff --git a/dev-packages/e2e-tests/test-applications/node-nestjs-distributed-tracing/src/trace-initiator.service.ts b/dev-packages/e2e-tests/test-applications/node-nestjs-distributed-tracing/src/trace-initiator.service.ts new file mode 100644 index 000000000000..67c5333cedaf --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/node-nestjs-distributed-tracing/src/trace-initiator.service.ts @@ -0,0 +1,47 @@ +import { Injectable } from '@nestjs/common'; +import { makeHttpRequest } from './utils'; + +@Injectable() +export class TraceInitiatorService { + constructor() {} + + testInboundHeaders(headers: Record, id: string) { + return { + headers, + id, + }; + } + + async testOutgoingHttp(id: string) { + const data = await makeHttpRequest(`http://localhost:3030/test-inbound-headers/${id}`); + + return data; + } + + async testOutgoingFetch(id: string) { + const response = await fetch(`http://localhost:3030/test-inbound-headers/${id}`); + const data = await response.json(); + + return data; + } + + async testOutgoingFetchExternalAllowed() { + const fetchResponse = await fetch('http://localhost:3040/external-allowed'); + + return fetchResponse.json(); + } + + async testOutgoingFetchExternalDisallowed() { + const fetchResponse = await fetch('http://localhost:3040/external-disallowed'); + + return fetchResponse.json(); + } + + async testOutgoingHttpExternalAllowed() { + return makeHttpRequest('http://localhost:3040/external-allowed'); + } + + async testOutgoingHttpExternalDisallowed() { + return makeHttpRequest('http://localhost:3040/external-disallowed'); + } +} diff --git a/dev-packages/e2e-tests/test-applications/node-nestjs-distributed-tracing/src/trace-receiver.controller.ts b/dev-packages/e2e-tests/test-applications/node-nestjs-distributed-tracing/src/trace-receiver.controller.ts new file mode 100644 index 000000000000..2a1899f1097d --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/node-nestjs-distributed-tracing/src/trace-receiver.controller.ts @@ -0,0 +1,17 @@ +import { Controller, Get, Headers } from '@nestjs/common'; +import { TraceReceiverService } from './trace-receiver.service'; + +@Controller() +export class TraceReceiverController { + constructor(private readonly traceReceiverService: TraceReceiverService) {} + + @Get('external-allowed') + externalAllowed(@Headers() headers) { + return this.traceReceiverService.externalAllowed(headers); + } + + @Get('external-disallowed') + externalDisallowed(@Headers() headers) { + return this.traceReceiverService.externalDisallowed(headers); + } +} diff --git a/dev-packages/e2e-tests/test-applications/node-nestjs-distributed-tracing/src/trace-receiver.module.ts b/dev-packages/e2e-tests/test-applications/node-nestjs-distributed-tracing/src/trace-receiver.module.ts new file mode 100644 index 000000000000..2680b3071fb7 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/node-nestjs-distributed-tracing/src/trace-receiver.module.ts @@ -0,0 +1,10 @@ +import { Module } from '@nestjs/common'; +import { TraceReceiverController } from './trace-receiver.controller'; +import { TraceReceiverService } from './trace-receiver.service'; + +@Module({ + imports: [], + controllers: [TraceReceiverController], + providers: [TraceReceiverService], +}) +export class TraceReceiverModule {} diff --git a/dev-packages/e2e-tests/test-applications/node-nestjs-distributed-tracing/src/trace-receiver.service.ts b/dev-packages/e2e-tests/test-applications/node-nestjs-distributed-tracing/src/trace-receiver.service.ts new file mode 100644 index 000000000000..a40b28ad0778 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/node-nestjs-distributed-tracing/src/trace-receiver.service.ts @@ -0,0 +1,18 @@ +import { Injectable } from '@nestjs/common'; + +@Injectable() +export class TraceReceiverService { + externalAllowed(headers: Record) { + return { + headers, + route: 'external-allowed', + }; + } + + externalDisallowed(headers: Record) { + return { + headers, + route: 'external-disallowed', + }; + } +} diff --git a/dev-packages/e2e-tests/test-applications/node-nestjs-distributed-tracing/src/utils.ts b/dev-packages/e2e-tests/test-applications/node-nestjs-distributed-tracing/src/utils.ts new file mode 100644 index 000000000000..27639ef26349 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/node-nestjs-distributed-tracing/src/utils.ts @@ -0,0 +1,26 @@ +import * as http from 'http'; + +export function makeHttpRequest(url) { + return new Promise(resolve => { + const data = []; + + http + .request(url, httpRes => { + httpRes.on('data', chunk => { + data.push(chunk); + }); + httpRes.on('error', error => { + resolve({ error: error.message, url }); + }); + httpRes.on('end', () => { + try { + const json = JSON.parse(Buffer.concat(data).toString()); + resolve(json); + } catch { + resolve({ data: Buffer.concat(data).toString(), url }); + } + }); + }) + .end(); + }); +} diff --git a/dev-packages/e2e-tests/test-applications/node-nestjs-distributed-tracing/start-event-proxy.mjs b/dev-packages/e2e-tests/test-applications/node-nestjs-distributed-tracing/start-event-proxy.mjs new file mode 100644 index 000000000000..e9917b9273da --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/node-nestjs-distributed-tracing/start-event-proxy.mjs @@ -0,0 +1,6 @@ +import { startEventProxyServer } from '@sentry-internal/test-utils'; + +startEventProxyServer({ + port: 3031, + proxyServerName: 'nestjs', +}); diff --git a/dev-packages/e2e-tests/test-applications/node-nestjs-distributed-tracing/tests/propagation.test.ts b/dev-packages/e2e-tests/test-applications/node-nestjs-distributed-tracing/tests/propagation.test.ts new file mode 100644 index 000000000000..2922435c542b --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/node-nestjs-distributed-tracing/tests/propagation.test.ts @@ -0,0 +1,356 @@ +import crypto from 'crypto'; +import { expect, test } from '@playwright/test'; +import { waitForTransaction } from '@sentry-internal/test-utils'; +import { SpanJSON } from '@sentry/types'; + +test('Propagates trace for outgoing http requests', async ({ baseURL }) => { + const id = crypto.randomUUID(); + + const inboundTransactionPromise = waitForTransaction('nestjs', transactionEvent => { + return ( + transactionEvent.contexts?.trace?.op === 'http.server' && + transactionEvent.contexts?.trace?.data?.['http.target'] === `/test-inbound-headers/${id}` + ); + }); + + const outboundTransactionPromise = waitForTransaction('nestjs', transactionEvent => { + return ( + transactionEvent.contexts?.trace?.op === 'http.server' && + transactionEvent.contexts?.trace?.data?.['http.target'] === `/test-outgoing-http/${id}` + ); + }); + + const response = await fetch(`${baseURL}/test-outgoing-http/${id}`); + const data = await response.json(); + + const inboundTransaction = await inboundTransactionPromise; + const outboundTransaction = await outboundTransactionPromise; + + const traceId = outboundTransaction?.contexts?.trace?.trace_id; + const outgoingHttpSpan = outboundTransaction?.spans?.find(span => span.op === 'http.client') as SpanJSON | undefined; + + expect(outgoingHttpSpan).toBeDefined(); + + const outgoingHttpSpanId = outgoingHttpSpan?.span_id; + + expect(traceId).toEqual(expect.any(String)); + + // data is passed through from the inbound request, to verify we have the correct headers set + const inboundHeaderSentryTrace = data.headers?.['sentry-trace']; + const inboundHeaderBaggage = data.headers?.['baggage']; + + expect(inboundHeaderSentryTrace).toEqual(`${traceId}-${outgoingHttpSpanId}-1`); + expect(inboundHeaderBaggage).toBeDefined(); + + const baggage = (inboundHeaderBaggage || '').split(','); + expect(baggage).toEqual( + expect.arrayContaining([ + 'sentry-environment=qa', + `sentry-trace_id=${traceId}`, + expect.stringMatching(/sentry-public_key=/), + ]), + ); + + expect(outboundTransaction.contexts?.trace).toEqual({ + data: { + 'sentry.source': 'route', + 'sentry.origin': 'auto.http.otel.http', + 'sentry.op': 'http.server', + 'sentry.sample_rate': 1, + url: `http://localhost:3030/test-outgoing-http/${id}`, + 'otel.kind': 'SERVER', + 'http.response.status_code': 200, + 'http.url': `http://localhost:3030/test-outgoing-http/${id}`, + 'http.host': 'localhost:3030', + 'net.host.name': 'localhost', + 'http.method': 'GET', + 'http.scheme': 'http', + 'http.target': `/test-outgoing-http/${id}`, + 'http.user_agent': 'node', + 'http.flavor': '1.1', + 'net.transport': 'ip_tcp', + 'net.host.ip': expect.any(String), + 'net.host.port': expect.any(Number), + 'net.peer.ip': expect.any(String), + 'net.peer.port': expect.any(Number), + 'http.status_code': 200, + 'http.status_text': 'OK', + 'http.route': '/test-outgoing-http/:id', + }, + op: 'http.server', + span_id: expect.any(String), + status: 'ok', + trace_id: traceId, + origin: 'auto.http.otel.http', + }); + + expect(inboundTransaction.contexts?.trace).toEqual({ + data: { + 'sentry.source': 'route', + 'sentry.origin': 'auto.http.otel.http', + 'sentry.op': 'http.server', + 'sentry.sample_rate': 1, + url: `http://localhost:3030/test-inbound-headers/${id}`, + 'otel.kind': 'SERVER', + 'http.response.status_code': 200, + 'http.url': `http://localhost:3030/test-inbound-headers/${id}`, + 'http.host': 'localhost:3030', + 'net.host.name': 'localhost', + 'http.method': 'GET', + 'http.scheme': 'http', + 'http.target': `/test-inbound-headers/${id}`, + 'http.flavor': '1.1', + 'net.transport': 'ip_tcp', + 'net.host.ip': expect.any(String), + 'net.host.port': expect.any(Number), + 'net.peer.ip': expect.any(String), + 'net.peer.port': expect.any(Number), + 'http.status_code': 200, + 'http.status_text': 'OK', + 'http.route': '/test-inbound-headers/:id', + }, + op: 'http.server', + parent_span_id: outgoingHttpSpanId, + span_id: expect.any(String), + status: 'ok', + trace_id: traceId, + origin: 'auto.http.otel.http', + }); +}); + +test('Propagates trace for outgoing fetch requests', async ({ baseURL }) => { + const id = crypto.randomUUID(); + + const inboundTransactionPromise = waitForTransaction('nestjs', transactionEvent => { + return ( + transactionEvent?.contexts?.trace?.op === 'http.server' && + transactionEvent.contexts?.trace?.data?.['http.target'] === `/test-inbound-headers/${id}` + ); + }); + + const outboundTransactionPromise = waitForTransaction('nestjs', transactionEvent => { + return ( + transactionEvent?.contexts?.trace?.op === 'http.server' && + transactionEvent.contexts?.trace?.data?.['http.target'] === `/test-outgoing-fetch/${id}` + ); + }); + + const response = await fetch(`${baseURL}/test-outgoing-fetch/${id}`); + const data = await response.json(); + + const inboundTransaction = await inboundTransactionPromise; + const outboundTransaction = await outboundTransactionPromise; + + const traceId = outboundTransaction?.contexts?.trace?.trace_id; + const outgoingHttpSpan = outboundTransaction?.spans?.find(span => span.op === 'http.client') as SpanJSON | undefined; + + expect(outgoingHttpSpan).toBeDefined(); + + const outgoingHttpSpanId = outgoingHttpSpan?.span_id; + + expect(traceId).toEqual(expect.any(String)); + + // data is passed through from the inbound request, to verify we have the correct headers set + const inboundHeaderSentryTrace = data.headers?.['sentry-trace']; + const inboundHeaderBaggage = data.headers?.['baggage']; + + expect(inboundHeaderSentryTrace).toEqual(`${traceId}-${outgoingHttpSpanId}-1`); + expect(inboundHeaderBaggage).toBeDefined(); + + const baggage = (inboundHeaderBaggage || '').split(','); + expect(baggage).toEqual( + expect.arrayContaining([ + 'sentry-environment=qa', + `sentry-trace_id=${traceId}`, + expect.stringMatching(/sentry-public_key=/), + ]), + ); + + expect(outboundTransaction.contexts?.trace).toEqual({ + data: { + 'sentry.source': 'route', + 'sentry.origin': 'auto.http.otel.http', + 'sentry.op': 'http.server', + 'sentry.sample_rate': 1, + url: `http://localhost:3030/test-outgoing-fetch/${id}`, + 'otel.kind': 'SERVER', + 'http.response.status_code': 200, + 'http.url': `http://localhost:3030/test-outgoing-fetch/${id}`, + 'http.host': 'localhost:3030', + 'net.host.name': 'localhost', + 'http.method': 'GET', + 'http.scheme': 'http', + 'http.target': `/test-outgoing-fetch/${id}`, + 'http.user_agent': 'node', + 'http.flavor': '1.1', + 'net.transport': 'ip_tcp', + 'net.host.ip': expect.any(String), + 'net.host.port': expect.any(Number), + 'net.peer.ip': expect.any(String), + 'net.peer.port': expect.any(Number), + 'http.status_code': 200, + 'http.status_text': 'OK', + 'http.route': '/test-outgoing-fetch/:id', + }, + op: 'http.server', + span_id: expect.any(String), + status: 'ok', + trace_id: traceId, + origin: 'auto.http.otel.http', + }); + + expect(inboundTransaction.contexts?.trace).toEqual({ + data: expect.objectContaining({ + 'sentry.source': 'route', + 'sentry.origin': 'auto.http.otel.http', + 'sentry.op': 'http.server', + 'sentry.sample_rate': 1, + url: `http://localhost:3030/test-inbound-headers/${id}`, + 'otel.kind': 'SERVER', + 'http.response.status_code': 200, + 'http.url': `http://localhost:3030/test-inbound-headers/${id}`, + 'http.host': 'localhost:3030', + 'net.host.name': 'localhost', + 'http.method': 'GET', + 'http.scheme': 'http', + 'http.target': `/test-inbound-headers/${id}`, + 'http.flavor': '1.1', + 'net.transport': 'ip_tcp', + 'net.host.ip': expect.any(String), + 'net.host.port': expect.any(Number), + 'net.peer.ip': expect.any(String), + 'net.peer.port': expect.any(Number), + 'http.status_code': 200, + 'http.status_text': 'OK', + 'http.route': '/test-inbound-headers/:id', + }), + op: 'http.server', + parent_span_id: outgoingHttpSpanId, + span_id: expect.any(String), + status: 'ok', + trace_id: traceId, + origin: 'auto.http.otel.http', + }); +}); + +test('Propagates trace for outgoing external http requests', async ({ baseURL }) => { + const inboundTransactionPromise = waitForTransaction('nestjs', transactionEvent => { + return ( + transactionEvent?.contexts?.trace?.op === 'http.server' && + transactionEvent.contexts?.trace?.data?.['http.target'] === `/test-outgoing-http-external-allowed` + ); + }); + + const response = await fetch(`${baseURL}/test-outgoing-http-external-allowed`); + const data = await response.json(); + + const inboundTransaction = await inboundTransactionPromise; + + const traceId = inboundTransaction?.contexts?.trace?.trace_id; + const spanId = inboundTransaction?.spans?.find(span => span.op === 'http.client')?.span_id; + + expect(traceId).toEqual(expect.any(String)); + expect(spanId).toEqual(expect.any(String)); + + expect(data).toEqual({ + headers: expect.objectContaining({ + 'sentry-trace': `${traceId}-${spanId}-1`, + baggage: expect.any(String), + }), + route: 'external-allowed', + }); + + const baggage = (data.headers.baggage || '').split(','); + expect(baggage).toEqual( + expect.arrayContaining([ + 'sentry-environment=qa', + `sentry-trace_id=${traceId}`, + expect.stringMatching(/sentry-public_key=/), + ]), + ); +}); + +test('Does not propagate outgoing http requests not covered by tracePropagationTargets', async ({ baseURL }) => { + const inboundTransactionPromise = waitForTransaction('nestjs', transactionEvent => { + return ( + transactionEvent?.contexts?.trace?.op === 'http.server' && + transactionEvent.contexts?.trace?.data?.['http.target'] === `/test-outgoing-http-external-disallowed` + ); + }); + + const response = await fetch(`${baseURL}/test-outgoing-http-external-disallowed`); + const data = await response.json(); + + const inboundTransaction = await inboundTransactionPromise; + + const traceId = inboundTransaction?.contexts?.trace?.trace_id; + const spanId = inboundTransaction?.spans?.find(span => span.op === 'http.client')?.span_id; + + expect(traceId).toEqual(expect.any(String)); + expect(spanId).toEqual(expect.any(String)); + + expect(data.route).toBe('external-disallowed'); + expect(data.headers?.['sentry-trace']).toBeUndefined(); + expect(data.headers?.baggage).toBeUndefined(); +}); + +test('Propagates trace for outgoing external fetch requests', async ({ baseURL }) => { + const inboundTransactionPromise = waitForTransaction('nestjs', transactionEvent => { + return ( + transactionEvent?.contexts?.trace?.op === 'http.server' && + transactionEvent.contexts?.trace?.data?.['http.target'] === `/test-outgoing-fetch-external-allowed` + ); + }); + + const response = await fetch(`${baseURL}/test-outgoing-fetch-external-allowed`); + const data = await response.json(); + + const inboundTransaction = await inboundTransactionPromise; + + const traceId = inboundTransaction?.contexts?.trace?.trace_id; + const spanId = inboundTransaction?.spans?.find(span => span.op === 'http.client')?.span_id; + + expect(traceId).toEqual(expect.any(String)); + expect(spanId).toEqual(expect.any(String)); + + expect(data).toEqual({ + headers: expect.objectContaining({ + 'sentry-trace': `${traceId}-${spanId}-1`, + baggage: expect.any(String), + }), + route: 'external-allowed', + }); + + const baggage = (data.headers.baggage || '').split(','); + expect(baggage).toEqual( + expect.arrayContaining([ + 'sentry-environment=qa', + `sentry-trace_id=${traceId}`, + expect.stringMatching(/sentry-public_key=/), + ]), + ); +}); + +test('Does not propagate outgoing fetch requests not covered by tracePropagationTargets', async ({ baseURL }) => { + const inboundTransactionPromise = waitForTransaction('nestjs', transactionEvent => { + return ( + transactionEvent?.contexts?.trace?.op === 'http.server' && + transactionEvent.contexts?.trace?.data?.['http.target'] === `/test-outgoing-fetch-external-disallowed` + ); + }); + + const response = await fetch(`${baseURL}/test-outgoing-fetch-external-disallowed`); + const data = await response.json(); + + const inboundTransaction = await inboundTransactionPromise; + + const traceId = inboundTransaction?.contexts?.trace?.trace_id; + const spanId = inboundTransaction?.spans?.find(span => span.op === 'http.client')?.span_id; + + expect(traceId).toEqual(expect.any(String)); + expect(spanId).toEqual(expect.any(String)); + + expect(data.route).toBe('external-disallowed'); + expect(data.headers?.['sentry-trace']).toBeUndefined(); + expect(data.headers?.baggage).toBeUndefined(); +}); diff --git a/dev-packages/e2e-tests/test-applications/node-nestjs-distributed-tracing/tsconfig.build.json b/dev-packages/e2e-tests/test-applications/node-nestjs-distributed-tracing/tsconfig.build.json new file mode 100644 index 000000000000..26c30d4eddf2 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/node-nestjs-distributed-tracing/tsconfig.build.json @@ -0,0 +1,4 @@ +{ + "extends": "./tsconfig.json", + "exclude": ["node_modules", "test", "dist"] +} diff --git a/dev-packages/e2e-tests/test-applications/node-nestjs-distributed-tracing/tsconfig.json b/dev-packages/e2e-tests/test-applications/node-nestjs-distributed-tracing/tsconfig.json new file mode 100644 index 000000000000..95f5641cf7f3 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/node-nestjs-distributed-tracing/tsconfig.json @@ -0,0 +1,21 @@ +{ + "compilerOptions": { + "module": "commonjs", + "declaration": true, + "removeComments": true, + "emitDecoratorMetadata": true, + "experimentalDecorators": true, + "allowSyntheticDefaultImports": true, + "target": "ES2021", + "sourceMap": true, + "outDir": "./dist", + "baseUrl": "./", + "incremental": true, + "skipLibCheck": true, + "strictNullChecks": false, + "noImplicitAny": false, + "strictBindCallApply": false, + "forceConsistentCasingInFileNames": false, + "noFallthroughCasesInSwitch": false + } +} From e19f43423c0a5573aca2cded370381b186751eb7 Mon Sep 17 00:00:00 2001 From: nicohrubec Date: Thu, 18 Jul 2024 14:05:16 +0200 Subject: [PATCH 42/60] Lint --- .../test-applications/nestjs-basic/src/app.module.ts | 2 +- .../src/trace-initiator.module.ts | 2 +- .../nestjs-with-submodules/src/app.module.ts | 2 +- .../nestjs-with-submodules/tests/errors.test.ts | 6 +++--- .../test-applications/node-nestjs-basic/src/main.ts | 4 ++-- .../node-nestjs-distributed-tracing/src/main.ts | 4 ++-- 6 files changed, 10 insertions(+), 10 deletions(-) diff --git a/dev-packages/e2e-tests/test-applications/nestjs-basic/src/app.module.ts b/dev-packages/e2e-tests/test-applications/nestjs-basic/src/app.module.ts index e0b4bd4b43b6..da1ccfb71360 100644 --- a/dev-packages/e2e-tests/test-applications/nestjs-basic/src/app.module.ts +++ b/dev-packages/e2e-tests/test-applications/nestjs-basic/src/app.module.ts @@ -1,8 +1,8 @@ import { Module } from '@nestjs/common'; import { ScheduleModule } from '@nestjs/schedule'; +import { SentryIntegrationModule } from '@sentry/nestjs/setup'; import { AppController } from './app.controller'; import { AppService } from './app.service'; -import {SentryIntegrationModule} from "@sentry/nestjs/setup"; @Module({ imports: [SentryIntegrationModule.forRoot(), ScheduleModule.forRoot()], diff --git a/dev-packages/e2e-tests/test-applications/nestjs-distributed-tracing/src/trace-initiator.module.ts b/dev-packages/e2e-tests/test-applications/nestjs-distributed-tracing/src/trace-initiator.module.ts index 0fa7a7a25175..e110f2883cd3 100644 --- a/dev-packages/e2e-tests/test-applications/nestjs-distributed-tracing/src/trace-initiator.module.ts +++ b/dev-packages/e2e-tests/test-applications/nestjs-distributed-tracing/src/trace-initiator.module.ts @@ -1,7 +1,7 @@ import { Module } from '@nestjs/common'; +import { SentryIntegrationModule } from '@sentry/nestjs/setup'; import { TraceInitiatorController } from './trace-initiator.controller'; import { TraceInitiatorService } from './trace-initiator.service'; -import {SentryIntegrationModule} from "@sentry/nestjs/setup"; @Module({ imports: [SentryIntegrationModule.forRoot()], diff --git a/dev-packages/e2e-tests/test-applications/nestjs-with-submodules/src/app.module.ts b/dev-packages/e2e-tests/test-applications/nestjs-with-submodules/src/app.module.ts index d96f80ca49c9..4a396093686a 100644 --- a/dev-packages/e2e-tests/test-applications/nestjs-with-submodules/src/app.module.ts +++ b/dev-packages/e2e-tests/test-applications/nestjs-with-submodules/src/app.module.ts @@ -1,8 +1,8 @@ import { Module } from '@nestjs/common'; +import { SentryIntegrationModule } from '@sentry/nestjs/setup'; import { AppController } from './app.controller'; import { AppService } from './app.service'; import { ExampleModule } from './example-module/example.module'; -import { SentryIntegrationModule } from '@sentry/nestjs/setup'; @Module({ imports: [SentryIntegrationModule.forRoot(), ExampleModule], diff --git a/dev-packages/e2e-tests/test-applications/nestjs-with-submodules/tests/errors.test.ts b/dev-packages/e2e-tests/test-applications/nestjs-with-submodules/tests/errors.test.ts index 4c20d16ee91b..ab93f8a498d2 100644 --- a/dev-packages/e2e-tests/test-applications/nestjs-with-submodules/tests/errors.test.ts +++ b/dev-packages/e2e-tests/test-applications/nestjs-with-submodules/tests/errors.test.ts @@ -1,9 +1,9 @@ import { expect, test } from '@playwright/test'; -import {waitForError, waitForTransaction} from '@sentry-internal/test-utils'; +import { waitForError, waitForTransaction } from '@sentry-internal/test-utils'; test('Handles exception correctly and does not send to Sentry if exception is thrown in module', async ({ - baseURL, - }) => { + baseURL, +}) => { let errorEventOccurred = false; waitForError('nestjs', event => { diff --git a/dev-packages/e2e-tests/test-applications/node-nestjs-basic/src/main.ts b/dev-packages/e2e-tests/test-applications/node-nestjs-basic/src/main.ts index 0dcf80a4f361..3a7b5ded8645 100644 --- a/dev-packages/e2e-tests/test-applications/node-nestjs-basic/src/main.ts +++ b/dev-packages/e2e-tests/test-applications/node-nestjs-basic/src/main.ts @@ -2,9 +2,9 @@ import './instrument'; // Import other modules -import {BaseExceptionFilter, HttpAdapterHost, NestFactory} from '@nestjs/core'; +import { BaseExceptionFilter, HttpAdapterHost, NestFactory } from '@nestjs/core'; +import * as Sentry from '@sentry/nestjs'; import { AppModule } from './app.module'; -import * as Sentry from "@sentry/nestjs"; const PORT = 3030; diff --git a/dev-packages/e2e-tests/test-applications/node-nestjs-distributed-tracing/src/main.ts b/dev-packages/e2e-tests/test-applications/node-nestjs-distributed-tracing/src/main.ts index bbdac0d2f1c5..7e3f7e0e2b52 100644 --- a/dev-packages/e2e-tests/test-applications/node-nestjs-distributed-tracing/src/main.ts +++ b/dev-packages/e2e-tests/test-applications/node-nestjs-distributed-tracing/src/main.ts @@ -2,10 +2,10 @@ import './instrument'; // Import other modules -import {BaseExceptionFilter, HttpAdapterHost, NestFactory} from '@nestjs/core'; +import { BaseExceptionFilter, HttpAdapterHost, NestFactory } from '@nestjs/core'; +import * as Sentry from '@sentry/nestjs'; import { TraceInitiatorModule } from './trace-initiator.module'; import { TraceReceiverModule } from './trace-receiver.module'; -import * as Sentry from "@sentry/nestjs"; const TRACE_INITIATOR_PORT = 3030; const TRACE_RECEIVER_PORT = 3040; From 17e4237a38a121d8021e6df7f65984054ecb2d9d Mon Sep 17 00:00:00 2001 From: nicohrubec Date: Thu, 18 Jul 2024 14:20:34 +0200 Subject: [PATCH 43/60] Move decorators to class definition + add docstrings and notes --- packages/nestjs/src/setup.ts | 55 +++++++++++++++++++----------------- 1 file changed, 29 insertions(+), 26 deletions(-) diff --git a/packages/nestjs/src/setup.ts b/packages/nestjs/src/setup.ts index 497fa45e6f9e..e58e65391223 100644 --- a/packages/nestjs/src/setup.ts +++ b/packages/nestjs/src/setup.ts @@ -25,11 +25,15 @@ import { logger } from '@sentry/utils'; import type { Observable } from 'rxjs'; /** - * + * Note: We cannot use @ syntax to add the decorators, so we add them directly below the classes as function wrappers. + */ + +/** + * Interceptor to add Sentry tracing capabilities to Nest.js applications. */ export class SentryTracingInterceptor implements NestInterceptor { /** - * + * Intercepts HTTP requests to set the transaction name for Sentry tracing. */ public intercept(context: ExecutionContext, next: CallHandler): Observable { if (getIsolationScope() === getDefaultIsolationScope()) { @@ -49,13 +53,14 @@ export class SentryTracingInterceptor implements NestInterceptor { return next.handle(); } } +Injectable()(SentryTracingInterceptor); /** - * + * Global filter to handle exceptions and report them to Sentry. */ export class SentryGlobalFilter extends BaseExceptionFilter { /** - * + * Catches exceptions and reports them to Sentry unless they are expected errors. */ public catch(exception: unknown, host: ArgumentsHost): void { const status_code = (exception as { status?: number }).status; @@ -69,13 +74,14 @@ export class SentryGlobalFilter extends BaseExceptionFilter { return super.catch(exception, host); } } +Catch()(SentryGlobalFilter); /** - * Set up a nest service that provides error handling and performance tracing. + * Service to set up Sentry performance tracing for Nest.js applications. */ export class SentryIntegrationService implements OnModuleInit { /** - * Called when the SentryModuleIntegration gets initialized. + * Initializes the Sentry integration service and registers span attributes. */ public onModuleInit(): void { // Sadly, NestInstrumentation has no requestHook, so we need to add the attributes here @@ -89,13 +95,14 @@ export class SentryIntegrationService implements OnModuleInit { } } } +Injectable()(SentryIntegrationService); /** * Set up a root module that can be injected in nest applications. */ export class SentryIntegrationModule { /** - * Called by the user to set the module as root module in a nest application. + * Configures the module as the root module in a Nest.js application. */ public static forRoot(): DynamicModule { return { @@ -115,6 +122,21 @@ export class SentryIntegrationModule { }; } } +Global()(SentryIntegrationModule); +Module({ + providers: [ + SentryIntegrationService, + { + provide: APP_FILTER, + useClass: SentryGlobalFilter, + }, + { + provide: APP_INTERCEPTOR, + useClass: SentryTracingInterceptor, + }, + ], + exports: [SentryIntegrationService], +})(SentryIntegrationModule); function addNestSpanAttributes(span: Span): void { const attributes = spanToJSON(span).data || {}; @@ -132,22 +154,3 @@ function addNestSpanAttributes(span: Span): void { [SEMANTIC_ATTRIBUTE_SENTRY_OP]: `${type}.nestjs`, }); } - -Catch()(SentryGlobalFilter); -Injectable()(SentryTracingInterceptor); -Injectable()(SentryIntegrationService); -Global()(SentryIntegrationModule); -Module({ - providers: [ - SentryIntegrationService, - { - provide: APP_FILTER, - useClass: SentryGlobalFilter, - }, - { - provide: APP_INTERCEPTOR, - useClass: SentryTracingInterceptor, - }, - ], - exports: [SentryIntegrationService], -})(SentryIntegrationModule); From b11ecd2c5c5ca4e8ca2156c03623400a2cb0b4c6 Mon Sep 17 00:00:00 2001 From: nicohrubec Date: Thu, 18 Jul 2024 16:59:38 +0200 Subject: [PATCH 44/60] Remove compiler options again from package --- packages/nestjs/tsconfig.json | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/packages/nestjs/tsconfig.json b/packages/nestjs/tsconfig.json index a368e55c8542..b0eb9ecb6476 100644 --- a/packages/nestjs/tsconfig.json +++ b/packages/nestjs/tsconfig.json @@ -3,7 +3,5 @@ "include": ["src/**/*"], - "compilerOptions": { - "experimentalDecorators": true - } + "compilerOptions": {} } From 5b23ae6ca1ef2d262c6cdaa27670621cdf3a3532 Mon Sep 17 00:00:00 2001 From: nicohrubec Date: Fri, 19 Jul 2024 08:58:21 +0200 Subject: [PATCH 45/60] Rename example module --- .../nestjs-with-submodules/src/app.module.ts | 4 ++-- .../example.controller.ts | 0 .../example.exception.ts | 0 .../example.filter.ts | 0 .../example.module.ts | 2 +- 5 files changed, 3 insertions(+), 3 deletions(-) rename dev-packages/e2e-tests/test-applications/nestjs-with-submodules/src/{example-module => example-module-global-filter}/example.controller.ts (100%) rename dev-packages/e2e-tests/test-applications/nestjs-with-submodules/src/{example-module => example-module-global-filter}/example.exception.ts (100%) rename dev-packages/e2e-tests/test-applications/nestjs-with-submodules/src/{example-module => example-module-global-filter}/example.filter.ts (100%) rename dev-packages/e2e-tests/test-applications/nestjs-with-submodules/src/{example-module => example-module-global-filter}/example.module.ts (89%) diff --git a/dev-packages/e2e-tests/test-applications/nestjs-with-submodules/src/app.module.ts b/dev-packages/e2e-tests/test-applications/nestjs-with-submodules/src/app.module.ts index 4a396093686a..f941e0bf065a 100644 --- a/dev-packages/e2e-tests/test-applications/nestjs-with-submodules/src/app.module.ts +++ b/dev-packages/e2e-tests/test-applications/nestjs-with-submodules/src/app.module.ts @@ -2,10 +2,10 @@ import { Module } from '@nestjs/common'; import { SentryIntegrationModule } from '@sentry/nestjs/setup'; import { AppController } from './app.controller'; import { AppService } from './app.service'; -import { ExampleModule } from './example-module/example.module'; +import { ExampleModuleGlobalFilter } from './example-module-global-filter/example.module'; @Module({ - imports: [SentryIntegrationModule.forRoot(), ExampleModule], + imports: [SentryIntegrationModule.forRoot(), ExampleModuleGlobalFilter], controllers: [AppController], providers: [AppService], }) diff --git a/dev-packages/e2e-tests/test-applications/nestjs-with-submodules/src/example-module/example.controller.ts b/dev-packages/e2e-tests/test-applications/nestjs-with-submodules/src/example-module-global-filter/example.controller.ts similarity index 100% rename from dev-packages/e2e-tests/test-applications/nestjs-with-submodules/src/example-module/example.controller.ts rename to dev-packages/e2e-tests/test-applications/nestjs-with-submodules/src/example-module-global-filter/example.controller.ts diff --git a/dev-packages/e2e-tests/test-applications/nestjs-with-submodules/src/example-module/example.exception.ts b/dev-packages/e2e-tests/test-applications/nestjs-with-submodules/src/example-module-global-filter/example.exception.ts similarity index 100% rename from dev-packages/e2e-tests/test-applications/nestjs-with-submodules/src/example-module/example.exception.ts rename to dev-packages/e2e-tests/test-applications/nestjs-with-submodules/src/example-module-global-filter/example.exception.ts diff --git a/dev-packages/e2e-tests/test-applications/nestjs-with-submodules/src/example-module/example.filter.ts b/dev-packages/e2e-tests/test-applications/nestjs-with-submodules/src/example-module-global-filter/example.filter.ts similarity index 100% rename from dev-packages/e2e-tests/test-applications/nestjs-with-submodules/src/example-module/example.filter.ts rename to dev-packages/e2e-tests/test-applications/nestjs-with-submodules/src/example-module-global-filter/example.filter.ts diff --git a/dev-packages/e2e-tests/test-applications/nestjs-with-submodules/src/example-module/example.module.ts b/dev-packages/e2e-tests/test-applications/nestjs-with-submodules/src/example-module-global-filter/example.module.ts similarity index 89% rename from dev-packages/e2e-tests/test-applications/nestjs-with-submodules/src/example-module/example.module.ts rename to dev-packages/e2e-tests/test-applications/nestjs-with-submodules/src/example-module-global-filter/example.module.ts index fabd71c4df90..8052cb137aac 100644 --- a/dev-packages/e2e-tests/test-applications/nestjs-with-submodules/src/example-module/example.module.ts +++ b/dev-packages/e2e-tests/test-applications/nestjs-with-submodules/src/example-module-global-filter/example.module.ts @@ -13,4 +13,4 @@ import { ExampleExceptionFilter } from './example.filter'; }, ], }) -export class ExampleModule {} +export class ExampleModuleGlobalFilter {} From 72d003109f40c975e8d8b13f528a42acc5fd17e9 Mon Sep 17 00:00:00 2001 From: nicohrubec Date: Fri, 19 Jul 2024 10:15:06 +0200 Subject: [PATCH 46/60] Add test with local filter --- .../nestjs-with-submodules/src/app.module.ts | 3 +- .../example.controller.ts | 14 ++++++++ .../example.exception.ts | 5 +++ .../example.filter.ts | 13 ++++++++ .../example.module.ts | 9 ++++++ .../tests/errors.test.ts | 32 +++++++++++++++++-- 6 files changed, 72 insertions(+), 4 deletions(-) create mode 100644 dev-packages/e2e-tests/test-applications/nestjs-with-submodules/src/example-module-local-filter/example.controller.ts create mode 100644 dev-packages/e2e-tests/test-applications/nestjs-with-submodules/src/example-module-local-filter/example.exception.ts create mode 100644 dev-packages/e2e-tests/test-applications/nestjs-with-submodules/src/example-module-local-filter/example.filter.ts create mode 100644 dev-packages/e2e-tests/test-applications/nestjs-with-submodules/src/example-module-local-filter/example.module.ts diff --git a/dev-packages/e2e-tests/test-applications/nestjs-with-submodules/src/app.module.ts b/dev-packages/e2e-tests/test-applications/nestjs-with-submodules/src/app.module.ts index f941e0bf065a..bc2df0623eab 100644 --- a/dev-packages/e2e-tests/test-applications/nestjs-with-submodules/src/app.module.ts +++ b/dev-packages/e2e-tests/test-applications/nestjs-with-submodules/src/app.module.ts @@ -3,9 +3,10 @@ import { SentryIntegrationModule } from '@sentry/nestjs/setup'; import { AppController } from './app.controller'; import { AppService } from './app.service'; import { ExampleModuleGlobalFilter } from './example-module-global-filter/example.module'; +import { ExampleModuleLocalFilter } from "./example-module-local-filter/example.module"; @Module({ - imports: [SentryIntegrationModule.forRoot(), ExampleModuleGlobalFilter], + imports: [SentryIntegrationModule.forRoot(), ExampleModuleGlobalFilter, ExampleModuleLocalFilter], controllers: [AppController], providers: [AppService], }) diff --git a/dev-packages/e2e-tests/test-applications/nestjs-with-submodules/src/example-module-local-filter/example.controller.ts b/dev-packages/e2e-tests/test-applications/nestjs-with-submodules/src/example-module-local-filter/example.controller.ts new file mode 100644 index 000000000000..c7e8f4cf57b1 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/nestjs-with-submodules/src/example-module-local-filter/example.controller.ts @@ -0,0 +1,14 @@ +import { Controller, Get, UseFilters } from '@nestjs/common'; +import { LocalExampleException } from './example.exception'; +import { LocalExampleExceptionFilter } from './example.filter'; + +@Controller('example-module-local-filter') +@UseFilters(LocalExampleExceptionFilter) +export class ExampleControllerLocalFilter { + constructor() {} + + @Get() + getExampleException() { + throw new LocalExampleException(); + } +} diff --git a/dev-packages/e2e-tests/test-applications/nestjs-with-submodules/src/example-module-local-filter/example.exception.ts b/dev-packages/e2e-tests/test-applications/nestjs-with-submodules/src/example-module-local-filter/example.exception.ts new file mode 100644 index 000000000000..85a64d26d75e --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/nestjs-with-submodules/src/example-module-local-filter/example.exception.ts @@ -0,0 +1,5 @@ +export class LocalExampleException extends Error { + constructor() { + super('Something went wrong in the example module with local filter!'); + } +} diff --git a/dev-packages/e2e-tests/test-applications/nestjs-with-submodules/src/example-module-local-filter/example.filter.ts b/dev-packages/e2e-tests/test-applications/nestjs-with-submodules/src/example-module-local-filter/example.filter.ts new file mode 100644 index 000000000000..9b6797c95e44 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/nestjs-with-submodules/src/example-module-local-filter/example.filter.ts @@ -0,0 +1,13 @@ +import { ArgumentsHost, BadRequestException, Catch } from '@nestjs/common'; +import { BaseExceptionFilter } from '@nestjs/core'; +import { LocalExampleException } from './example.exception'; + +@Catch(LocalExampleException) +export class LocalExampleExceptionFilter extends BaseExceptionFilter { + catch(exception: unknown, host: ArgumentsHost) { + if (exception instanceof LocalExampleException) { + return super.catch(new BadRequestException(exception.message), host); + } + return super.catch(exception, host); + } +} diff --git a/dev-packages/e2e-tests/test-applications/nestjs-with-submodules/src/example-module-local-filter/example.module.ts b/dev-packages/e2e-tests/test-applications/nestjs-with-submodules/src/example-module-local-filter/example.module.ts new file mode 100644 index 000000000000..91d229a553c1 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/nestjs-with-submodules/src/example-module-local-filter/example.module.ts @@ -0,0 +1,9 @@ +import { Module } from '@nestjs/common'; +import { ExampleControllerLocalFilter } from './example.controller'; + +@Module({ + imports: [], + controllers: [ExampleControllerLocalFilter], + providers: [], +}) +export class ExampleModuleLocalFilter {} diff --git a/dev-packages/e2e-tests/test-applications/nestjs-with-submodules/tests/errors.test.ts b/dev-packages/e2e-tests/test-applications/nestjs-with-submodules/tests/errors.test.ts index ab93f8a498d2..c2a2bac9194f 100644 --- a/dev-packages/e2e-tests/test-applications/nestjs-with-submodules/tests/errors.test.ts +++ b/dev-packages/e2e-tests/test-applications/nestjs-with-submodules/tests/errors.test.ts @@ -1,9 +1,7 @@ import { expect, test } from '@playwright/test'; import { waitForError, waitForTransaction } from '@sentry-internal/test-utils'; -test('Handles exception correctly and does not send to Sentry if exception is thrown in module', async ({ - baseURL, -}) => { +test('Custom global filter defined in module handles exception correctly', async ({ baseURL }) => { let errorEventOccurred = false; waitForError('nestjs', event => { @@ -27,3 +25,31 @@ test('Handles exception correctly and does not send to Sentry if exception is th expect(errorEventOccurred).toBe(false); }); + +test('Custom local filter defined in module handles exception correctly', async ({ baseURL }) => { + let errorEventOccurred = false; + + waitForError('nestjs', event => { + if ( + !event.type && + event.exception?.values?.[0]?.value === 'Something went wrong in the example module with local filter!' + ) { + errorEventOccurred = true; + } + + return event?.transaction === 'GET /example-module-local-filter'; + }); + + const transactionEventPromise = waitForTransaction('nestjs', transactionEvent => { + return transactionEvent?.transaction === 'GET /example-module-local-filter'; + }); + + const response = await fetch(`${baseURL}/example-module-local-filter`); + expect(response.status).toBe(400); + + await transactionEventPromise; + + await new Promise(resolve => setTimeout(resolve, 10000)); + + expect(errorEventOccurred).toBe(false); +}); From 07533a7e9eb7b6f2072f4e513dd8f41f95d193c4 Mon Sep 17 00:00:00 2001 From: nicohrubec Date: Fri, 19 Jul 2024 11:21:41 +0200 Subject: [PATCH 47/60] Test: Send uncaught exception to sentry if thrown in module with global filter + some renamings --- .../nestjs-with-submodules/src/app.module.ts | 2 +- .../example.controller.ts | 9 ++++- .../example.controller.ts | 4 +- .../tests/errors.test.ts | 40 ++++++++++++++++--- 4 files changed, 44 insertions(+), 11 deletions(-) diff --git a/dev-packages/e2e-tests/test-applications/nestjs-with-submodules/src/app.module.ts b/dev-packages/e2e-tests/test-applications/nestjs-with-submodules/src/app.module.ts index bc2df0623eab..50e4f3503ce7 100644 --- a/dev-packages/e2e-tests/test-applications/nestjs-with-submodules/src/app.module.ts +++ b/dev-packages/e2e-tests/test-applications/nestjs-with-submodules/src/app.module.ts @@ -3,7 +3,7 @@ import { SentryIntegrationModule } from '@sentry/nestjs/setup'; import { AppController } from './app.controller'; import { AppService } from './app.service'; import { ExampleModuleGlobalFilter } from './example-module-global-filter/example.module'; -import { ExampleModuleLocalFilter } from "./example-module-local-filter/example.module"; +import { ExampleModuleLocalFilter } from './example-module-local-filter/example.module'; @Module({ imports: [SentryIntegrationModule.forRoot(), ExampleModuleGlobalFilter, ExampleModuleLocalFilter], diff --git a/dev-packages/e2e-tests/test-applications/nestjs-with-submodules/src/example-module-global-filter/example.controller.ts b/dev-packages/e2e-tests/test-applications/nestjs-with-submodules/src/example-module-global-filter/example.controller.ts index ab10703510b7..098a6727bf59 100644 --- a/dev-packages/e2e-tests/test-applications/nestjs-with-submodules/src/example-module-global-filter/example.controller.ts +++ b/dev-packages/e2e-tests/test-applications/nestjs-with-submodules/src/example-module-global-filter/example.controller.ts @@ -5,8 +5,13 @@ import { ExampleException } from './example.exception'; export class ExampleController { constructor() {} - @Get() - getExampleException(): string { + @Get('/expected-exception') + getCaughtException(): string { throw new ExampleException(); } + + @Get('/unexpected-exception') + getUncaughtException(): string { + throw new Error(`This is an uncaught exception!`); + } } diff --git a/dev-packages/e2e-tests/test-applications/nestjs-with-submodules/src/example-module-local-filter/example.controller.ts b/dev-packages/e2e-tests/test-applications/nestjs-with-submodules/src/example-module-local-filter/example.controller.ts index c7e8f4cf57b1..41d75d6eaf89 100644 --- a/dev-packages/e2e-tests/test-applications/nestjs-with-submodules/src/example-module-local-filter/example.controller.ts +++ b/dev-packages/e2e-tests/test-applications/nestjs-with-submodules/src/example-module-local-filter/example.controller.ts @@ -7,8 +7,8 @@ import { LocalExampleExceptionFilter } from './example.filter'; export class ExampleControllerLocalFilter { constructor() {} - @Get() - getExampleException() { + @Get('/expected-exception') + getCaughtException() { throw new LocalExampleException(); } } diff --git a/dev-packages/e2e-tests/test-applications/nestjs-with-submodules/tests/errors.test.ts b/dev-packages/e2e-tests/test-applications/nestjs-with-submodules/tests/errors.test.ts index c2a2bac9194f..581375ba980d 100644 --- a/dev-packages/e2e-tests/test-applications/nestjs-with-submodules/tests/errors.test.ts +++ b/dev-packages/e2e-tests/test-applications/nestjs-with-submodules/tests/errors.test.ts @@ -1,6 +1,34 @@ import { expect, test } from '@playwright/test'; import { waitForError, waitForTransaction } from '@sentry-internal/test-utils'; +test('Sends unexpected exception to Sentry if thrown in module with global filter', async ({ baseURL }) => { + const errorEventPromise = waitForError('nestjs', event => { + return !event.type && event.exception?.values?.[0]?.value === 'This is an uncaught exception!'; + }); + + const response = await fetch(`${baseURL}/example-module/unexpected-exception`); + expect(response.status).toBe(500); + + const errorEvent = await errorEventPromise; + + expect(errorEvent.exception?.values).toHaveLength(1); + expect(errorEvent.exception?.values?.[0]?.value).toBe('This is an uncaught exception!'); + + expect(errorEvent.request).toEqual({ + method: 'GET', + cookies: {}, + headers: expect.any(Object), + url: 'http://localhost:3030/example-module/unexpected-exception', + }); + + expect(errorEvent.transaction).toEqual('GET /example-module/unexpected-exception'); + + expect(errorEvent.contexts?.trace).toEqual({ + trace_id: expect.any(String), + span_id: expect.any(String), + }); +}); + test('Custom global filter defined in module handles exception correctly', async ({ baseURL }) => { let errorEventOccurred = false; @@ -9,14 +37,14 @@ test('Custom global filter defined in module handles exception correctly', async errorEventOccurred = true; } - return event?.transaction === 'GET /example-module'; + return event?.transaction === 'GET /example-module/expected-exception'; }); const transactionEventPromise = waitForTransaction('nestjs', transactionEvent => { - return transactionEvent?.transaction === 'GET /example-module'; + return transactionEvent?.transaction === 'GET /example-module/expected-exception'; }); - const response = await fetch(`${baseURL}/example-module`); + const response = await fetch(`${baseURL}/example-module/expected-exception`); expect(response.status).toBe(400); await transactionEventPromise; @@ -37,14 +65,14 @@ test('Custom local filter defined in module handles exception correctly', async errorEventOccurred = true; } - return event?.transaction === 'GET /example-module-local-filter'; + return event?.transaction === 'GET /example-module-local-filter/expected-exception'; }); const transactionEventPromise = waitForTransaction('nestjs', transactionEvent => { - return transactionEvent?.transaction === 'GET /example-module-local-filter'; + return transactionEvent?.transaction === 'GET /example-module-local-filter/expected-exception'; }); - const response = await fetch(`${baseURL}/example-module-local-filter`); + const response = await fetch(`${baseURL}/example-module-local-filter/expected-exception`); expect(response.status).toBe(400); await transactionEventPromise; From a0dcd4f07beadef9c96385921844254cb3052b84 Mon Sep 17 00:00:00 2001 From: nicohrubec Date: Fri, 19 Jul 2024 11:31:46 +0200 Subject: [PATCH 48/60] Add transaction test --- .../example.controller.ts | 8 ++ .../tests/transactions.test.ts | 123 ++++++++++++++++++ 2 files changed, 131 insertions(+) create mode 100644 dev-packages/e2e-tests/test-applications/nestjs-with-submodules/tests/transactions.test.ts diff --git a/dev-packages/e2e-tests/test-applications/nestjs-with-submodules/src/example-module-global-filter/example.controller.ts b/dev-packages/e2e-tests/test-applications/nestjs-with-submodules/src/example-module-global-filter/example.controller.ts index 098a6727bf59..53356e906130 100644 --- a/dev-packages/e2e-tests/test-applications/nestjs-with-submodules/src/example-module-global-filter/example.controller.ts +++ b/dev-packages/e2e-tests/test-applications/nestjs-with-submodules/src/example-module-global-filter/example.controller.ts @@ -1,4 +1,5 @@ import { Controller, Get } from '@nestjs/common'; +import * as Sentry from '@sentry/nestjs'; import { ExampleException } from './example.exception'; @Controller('example-module') @@ -14,4 +15,11 @@ export class ExampleController { getUncaughtException(): string { throw new Error(`This is an uncaught exception!`); } + + @Get('/transaction') + testTransaction() { + Sentry.startSpan({ name: 'test-span' }, () => { + Sentry.startSpan({ name: 'child-span' }, () => {}); + }); + } } diff --git a/dev-packages/e2e-tests/test-applications/nestjs-with-submodules/tests/transactions.test.ts b/dev-packages/e2e-tests/test-applications/nestjs-with-submodules/tests/transactions.test.ts new file mode 100644 index 000000000000..25375f5fd962 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/nestjs-with-submodules/tests/transactions.test.ts @@ -0,0 +1,123 @@ +import { expect, test } from '@playwright/test'; +import { waitForTransaction } from '@sentry-internal/test-utils'; + +test('Sends an API route transaction from module', async ({ baseURL }) => { + const pageloadTransactionEventPromise = waitForTransaction('nestjs', transactionEvent => { + return ( + transactionEvent?.contexts?.trace?.op === 'http.server' && + transactionEvent?.transaction === 'GET /example-module/transaction' + ); + }); + + await fetch(`${baseURL}/example-module/transaction`); + + const transactionEvent = await pageloadTransactionEventPromise; + + expect(transactionEvent.contexts?.trace).toEqual({ + data: { + 'sentry.source': 'route', + 'sentry.origin': 'auto.http.otel.http', + 'sentry.op': 'http.server', + 'sentry.sample_rate': 1, + url: 'http://localhost:3030/example-module/transaction', + 'otel.kind': 'SERVER', + 'http.response.status_code': 200, + 'http.url': 'http://localhost:3030/example-module/transaction', + 'http.host': 'localhost:3030', + 'net.host.name': 'localhost', + 'http.method': 'GET', + 'http.scheme': 'http', + 'http.target': '/example-module/transaction', + 'http.user_agent': 'node', + 'http.flavor': '1.1', + 'net.transport': 'ip_tcp', + 'net.host.ip': expect.any(String), + 'net.host.port': expect.any(Number), + 'net.peer.ip': expect.any(String), + 'net.peer.port': expect.any(Number), + 'http.status_code': 200, + 'http.status_text': 'OK', + 'http.route': '/example-module/transaction', + }, + op: 'http.server', + span_id: expect.any(String), + status: 'ok', + trace_id: expect.any(String), + origin: 'auto.http.otel.http', + }); + + expect(transactionEvent).toEqual( + expect.objectContaining({ + spans: expect.arrayContaining([ + { + data: { + 'express.name': '/example-module/transaction', + 'express.type': 'request_handler', + 'http.route': '/example-module/transaction', + 'sentry.origin': 'auto.http.otel.express', + 'sentry.op': 'request_handler.express', + }, + op: 'request_handler.express', + description: '/example-module/transaction', + parent_span_id: expect.any(String), + span_id: expect.any(String), + start_timestamp: expect.any(Number), + status: 'ok', + timestamp: expect.any(Number), + trace_id: expect.any(String), + origin: 'auto.http.otel.express', + }, + { + data: { + 'sentry.origin': 'manual', + }, + description: 'test-span', + parent_span_id: expect.any(String), + span_id: expect.any(String), + start_timestamp: expect.any(Number), + status: 'ok', + timestamp: expect.any(Number), + trace_id: expect.any(String), + origin: 'manual', + }, + { + data: { + 'sentry.origin': 'manual', + }, + description: 'child-span', + parent_span_id: expect.any(String), + span_id: expect.any(String), + start_timestamp: expect.any(Number), + status: 'ok', + timestamp: expect.any(Number), + trace_id: expect.any(String), + origin: 'manual', + }, + { + span_id: expect.any(String), + trace_id: expect.any(String), + data: { + 'sentry.origin': 'auto.http.otel.nestjs', + 'sentry.op': 'handler.nestjs', + component: '@nestjs/core', + 'nestjs.version': expect.any(String), + 'nestjs.type': 'handler', + 'nestjs.callback': 'testTransaction', + }, + description: 'testTransaction', + parent_span_id: expect.any(String), + start_timestamp: expect.any(Number), + timestamp: expect.any(Number), + status: 'ok', + origin: 'auto.http.otel.nestjs', + op: 'handler.nestjs', + }, + ]), + transaction: 'GET /example-module/transaction', + type: 'transaction', + transaction_info: { + source: 'route', + }, + }), + ); +}); From ce3322a90839e9c100af84dc91cba463a8e51a06 Mon Sep 17 00:00:00 2001 From: nicohrubec Date: Fri, 19 Jul 2024 12:10:51 +0200 Subject: [PATCH 49/60] Testing wrong registration order --- .../nestjs-with-submodules/src/app.module.ts | 8 ++- .../example.controller.ts | 17 ++++++ .../example.exception.ts | 5 ++ .../example.filter.ts | 13 ++++ .../example.module.ts | 16 +++++ .../tests/errors.test.ts | 61 +++++++++++++++++++ 6 files changed, 119 insertions(+), 1 deletion(-) create mode 100644 dev-packages/e2e-tests/test-applications/nestjs-with-submodules/src/example-module-global-filter-wrong-registration-order/example.controller.ts create mode 100644 dev-packages/e2e-tests/test-applications/nestjs-with-submodules/src/example-module-global-filter-wrong-registration-order/example.exception.ts create mode 100644 dev-packages/e2e-tests/test-applications/nestjs-with-submodules/src/example-module-global-filter-wrong-registration-order/example.filter.ts create mode 100644 dev-packages/e2e-tests/test-applications/nestjs-with-submodules/src/example-module-global-filter-wrong-registration-order/example.module.ts diff --git a/dev-packages/e2e-tests/test-applications/nestjs-with-submodules/src/app.module.ts b/dev-packages/e2e-tests/test-applications/nestjs-with-submodules/src/app.module.ts index 50e4f3503ce7..8666aaf9a7a5 100644 --- a/dev-packages/e2e-tests/test-applications/nestjs-with-submodules/src/app.module.ts +++ b/dev-packages/e2e-tests/test-applications/nestjs-with-submodules/src/app.module.ts @@ -2,11 +2,17 @@ import { Module } from '@nestjs/common'; import { SentryIntegrationModule } from '@sentry/nestjs/setup'; import { AppController } from './app.controller'; import { AppService } from './app.service'; +import { ExampleModuleGlobalFilterWrongRegistrationOrder } from './example-module-global-filter-wrong-registration-order/example.module'; import { ExampleModuleGlobalFilter } from './example-module-global-filter/example.module'; import { ExampleModuleLocalFilter } from './example-module-local-filter/example.module'; @Module({ - imports: [SentryIntegrationModule.forRoot(), ExampleModuleGlobalFilter, ExampleModuleLocalFilter], + imports: [ + ExampleModuleGlobalFilterWrongRegistrationOrder, + SentryIntegrationModule.forRoot(), + ExampleModuleGlobalFilter, + ExampleModuleLocalFilter, + ], controllers: [AppController], providers: [AppService], }) diff --git a/dev-packages/e2e-tests/test-applications/nestjs-with-submodules/src/example-module-global-filter-wrong-registration-order/example.controller.ts b/dev-packages/e2e-tests/test-applications/nestjs-with-submodules/src/example-module-global-filter-wrong-registration-order/example.controller.ts new file mode 100644 index 000000000000..028af4a43f87 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/nestjs-with-submodules/src/example-module-global-filter-wrong-registration-order/example.controller.ts @@ -0,0 +1,17 @@ +import { Controller, Get } from '@nestjs/common'; +import { ExampleExceptionWrongRegistrationOrder } from './example.exception'; + +@Controller('example-module-wrong-order') +export class ExampleController { + constructor() {} + + @Get('/expected-exception') + getCaughtException(): string { + throw new ExampleExceptionWrongRegistrationOrder(); + } + + @Get('/unexpected-exception') + getUncaughtException(): string { + throw new Error(`This is an uncaught exception!`); + } +} diff --git a/dev-packages/e2e-tests/test-applications/nestjs-with-submodules/src/example-module-global-filter-wrong-registration-order/example.exception.ts b/dev-packages/e2e-tests/test-applications/nestjs-with-submodules/src/example-module-global-filter-wrong-registration-order/example.exception.ts new file mode 100644 index 000000000000..0e4f58314fa2 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/nestjs-with-submodules/src/example-module-global-filter-wrong-registration-order/example.exception.ts @@ -0,0 +1,5 @@ +export class ExampleExceptionWrongRegistrationOrder extends Error { + constructor() { + super('Something went wrong in the example module!'); + } +} diff --git a/dev-packages/e2e-tests/test-applications/nestjs-with-submodules/src/example-module-global-filter-wrong-registration-order/example.filter.ts b/dev-packages/e2e-tests/test-applications/nestjs-with-submodules/src/example-module-global-filter-wrong-registration-order/example.filter.ts new file mode 100644 index 000000000000..6ecdf88937aa --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/nestjs-with-submodules/src/example-module-global-filter-wrong-registration-order/example.filter.ts @@ -0,0 +1,13 @@ +import { ArgumentsHost, BadRequestException, Catch } from '@nestjs/common'; +import { BaseExceptionFilter } from '@nestjs/core'; +import { ExampleExceptionWrongRegistrationOrder } from './example.exception'; + +@Catch(ExampleExceptionWrongRegistrationOrder) +export class ExampleExceptionFilterWrongRegistrationOrder extends BaseExceptionFilter { + catch(exception: unknown, host: ArgumentsHost) { + if (exception instanceof ExampleExceptionWrongRegistrationOrder) { + return super.catch(new BadRequestException(exception.message), host); + } + return super.catch(exception, host); + } +} diff --git a/dev-packages/e2e-tests/test-applications/nestjs-with-submodules/src/example-module-global-filter-wrong-registration-order/example.module.ts b/dev-packages/e2e-tests/test-applications/nestjs-with-submodules/src/example-module-global-filter-wrong-registration-order/example.module.ts new file mode 100644 index 000000000000..c98a5757b01c --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/nestjs-with-submodules/src/example-module-global-filter-wrong-registration-order/example.module.ts @@ -0,0 +1,16 @@ +import { Module } from '@nestjs/common'; +import { APP_FILTER } from '@nestjs/core'; +import { ExampleController } from './example.controller'; +import { ExampleExceptionFilterWrongRegistrationOrder } from './example.filter'; + +@Module({ + imports: [], + controllers: [ExampleController], + providers: [ + { + provide: APP_FILTER, + useClass: ExampleExceptionFilterWrongRegistrationOrder, + }, + ], +}) +export class ExampleModuleGlobalFilterWrongRegistrationOrder {} diff --git a/dev-packages/e2e-tests/test-applications/nestjs-with-submodules/tests/errors.test.ts b/dev-packages/e2e-tests/test-applications/nestjs-with-submodules/tests/errors.test.ts index 581375ba980d..819ff3a55f6d 100644 --- a/dev-packages/e2e-tests/test-applications/nestjs-with-submodules/tests/errors.test.ts +++ b/dev-packages/e2e-tests/test-applications/nestjs-with-submodules/tests/errors.test.ts @@ -29,6 +29,36 @@ test('Sends unexpected exception to Sentry if thrown in module with global filte }); }); +test('Sends unexpected exception to Sentry if thrown in module that was registered before Sentry', async ({ + baseURL, +}) => { + const errorEventPromise = waitForError('nestjs', event => { + return !event.type && event.exception?.values?.[0]?.value === 'This is an uncaught exception!'; + }); + + const response = await fetch(`${baseURL}/example-module-wrong-order/unexpected-exception`); + expect(response.status).toBe(500); + + const errorEvent = await errorEventPromise; + + expect(errorEvent.exception?.values).toHaveLength(1); + expect(errorEvent.exception?.values?.[0]?.value).toBe('This is an uncaught exception!'); + + expect(errorEvent.request).toEqual({ + method: 'GET', + cookies: {}, + headers: expect.any(Object), + url: 'http://localhost:3030/example-module-wrong-order/unexpected-exception', + }); + + expect(errorEvent.transaction).toEqual('GET /example-module-wrong-order/unexpected-exception'); + + expect(errorEvent.contexts?.trace).toEqual({ + trace_id: expect.any(String), + span_id: expect.any(String), + }); +}); + test('Custom global filter defined in module handles exception correctly', async ({ baseURL }) => { let errorEventOccurred = false; @@ -81,3 +111,34 @@ test('Custom local filter defined in module handles exception correctly', async expect(errorEventOccurred).toBe(false); }); + +test('Does not handle expected exception if exception is thrown in module registered before Sentry', async ({ + baseURL, +}) => { + const errorEventPromise = waitForError('nestjs', event => { + return !event.type && event.exception?.values?.[0]?.value === 'Something went wrong in the example module!'; + }); + + const response = await fetch(`${baseURL}/example-module-wrong-order/expected-exception`); + expect(response.status).toBe(500); // should be 400 + + // should never arrive, but does because the exception is not handled properly + const errorEvent = await errorEventPromise; + + expect(errorEvent.exception?.values).toHaveLength(1); + expect(errorEvent.exception?.values?.[0]?.value).toBe('Something went wrong in the example module!'); + + expect(errorEvent.request).toEqual({ + method: 'GET', + cookies: {}, + headers: expect.any(Object), + url: 'http://localhost:3030/example-module-wrong-order/expected-exception', + }); + + expect(errorEvent.transaction).toEqual('GET /example-module-wrong-order/expected-exception'); + + expect(errorEvent.contexts?.trace).toEqual({ + trace_id: expect.any(String), + span_id: expect.any(String), + }); +}); From 220a943a4b82d5e280039491d8eff4408646e0fd Mon Sep 17 00:00:00 2001 From: nicohrubec Date: Fri, 19 Jul 2024 12:24:14 +0200 Subject: [PATCH 50/60] Add compiler option to verify nest works in subexport configured apps --- .../e2e-tests/test-applications/nestjs-basic/tsconfig.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/dev-packages/e2e-tests/test-applications/nestjs-basic/tsconfig.json b/dev-packages/e2e-tests/test-applications/nestjs-basic/tsconfig.json index 95f5641cf7f3..cf79f029c781 100644 --- a/dev-packages/e2e-tests/test-applications/nestjs-basic/tsconfig.json +++ b/dev-packages/e2e-tests/test-applications/nestjs-basic/tsconfig.json @@ -16,6 +16,7 @@ "noImplicitAny": false, "strictBindCallApply": false, "forceConsistentCasingInFileNames": false, - "noFallthroughCasesInSwitch": false + "noFallthroughCasesInSwitch": false, + "moduleResolution": "Node16" } } From da2b952561d4de93c0f90e1843206a1da7bf03d2 Mon Sep 17 00:00:00 2001 From: nicohrubec Date: Tue, 23 Jul 2024 13:02:20 +0200 Subject: [PATCH 51/60] More specific test names --- .../nestjs-with-submodules/tests/errors.test.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/dev-packages/e2e-tests/test-applications/nestjs-with-submodules/tests/errors.test.ts b/dev-packages/e2e-tests/test-applications/nestjs-with-submodules/tests/errors.test.ts index 819ff3a55f6d..cebe39aaae17 100644 --- a/dev-packages/e2e-tests/test-applications/nestjs-with-submodules/tests/errors.test.ts +++ b/dev-packages/e2e-tests/test-applications/nestjs-with-submodules/tests/errors.test.ts @@ -59,7 +59,7 @@ test('Sends unexpected exception to Sentry if thrown in module that was register }); }); -test('Custom global filter defined in module handles exception correctly', async ({ baseURL }) => { +test('Does not send exception to Sentry if user-defined global exception filter already catches the exception', async ({ baseURL }) => { let errorEventOccurred = false; waitForError('nestjs', event => { @@ -84,7 +84,7 @@ test('Custom global filter defined in module handles exception correctly', async expect(errorEventOccurred).toBe(false); }); -test('Custom local filter defined in module handles exception correctly', async ({ baseURL }) => { +test('Does not send exception to Sentry if user-defined local exception filter already catches the exception', async ({ baseURL }) => { let errorEventOccurred = false; waitForError('nestjs', event => { From b3b6d14725cd1abae7040461000448acaa38bf54 Mon Sep 17 00:00:00 2001 From: nicohrubec Date: Tue, 23 Jul 2024 13:20:07 +0200 Subject: [PATCH 52/60] Move exports below decorators --- .../nestjs-with-submodules/tests/errors.test.ts | 8 ++++++-- packages/nestjs/src/setup.ts | 15 +++++++++------ 2 files changed, 15 insertions(+), 8 deletions(-) diff --git a/dev-packages/e2e-tests/test-applications/nestjs-with-submodules/tests/errors.test.ts b/dev-packages/e2e-tests/test-applications/nestjs-with-submodules/tests/errors.test.ts index cebe39aaae17..8d5885f146df 100644 --- a/dev-packages/e2e-tests/test-applications/nestjs-with-submodules/tests/errors.test.ts +++ b/dev-packages/e2e-tests/test-applications/nestjs-with-submodules/tests/errors.test.ts @@ -59,7 +59,9 @@ test('Sends unexpected exception to Sentry if thrown in module that was register }); }); -test('Does not send exception to Sentry if user-defined global exception filter already catches the exception', async ({ baseURL }) => { +test('Does not send exception to Sentry if user-defined global exception filter already catches the exception', async ({ + baseURL, +}) => { let errorEventOccurred = false; waitForError('nestjs', event => { @@ -84,7 +86,9 @@ test('Does not send exception to Sentry if user-defined global exception filter expect(errorEventOccurred).toBe(false); }); -test('Does not send exception to Sentry if user-defined local exception filter already catches the exception', async ({ baseURL }) => { +test('Does not send exception to Sentry if user-defined local exception filter already catches the exception', async ({ + baseURL, +}) => { let errorEventOccurred = false; waitForError('nestjs', event => { diff --git a/packages/nestjs/src/setup.ts b/packages/nestjs/src/setup.ts index e58e65391223..d131e885d1fb 100644 --- a/packages/nestjs/src/setup.ts +++ b/packages/nestjs/src/setup.ts @@ -9,8 +9,7 @@ import type { import { Catch } from '@nestjs/common'; import { Injectable } from '@nestjs/common'; import { Global, Module } from '@nestjs/common'; -import { APP_FILTER, BaseExceptionFilter } from '@nestjs/core'; -import { APP_INTERCEPTOR } from '@nestjs/core'; +import { APP_FILTER, APP_INTERCEPTOR, BaseExceptionFilter } from '@nestjs/core'; import { SEMANTIC_ATTRIBUTE_SENTRY_OP, SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN, @@ -31,7 +30,7 @@ import type { Observable } from 'rxjs'; /** * Interceptor to add Sentry tracing capabilities to Nest.js applications. */ -export class SentryTracingInterceptor implements NestInterceptor { +class SentryTracingInterceptor implements NestInterceptor { /** * Intercepts HTTP requests to set the transaction name for Sentry tracing. */ @@ -54,11 +53,12 @@ export class SentryTracingInterceptor implements NestInterceptor { } } Injectable()(SentryTracingInterceptor); +export { SentryTracingInterceptor }; /** * Global filter to handle exceptions and report them to Sentry. */ -export class SentryGlobalFilter extends BaseExceptionFilter { +class SentryGlobalFilter extends BaseExceptionFilter { /** * Catches exceptions and reports them to Sentry unless they are expected errors. */ @@ -75,11 +75,12 @@ export class SentryGlobalFilter extends BaseExceptionFilter { } } Catch()(SentryGlobalFilter); +export { SentryGlobalFilter }; /** * Service to set up Sentry performance tracing for Nest.js applications. */ -export class SentryIntegrationService implements OnModuleInit { +class SentryIntegrationService implements OnModuleInit { /** * Initializes the Sentry integration service and registers span attributes. */ @@ -96,11 +97,12 @@ export class SentryIntegrationService implements OnModuleInit { } } Injectable()(SentryIntegrationService); +export { SentryIntegrationService }; /** * Set up a root module that can be injected in nest applications. */ -export class SentryIntegrationModule { +class SentryIntegrationModule { /** * Configures the module as the root module in a Nest.js application. */ @@ -137,6 +139,7 @@ Module({ ], exports: [SentryIntegrationService], })(SentryIntegrationModule); +export { SentryIntegrationModule }; function addNestSpanAttributes(span: Span): void { const attributes = spanToJSON(span).data || {}; From 2eb4bdc6c5ca8fcdad0774103cdd22612a609d7d Mon Sep 17 00:00:00 2001 From: nicohrubec Date: Tue, 23 Jul 2024 13:37:00 +0200 Subject: [PATCH 53/60] Remove duplicated // --- packages/nestjs/tsconfig.setup-types.json | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/nestjs/tsconfig.setup-types.json b/packages/nestjs/tsconfig.setup-types.json index b4534f32cf94..2ef9310f3edc 100644 --- a/packages/nestjs/tsconfig.setup-types.json +++ b/packages/nestjs/tsconfig.setup-types.json @@ -10,6 +10,5 @@ "//": "This type is built separately because it is for a subpath export, which has problems if it is not in the root", "include": ["src/setup.ts"], - "//": "Without this, we cannot output into the root dir", "exclude": [] } From 357a27489b9dc04eff98cab080bcdd1a8c1dc0c1 Mon Sep 17 00:00:00 2001 From: nicohrubec Date: Tue, 23 Jul 2024 13:46:00 +0200 Subject: [PATCH 54/60] DEBUG_BUILD --- packages/nestjs/src/setup.ts | 4 +++- packages/node/src/integrations/tracing/nest.ts | 4 +++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/packages/nestjs/src/setup.ts b/packages/nestjs/src/setup.ts index d131e885d1fb..d86fbc9a585f 100644 --- a/packages/nestjs/src/setup.ts +++ b/packages/nestjs/src/setup.ts @@ -19,6 +19,7 @@ import { getIsolationScope, spanToJSON, } from '@sentry/core'; +import { DEBUG_BUILD } from '@sentry/node/build/types/debug-build'; import type { Span } from '@sentry/types'; import { logger } from '@sentry/utils'; import type { Observable } from 'rxjs'; @@ -36,7 +37,8 @@ class SentryTracingInterceptor implements NestInterceptor { */ public intercept(context: ExecutionContext, next: CallHandler): Observable { if (getIsolationScope() === getDefaultIsolationScope()) { - logger.warn('Isolation scope is still the default isolation scope, skipping setting transactionName.'); + DEBUG_BUILD && + logger.warn('Isolation scope is still the default isolation scope, skipping setting transactionName.'); return next.handle(); } diff --git a/packages/node/src/integrations/tracing/nest.ts b/packages/node/src/integrations/tracing/nest.ts index ab6a66fdb895..032c544bc59d 100644 --- a/packages/node/src/integrations/tracing/nest.ts +++ b/packages/node/src/integrations/tracing/nest.ts @@ -11,6 +11,7 @@ import { } from '@sentry/core'; import type { IntegrationFn, Span } from '@sentry/types'; import { logger } from '@sentry/utils'; +import { DEBUG_BUILD } from '../../debug-build'; import { generateInstrumentOnce } from '../../otel/instrument'; interface MinimalNestJsExecutionContext { @@ -79,7 +80,8 @@ export function setupNestErrorHandler(app: MinimalNestJsApp, baseFilter: NestJsE app.useGlobalInterceptors({ intercept(context, next) { if (getIsolationScope() === getDefaultIsolationScope()) { - logger.warn('Isolation scope is still the default isolation scope, skipping setting transactionName.'); + DEBUG_BUILD && + logger.warn('Isolation scope is still the default isolation scope, skipping setting transactionName.'); return next.handle(); } From 46edc3484a7e43cdd6b417b55b234ca7d9fd086d Mon Sep 17 00:00:00 2001 From: nicohrubec Date: Tue, 23 Jul 2024 13:59:23 +0200 Subject: [PATCH 55/60] Update nest README --- packages/nestjs/README.md | 43 ++++++++++++++++++++++++++++++++++++++- 1 file changed, 42 insertions(+), 1 deletion(-) diff --git a/packages/nestjs/README.md b/packages/nestjs/README.md index e336a856c03e..2fc282cb34f1 100644 --- a/packages/nestjs/README.md +++ b/packages/nestjs/README.md @@ -24,6 +24,8 @@ yarn add @sentry/nestjs ## Usage +Add a instrument.ts file: + ```typescript // CJS Syntax const Sentry = require('@sentry/nestjs'); @@ -36,7 +38,46 @@ Sentry.init({ }); ``` -Note that it is necessary to initialize Sentry **before you import any package that may be instrumented by us**. +You need to require or import the instrument.js file before requiring any other modules in your application. This is +necessary to ensure that Sentry can automatically instrument all modules in your application: + +```typescript +// Import this first! +import './instrument'; + +// Now import other modules +import * as Sentry from '@sentry/nestjs'; +import { NestFactory } from '@nestjs/core'; +import { AppModule } from './app.module'; + +async function bootstrap() { + const app = await NestFactory.create(AppModule); + await app.listen(3000); +} + +bootstrap(); +``` + +Then you can add the SentryModule as a root module: + +```typescript +import { Module } from '@nestjs/common'; +import { SentryModule } from '@sentry/nestjs/setup'; +import { AppController } from './app.controller'; +import { AppService } from './app.service'; + +@Module({ + imports: [ + SentryModule.forRoot(), + // ...other modules + ], + controllers: [AppController], + providers: [AppService], +}) +export class AppModule {} +``` + +The SentryModule needs registered before any module that should be instrumented by Sentry. ## SentryTraced From 345b55841292d391fee38b88445ae219d78070b5 Mon Sep 17 00:00:00 2001 From: nicohrubec Date: Tue, 23 Jul 2024 14:22:22 +0200 Subject: [PATCH 56/60] Rename to SentryModule --- .../nestjs-basic/src/app.module.ts | 4 +-- .../src/trace-initiator.module.ts | 4 +-- .../nestjs-with-submodules/src/app.module.ts | 4 +-- packages/nestjs/src/setup.ts | 28 +++++++++---------- .../node/src/integrations/tracing/nest.ts | 4 +-- 5 files changed, 20 insertions(+), 24 deletions(-) diff --git a/dev-packages/e2e-tests/test-applications/nestjs-basic/src/app.module.ts b/dev-packages/e2e-tests/test-applications/nestjs-basic/src/app.module.ts index da1ccfb71360..f4c5ceb0cc5a 100644 --- a/dev-packages/e2e-tests/test-applications/nestjs-basic/src/app.module.ts +++ b/dev-packages/e2e-tests/test-applications/nestjs-basic/src/app.module.ts @@ -1,11 +1,11 @@ import { Module } from '@nestjs/common'; import { ScheduleModule } from '@nestjs/schedule'; -import { SentryIntegrationModule } from '@sentry/nestjs/setup'; +import { SentryModule } from '@sentry/nestjs/setup'; import { AppController } from './app.controller'; import { AppService } from './app.service'; @Module({ - imports: [SentryIntegrationModule.forRoot(), ScheduleModule.forRoot()], + imports: [SentryModule.forRoot(), ScheduleModule.forRoot()], controllers: [AppController], providers: [AppService], }) diff --git a/dev-packages/e2e-tests/test-applications/nestjs-distributed-tracing/src/trace-initiator.module.ts b/dev-packages/e2e-tests/test-applications/nestjs-distributed-tracing/src/trace-initiator.module.ts index e110f2883cd3..e7d27aa94f42 100644 --- a/dev-packages/e2e-tests/test-applications/nestjs-distributed-tracing/src/trace-initiator.module.ts +++ b/dev-packages/e2e-tests/test-applications/nestjs-distributed-tracing/src/trace-initiator.module.ts @@ -1,10 +1,10 @@ import { Module } from '@nestjs/common'; -import { SentryIntegrationModule } from '@sentry/nestjs/setup'; +import { SentryModule } from '@sentry/nestjs/setup'; import { TraceInitiatorController } from './trace-initiator.controller'; import { TraceInitiatorService } from './trace-initiator.service'; @Module({ - imports: [SentryIntegrationModule.forRoot()], + imports: [SentryModule.forRoot()], controllers: [TraceInitiatorController], providers: [TraceInitiatorService], }) diff --git a/dev-packages/e2e-tests/test-applications/nestjs-with-submodules/src/app.module.ts b/dev-packages/e2e-tests/test-applications/nestjs-with-submodules/src/app.module.ts index 8666aaf9a7a5..212b17a3556b 100644 --- a/dev-packages/e2e-tests/test-applications/nestjs-with-submodules/src/app.module.ts +++ b/dev-packages/e2e-tests/test-applications/nestjs-with-submodules/src/app.module.ts @@ -1,5 +1,5 @@ import { Module } from '@nestjs/common'; -import { SentryIntegrationModule } from '@sentry/nestjs/setup'; +import { SentryModule } from '@sentry/nestjs/setup'; import { AppController } from './app.controller'; import { AppService } from './app.service'; import { ExampleModuleGlobalFilterWrongRegistrationOrder } from './example-module-global-filter-wrong-registration-order/example.module'; @@ -9,7 +9,7 @@ import { ExampleModuleLocalFilter } from './example-module-local-filter/example. @Module({ imports: [ ExampleModuleGlobalFilterWrongRegistrationOrder, - SentryIntegrationModule.forRoot(), + SentryModule.forRoot(), ExampleModuleGlobalFilter, ExampleModuleLocalFilter, ], diff --git a/packages/nestjs/src/setup.ts b/packages/nestjs/src/setup.ts index d86fbc9a585f..965f8404288d 100644 --- a/packages/nestjs/src/setup.ts +++ b/packages/nestjs/src/setup.ts @@ -19,7 +19,6 @@ import { getIsolationScope, spanToJSON, } from '@sentry/core'; -import { DEBUG_BUILD } from '@sentry/node/build/types/debug-build'; import type { Span } from '@sentry/types'; import { logger } from '@sentry/utils'; import type { Observable } from 'rxjs'; @@ -37,8 +36,7 @@ class SentryTracingInterceptor implements NestInterceptor { */ public intercept(context: ExecutionContext, next: CallHandler): Observable { if (getIsolationScope() === getDefaultIsolationScope()) { - DEBUG_BUILD && - logger.warn('Isolation scope is still the default isolation scope, skipping setting transactionName.'); + logger.warn('Isolation scope is still the default isolation scope, skipping setting transactionName.'); return next.handle(); } @@ -82,7 +80,7 @@ export { SentryGlobalFilter }; /** * Service to set up Sentry performance tracing for Nest.js applications. */ -class SentryIntegrationService implements OnModuleInit { +class SentryService implements OnModuleInit { /** * Initializes the Sentry integration service and registers span attributes. */ @@ -98,21 +96,21 @@ class SentryIntegrationService implements OnModuleInit { } } } -Injectable()(SentryIntegrationService); -export { SentryIntegrationService }; +Injectable()(SentryService); +export { SentryService }; /** * Set up a root module that can be injected in nest applications. */ -class SentryIntegrationModule { +class SentryModule { /** * Configures the module as the root module in a Nest.js application. */ public static forRoot(): DynamicModule { return { - module: SentryIntegrationModule, + module: SentryModule, providers: [ - SentryIntegrationService, + SentryService, { provide: APP_FILTER, useClass: SentryGlobalFilter, @@ -122,14 +120,14 @@ class SentryIntegrationModule { useClass: SentryTracingInterceptor, }, ], - exports: [SentryIntegrationService], + exports: [SentryService], }; } } -Global()(SentryIntegrationModule); +Global()(SentryModule); Module({ providers: [ - SentryIntegrationService, + SentryService, { provide: APP_FILTER, useClass: SentryGlobalFilter, @@ -139,9 +137,9 @@ Module({ useClass: SentryTracingInterceptor, }, ], - exports: [SentryIntegrationService], -})(SentryIntegrationModule); -export { SentryIntegrationModule }; + exports: [SentryService], +})(SentryModule); +export { SentryModule }; function addNestSpanAttributes(span: Span): void { const attributes = spanToJSON(span).data || {}; diff --git a/packages/node/src/integrations/tracing/nest.ts b/packages/node/src/integrations/tracing/nest.ts index 032c544bc59d..ab6a66fdb895 100644 --- a/packages/node/src/integrations/tracing/nest.ts +++ b/packages/node/src/integrations/tracing/nest.ts @@ -11,7 +11,6 @@ import { } from '@sentry/core'; import type { IntegrationFn, Span } from '@sentry/types'; import { logger } from '@sentry/utils'; -import { DEBUG_BUILD } from '../../debug-build'; import { generateInstrumentOnce } from '../../otel/instrument'; interface MinimalNestJsExecutionContext { @@ -80,8 +79,7 @@ export function setupNestErrorHandler(app: MinimalNestJsApp, baseFilter: NestJsE app.useGlobalInterceptors({ intercept(context, next) { if (getIsolationScope() === getDefaultIsolationScope()) { - DEBUG_BUILD && - logger.warn('Isolation scope is still the default isolation scope, skipping setting transactionName.'); + logger.warn('Isolation scope is still the default isolation scope, skipping setting transactionName.'); return next.handle(); } From 7908cb2818d3ad877cb9f2439c63d9e86cf76bcc Mon Sep 17 00:00:00 2001 From: nicohrubec Date: Tue, 23 Jul 2024 14:24:14 +0200 Subject: [PATCH 57/60] . --- packages/nestjs/src/setup.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/nestjs/src/setup.ts b/packages/nestjs/src/setup.ts index 965f8404288d..b274b85ec43b 100644 --- a/packages/nestjs/src/setup.ts +++ b/packages/nestjs/src/setup.ts @@ -82,7 +82,7 @@ export { SentryGlobalFilter }; */ class SentryService implements OnModuleInit { /** - * Initializes the Sentry integration service and registers span attributes. + * Initializes the Sentry service and registers span attributes. */ public onModuleInit(): void { // Sadly, NestInstrumentation has no requestHook, so we need to add the attributes here From 82739569346c7eb768520322305bf0378fc55963 Mon Sep 17 00:00:00 2001 From: nicohrubec Date: Tue, 23 Jul 2024 15:02:51 +0200 Subject: [PATCH 58/60] Update supported nest versions --- packages/nestjs/package.json | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/nestjs/package.json b/packages/nestjs/package.json index c5dbaaa41991..dd1390c1f229 100644 --- a/packages/nestjs/package.json +++ b/packages/nestjs/package.json @@ -50,12 +50,12 @@ "@sentry/utils": "8.19.0" }, "devDependencies": { - "@nestjs/core": "^10.3.10", - "@nestjs/common": "^10.3.10" + "@nestjs/core": "^8.0.0 || ^9.0.0 || ^10.0.0", + "@nestjs/common": "^8.0.0 || ^9.0.0 || ^10.0.0" }, "peerDependencies": { - "@nestjs/core": "^10.3.10", - "@nestjs/common": "^10.3.10" + "@nestjs/core": "^8.0.0 || ^9.0.0 || ^10.0.0", + "@nestjs/common": "^8.0.0 || ^9.0.0 || ^10.0.0" }, "scripts": { "build": "run-p build:transpile build:types", From 94663084f92c8824f137e005cc45b61bdd5b693e Mon Sep 17 00:00:00 2001 From: nicohrubec Date: Tue, 23 Jul 2024 15:44:37 +0200 Subject: [PATCH 59/60] Update README --- packages/nestjs/README.md | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/packages/nestjs/README.md b/packages/nestjs/README.md index 2fc282cb34f1..6c5d1ef897fd 100644 --- a/packages/nestjs/README.md +++ b/packages/nestjs/README.md @@ -27,9 +27,6 @@ yarn add @sentry/nestjs Add a instrument.ts file: ```typescript -// CJS Syntax -const Sentry = require('@sentry/nestjs'); -// ESM Syntax import * as Sentry from '@sentry/nestjs'; Sentry.init({ @@ -58,7 +55,7 @@ async function bootstrap() { bootstrap(); ``` -Then you can add the SentryModule as a root module: +Then you can add the `SentryModule` as a root module: ```typescript import { Module } from '@nestjs/common'; @@ -77,7 +74,7 @@ import { AppService } from './app.service'; export class AppModule {} ``` -The SentryModule needs registered before any module that should be instrumented by Sentry. +The `SentryModule` needs to be registered before any module that should be instrumented by Sentry. ## SentryTraced From 9524ac9fcf3770d89217d8bcbdc2022e2b328d09 Mon Sep 17 00:00:00 2001 From: nicohrubec Date: Tue, 23 Jul 2024 15:46:54 +0200 Subject: [PATCH 60/60] Update README --- packages/nestjs/README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/nestjs/README.md b/packages/nestjs/README.md index 6c5d1ef897fd..9e0192551afc 100644 --- a/packages/nestjs/README.md +++ b/packages/nestjs/README.md @@ -24,7 +24,7 @@ yarn add @sentry/nestjs ## Usage -Add a instrument.ts file: +Add an `instrument.ts` file: ```typescript import * as Sentry from '@sentry/nestjs'; @@ -35,7 +35,7 @@ Sentry.init({ }); ``` -You need to require or import the instrument.js file before requiring any other modules in your application. This is +You need to require or import the `instrument.ts` file before requiring any other modules in your application. This is necessary to ensure that Sentry can automatically instrument all modules in your application: ```typescript