Skip to content

Commit b577079

Browse files
authored
feat(nestjs): Change nest sdk setup (#12920)
- Adds a new nest root module that can be used to setup the Nest SDK as a replacement for the existing setup (with a function). Instead of calling `setupNestErrorHandler` in the main.ts file, users can now add `SentryModule.forRoot()` (feedback about the name is definitely welcome) as an import in their main app module. This approach is much more native to nest than what we used so far. This root module is introduced in the setup.ts file. - This root module is exported with a submodule export `@sentry/nestjs/setup`, because the SDK now depends on nestjs directly and without this the nest instrumentation does not work anymore, since nest gets imported before Sentry.init gets called, which disables the otel nest instrumentation. - Judging from the e2e tests it seems that this new approach also resolves some issues the previous implementation had, specifically [this issue](#12351) seems to be resolved. The e2e test that was in place, just documented the current (wrong) behavior. So I updated the test to reflect the new (correct) behavior. - I updated all the test applications to use the new approach but kept a copy of the nestjs-basic and nestjs-distributed-tracing with the old setup (now named node-nestjs-basic and node-nestjs-distributed-tracing respectively) so we can still verify that the old setup (which a lot of people use) still keeps working going forward. - Updated/New tests in this PR: - Sends unexpected exception to Sentry if thrown in Submodule - Does not send expected exception to Sentry if thrown in Submodule and caught by a global exception filter - Does not send expected exception to Sentry if thrown in Submodule and caught by a local exception filter - Sends expected exception to Sentry if thrown from submodule registered before Sentry - To accomodate the new tests I added several submodules in the nestjs-with-submodules test-application. These are overall similarly but have important distinctions: - example-module-local-filter: Submodule with a local filter registered using `@UseFilters` on the controller. - example-module-global-filter: Submodule with a global filter registered using APP_FILTER in the submodule definition. - example-module-global-filter-wrong-registration-order: Also has a global filter set with APP_FILTER, but is registered in the root module as first submodule, even before the SentryIntegration is initialized. This case does not work properly in the new setup (Sentry should be set first), so this module is used for tests documenting this behavior. - Also set "moduleResolution": "Node16" in the nestjs-basic sample app to ensure our submodule-export workaround works in both, default and sub-path-export-compatible TS configs as was suggested [here](#12948 (comment)).
1 parent f867cc0 commit b577079

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

67 files changed

+1953
-59
lines changed

.github/workflows/build.yml

+2
Original file line numberDiff line numberDiff line change
@@ -1010,6 +1010,8 @@ jobs:
10101010
'generic-ts3.8',
10111011
'node-fastify',
10121012
'node-hapi',
1013+
'node-nestjs-basic',
1014+
'node-nestjs-distributed-tracing',
10131015
'nestjs-basic',
10141016
'nestjs-distributed-tracing',
10151017
'nestjs-with-submodules',

dev-packages/e2e-tests/test-applications/nestjs-basic/src/app.module.ts

+2-1
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
11
import { Module } from '@nestjs/common';
22
import { ScheduleModule } from '@nestjs/schedule';
3+
import { SentryModule } from '@sentry/nestjs/setup';
34
import { AppController } from './app.controller';
45
import { AppService } from './app.service';
56

67
@Module({
7-
imports: [ScheduleModule.forRoot()],
8+
imports: [SentryModule.forRoot(), ScheduleModule.forRoot()],
89
controllers: [AppController],
910
providers: [AppService],
1011
})

dev-packages/e2e-tests/test-applications/nestjs-basic/src/main.ts

+1-6
Original file line numberDiff line numberDiff line change
@@ -2,18 +2,13 @@
22
import './instrument';
33

44
// Import other modules
5-
import { BaseExceptionFilter, HttpAdapterHost, NestFactory } from '@nestjs/core';
6-
import * as Sentry from '@sentry/nestjs';
5+
import { NestFactory } from '@nestjs/core';
76
import { AppModule } from './app.module';
87

98
const PORT = 3030;
109

1110
async function bootstrap() {
1211
const app = await NestFactory.create(AppModule);
13-
14-
const { httpAdapter } = app.get(HttpAdapterHost);
15-
Sentry.setupNestErrorHandler(app, new BaseExceptionFilter(httpAdapter));
16-
1712
await app.listen(PORT);
1813
}
1914

dev-packages/e2e-tests/test-applications/nestjs-basic/tsconfig.json

+2-1
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
"noImplicitAny": false,
1717
"strictBindCallApply": false,
1818
"forceConsistentCasingInFileNames": false,
19-
"noFallthroughCasesInSwitch": false
19+
"noFallthroughCasesInSwitch": false,
20+
"moduleResolution": "Node16"
2021
}
2122
}

