Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 2 additions & 8 deletions .github/workflows/canary.yml
Original file line number Diff line number Diff line change
Expand Up @@ -111,15 +111,9 @@ jobs:
- test-application: 'tanstackstart-react'
build-command: 'test:build-latest'
label: 'tanstackstart-react (latest)'
- test-application: 'nestjs-11'
- test-application: 'nestjs-12'
build-command: 'test:build-latest'
label: 'nestjs-11 (latest)'
- test-application: 'nestjs-websockets'
build-command: 'test:build-latest'
label: 'nestjs-websockets (latest)'
- test-application: 'nestjs-microservices'
build-command: 'test:build-latest'
label: 'nestjs-microservices (latest)'
label: 'nestjs-12 (latest)'

steps:
- name: Check out current commit
Expand Down
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,10 @@

Across all AI integrations (OpenAI, Anthropic, Google GenAI, LangChain, and LangGraph), the SDK no longer sends an event to Sentry for errors that the AI framework propagates to your code. Previously the instrumentation reported these as unhandled (`handled: false`) before your own error handling ran, so an error your code caught still showed up in Sentry as an unhandled crash. The span is still marked as errored and the error still propagates, so reporting is left to your application: if your code does not handle the error, it reaches Sentry's global error handlers and is captured as unhandled, just like any other uncaught error. Errors that a provider surfaces as data on an otherwise successful response (such as Anthropic error-shaped responses or Google GenAI blocked content) are still captured, since your code never sees them propagate.