dev-packages/e2e-tests/test-applications/nestjs-distributed-tracing/src/main.ts

+1-6
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,7 @@
22
import './instrument';
33

44
// Import other modules
5-
import { BaseExceptionFilter, HttpAdapterHost, NestFactory } from '@nestjs/core';
6-
import * as Sentry from '@sentry/nestjs';
5+
import { NestFactory } from '@nestjs/core';
76
import { TraceInitiatorModule } from './trace-initiator.module';
87
import { TraceReceiverModule } from './trace-receiver.module';
98

@@ -12,10 +11,6 @@ const TRACE_RECEIVER_PORT = 3040;
1211

1312
async function bootstrap() {
1413
const trace_initiator_app = await NestFactory.create(TraceInitiatorModule);
15-
16-
const { httpAdapter } = trace_initiator_app.get(HttpAdapterHost);
17-
Sentry.setupNestErrorHandler(trace_initiator_app, new BaseExceptionFilter(httpAdapter));
18-
1914
await trace_initiator_app.listen(TRACE_INITIATOR_PORT);
2015

2116
const trace_receiver_app = await NestFactory.create(TraceReceiverModule);

dev-packages/e2e-tests/test-applications/nestjs-distributed-tracing/src/trace-initiator.module.ts

+2-1
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
import { Module } from '@nestjs/common';
2+
import { SentryModule } from '@sentry/nestjs/setup';
23
import { TraceInitiatorController } from './trace-initiator.controller';
34
import { TraceInitiatorService } from './trace-initiator.service';
45

56
@Module({
6-
imports: [],
7+
imports: [SentryModule.forRoot()],
78
controllers: [TraceInitiatorController],
89
providers: [TraceInitiatorService],
910
})

dev-packages/e2e-tests/test-applications/nestjs-with-submodules/src/app.module.ts

+10-2
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,18 @@
11
import { Module } from '@nestjs/common';
2+
import { SentryModule } from '@sentry/nestjs/setup';
23
import { AppController } from './app.controller';
34
import { AppService } from './app.service';
4-
import { ExampleModule } from './example-module/example.module';
5+
import { ExampleModuleGlobalFilterWrongRegistrationOrder } from './example-module-global-filter-wrong-registration-order/example.module';
6+
import { ExampleModuleGlobalFilter } from './example-module-global-filter/example.module';
7+
import { ExampleModuleLocalFilter } from './example-module-local-filter/example.module';
58

69
@Module({
7-
imports: [ExampleModule],
10+
imports: [
11+
ExampleModuleGlobalFilterWrongRegistrationOrder,
12+
SentryModule.forRoot(),
13+
ExampleModuleGlobalFilter,
14+
ExampleModuleLocalFilter,
15+
],
816
controllers: [AppController],
917
providers: [AppService],
1018
})
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import { Controller, Get } from '@nestjs/common';
2+
import { ExampleExceptionWrongRegistrationOrder } from './example.exception';
3+
4+
@Controller('example-module-wrong-order')
5+
export class ExampleController {
6+
constructor() {}
7+
8+
@Get('/expected-exception')
9+
getCaughtException(): string {
10+
throw new ExampleExceptionWrongRegistrationOrder();
11+
}
12+
13+
@Get('/unexpected-exception')
14+
getUncaughtException(): string {
15+
throw new Error(`This is an uncaught exception!`);
16+
}
17+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
export class ExampleExceptionWrongRegistrationOrder extends Error {
2+
constructor() {
3+
super('Something went wrong in the example module!');
4+
}
5+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import { ArgumentsHost, BadRequestException, Catch } from '@nestjs/common';
2+
import { BaseExceptionFilter } from '@nestjs/core';
3+
import { ExampleExceptionWrongRegistrationOrder } from './example.exception';
4+
5+
@Catch(ExampleExceptionWrongRegistrationOrder)
6+
export class ExampleExceptionFilterWrongRegistrationOrder extends BaseExceptionFilter {
7+
catch(exception: unknown, host: ArgumentsHost) {
8+
if (exception instanceof ExampleExceptionWrongRegistrationOrder) {
9+
return super.catch(new BadRequestException(exception.message), host);
10+
}
11+
return super.catch(exception, host);
12+
}
13+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
import { Module } from '@nestjs/common';
2+
import { APP_FILTER } from '@nestjs/core';
3+
import { ExampleController } from './example.controller';
4+
import { ExampleExceptionFilterWrongRegistrationOrder } from './example.filter';
5+
6+
@Module({
7+
imports: [],
8+
controllers: [ExampleController],
9+
providers: [
10+
{
11+
provide: APP_FILTER,
12+
useClass: ExampleExceptionFilterWrongRegistrationOrder,
13+
},
14+
],
15+
})
16+
export class ExampleModuleGlobalFilterWrongRegistrationOrder {}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
import { Controller, Get } from '@nestjs/common';
2+
import * as Sentry from '@sentry/nestjs';
3+
import { ExampleException } from './example.exception';
4+
5+
@Controller('example-module')
6+
export class ExampleController {
7+
constructor() {}
8+
9+
@Get('/expected-exception')
10+
getCaughtException(): string {
11+
throw new ExampleException();
12+
}
13+
14+
@Get('/unexpected-exception')
15+
getUncaughtException(): string {
16+
throw new Error(`This is an uncaught exception!`);
17+
}
18+
19+
@Get('/transaction')
20+
testTransaction() {
21+
Sentry.startSpan({ name: 'test-span' }, () => {
22+
Sentry.startSpan({ name: 'child-span' }, () => {});
23+
});
24+
}
25+
}

dev-packages/e2e-tests/test-applications/nestjs-with-submodules/src/example-module/example.module.ts renamed to dev-packages/e2e-tests/test-applications/nestjs-with-submodules/src/example-module-global-filter/example.module.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -13,4 +13,4 @@ import { ExampleExceptionFilter } from './example.filter';
1313
},
1414
],
1515
})
16-
export class ExampleModule {}
16+
export class ExampleModuleGlobalFilter {}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
import { Controller, Get, UseFilters } from '@nestjs/common';
2+
import { LocalExampleException } from './example.exception';
3+
import { LocalExampleExceptionFilter } from './example.filter';
4+
5+
@Controller('example-module-local-filter')
6+
@UseFilters(LocalExampleExceptionFilter)
7+
export class ExampleControllerLocalFilter {
8+
constructor() {}
9+
10+
@Get('/expected-exception')
11+
getCaughtException() {
12+
throw new LocalExampleException();
13+
}
14+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
export class LocalExampleException extends Error {
2+
constructor() {
3+
super('Something went wrong in the example module with local filter!');
4+
}
5+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import { ArgumentsHost, BadRequestException, Catch } from '@nestjs/common';
2+
import { BaseExceptionFilter } from '@nestjs/core';
3+
import { LocalExampleException } from './example.exception';
4+
5+
@Catch(LocalExampleException)
6+
export class LocalExampleExceptionFilter extends BaseExceptionFilter {
7+
catch(exception: unknown, host: ArgumentsHost) {
8+
if (exception instanceof LocalExampleException) {
9+
return super.catch(new BadRequestException(exception.message), host);
10+
}
11+
return super.catch(exception, host);
12+
}
13+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
import { Module } from '@nestjs/common';
2+
import { ExampleControllerLocalFilter } from './example.controller';
3+
4+
@Module({
5+
imports: [],
6+
controllers: [ExampleControllerLocalFilter],
7+
providers: [],
8+
})
9+
export class ExampleModuleLocalFilter {}

dev-packages/e2e-tests/test-applications/nestjs-with-submodules/src/example-module/example.controller.ts

-12
This file was deleted.

dev-packages/e2e-tests/test-applications/nestjs-with-submodules/src/main.ts

+1-6
Original file line numberDiff line numberDiff line change
@@ -2,18 +2,13 @@
22
import './instrument';
33

44
// Import other modules
5-
import { BaseExceptionFilter, HttpAdapterHost, NestFactory } from '@nestjs/core';
6-
import * as Sentry from '@sentry/nestjs';
5+
import { NestFactory } from '@nestjs/core';
76
import { AppModule } from './app.module';
87

98
const PORT = 3030;
109

1110
async function bootstrap() {
1211
const app = await NestFactory.create(AppModule);
13-
14-
const { httpAdapter } = app.get(HttpAdapterHost);
15-
Sentry.setupNestErrorHandler(app, new BaseExceptionFilter(httpAdapter));
16-
1712
await app.listen(PORT);
1813
}
1914

0 commit comments

Comments
 (0)