- **feat(v10/nestjs): Add support for NestJS v12 ([#23729](https://github.com/getsentry/sentry-javascript/pull/23729))**

`@sentry/nestjs` is now compatible with NestJS v12 applications.

Work in this release was contributed by @ryanrho-mercor, @lux-in-tenebris-lucet, and @suhailopensource. Thank you for your contributions!

## 10.71.0
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@
"clean": "npx rimraf node_modules pnpm-lock.yaml",
"test": "playwright test",
"test:build": "pnpm install",
"test:build-latest": "pnpm install && pnpm add @nestjs/common@latest @nestjs/core@latest @nestjs/platform-express@latest @nestjs/microservices@latest && pnpm add -D @nestjs/cli@latest @nestjs/testing@latest && pnpm build",
"test:assert": "pnpm test"
},
"dependencies": {
Expand Down
56 changes: 56 additions & 0 deletions dev-packages/e2e-tests/test-applications/nestjs-12/.gitignore
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"$schema": "https://json.schemastore.org/nest-cli",
"collection": "@nestjs/schematics",
"sourceRoot": "src",
"compilerOptions": {
"deleteOutDir": true
}
}
54 changes: 54 additions & 0 deletions dev-packages/e2e-tests/test-applications/nestjs-12/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
{
"name": "nestjs-12",
"version": "0.0.1",
"private": true,
"type": "module",
"scripts": {
"build": "nest build",
"start": "node --import ./dist/instrument.js dist/main.js",
"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 && pnpm build",
"test:build-latest": "pnpm install && pnpm add @nestjs/common@latest @nestjs/core@latest @nestjs/platform-express@latest @nestjs/microservices@latest && pnpm add -D @nestjs/cli@latest @nestjs/testing@latest && pnpm build",
"test:assert": "pnpm test"
},
"dependencies": {
"@nestjs/common": "^12.0.0",
"@nestjs/core": "^12.0.0",
"@nestjs/microservices": "^12.0.0",
"@nestjs/schedule": "^12.0.0",
"@nestjs/platform-express": "^12.0.0",
"@sentry/nestjs": "file:../../packed/sentry-nestjs-packed.tgz",
"reflect-metadata": "^0.2.0",
"rxjs": "^7.8.1"
},
"devDependencies": {
"@playwright/test": "~1.56.0",
"@sentry-internal/test-utils": "link:../../../test-utils",
"@nestjs/cli": "^12.0.0",
"@nestjs/schematics": "^12.0.0",
"@nestjs/testing": "^12.0.0",
"@types/express": "^5.0.0",
"@types/node": "^22.0.0",
"@types/supertest": "^6.0.0",
"@typescript-eslint/eslint-plugin": "^6.0.0",
"@typescript-eslint/parser": "^6.0.0",
"eslint": "^8.42.0",
"source-map-support": "^0.5.21",
"supertest": "^6.3.3",
"ts-loader": "^9.4.3",
"tsconfig-paths": "^4.2.0",
"typescript": "~5.9.0"
},
"pnpm": {
"overrides": {
"minimatch": "10.0.1"
}
},
"volta": {
"extends": "../../package.json"
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { getPlaywrightConfig } from '@sentry-internal/test-utils';

const config = getPlaywrightConfig({
startCommand: `pnpm start`,
});

export default config;
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
import { Controller, Get, Param, ParseIntPipe, UseFilters, UseGuards, UseInterceptors } from '@nestjs/common';
import { flush } from '@sentry/nestjs';
import { AppService } from './app.service.js';
import { AsyncInterceptor } from './async-example.interceptor.js';
import { ExampleInterceptor1 } from './example-1.interceptor.js';
import { ExampleInterceptor2 } from './example-2.interceptor.js';
import { ExampleExceptionGlobalFilter } from './example-global-filter.exception.js';
import { ExampleExceptionLocalFilter } from './example-local-filter.exception.js';
import { ExampleLocalFilter } from './example-local.filter.js';
import { ExampleGuard } from './example.guard.js';

@Controller()
@UseFilters(ExampleLocalFilter)
export class AppController {
constructor(private readonly appService: AppService) {}

@Get('test-transaction')
testTransaction() {
return this.appService.testTransaction();
}

@Get('test-middleware-instrumentation')
testMiddlewareInstrumentation() {
return this.appService.testSpan();
}

@Get('test-guard-instrumentation')
@UseGuards(ExampleGuard)
testGuardInstrumentation() {
return {};
}

@Get('test-interceptor-instrumentation')
@UseInterceptors(ExampleInterceptor1, ExampleInterceptor2)
testInterceptorInstrumentation() {
return this.appService.testSpan();
}

@Get('test-async-interceptor-instrumentation')
@UseInterceptors(AsyncInterceptor)
testAsyncInterceptorInstrumentation() {
return this.appService.testSpan();
}

@Get('test-pipe-instrumentation/:id')
testPipeInstrumentation(@Param('id', ParseIntPipe) id: number) {
return { value: id };
}

@Get('test-exception/:id')
async testException(@Param('id') id: string) {
return this.appService.testException(id);
}

@Get('test-expected-400-exception/:id')
async testExpected400Exception(@Param('id') id: string) {
return this.appService.testExpected400Exception(id);
}

@Get('test-expected-500-exception/:id')
async testExpected500Exception(@Param('id') id: string) {
return this.appService.testExpected500Exception(id);
}

@Get('test-expected-rpc-exception/:id')
async testExpectedRpcException(@Param('id') id: string) {
return this.appService.testExpectedRpcException(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/:job')
async killTestCron(@Param('job') job: string) {
this.appService.killTestCron(job);
}

@Get('flush')
async flush() {
await flush();
}

@Get('example-exception-global-filter')
async exampleExceptionGlobalFilter() {
throw new ExampleExceptionGlobalFilter();
}

@Get('example-exception-local-filter')
async exampleExceptionLocalFilter() {
throw new ExampleExceptionLocalFilter();
}

@Get('test-service-use')
testServiceWithUseMethod() {
return this.appService.use();
}

@Get('test-service-transform')
testServiceWithTransform() {
return this.appService.transform();
}

@Get('test-service-intercept')
testServiceWithIntercept() {
return this.appService.intercept();
}

@Get('test-service-canActivate')
testServiceWithCanActivate() {
return this.appService.canActivate();
}

@Get('test-function-name')
testFunctionName() {
return this.appService.getFunctionName();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import { MiddlewareConsumer, Module } from '@nestjs/common';
import { APP_FILTER } from '@nestjs/core';
import { ScheduleModule } from '@nestjs/schedule';
import { SentryGlobalFilter, SentryModule } from '@sentry/nestjs/setup';
import { AppController } from './app.controller.js';
import { AppService } from './app.service.js';
import { ExampleGlobalFilter } from './example-global.filter.js';
import { ExampleMiddleware } from './example.middleware.js';

@Module({
imports: [SentryModule.forRoot(), ScheduleModule.forRoot()],
controllers: [AppController],
providers: [
AppService,
{
provide: APP_FILTER,
useClass: SentryGlobalFilter,
},
{
provide: APP_FILTER,
useClass: ExampleGlobalFilter,
},
],
})
export class AppModule {
configure(consumer: MiddlewareConsumer): void {
consumer.apply(ExampleMiddleware).forRoutes('test-middleware-instrumentation');
}
}
Loading
Loading