Skip to content

nestjs: add exception filter and exception handler #293

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 5 commits into from
Oct 11, 2024
Merged
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
134 changes: 120 additions & 14 deletions packages/nestjs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ after which you can explore the rich set of Backtrace features.
- [Integrate the SDK](#integrate-the-sdk)
- [Upload source maps](#upload-source-maps)
- [Add a Backtrace error interceptor](#add-a-backtrace-error-interceptor)
- [Add a Backtrace exception filter](#add-a-backtrace-exception-filter)
- [Use the Backtrace exception handler in your code](#use-the-backtrace-exception-handler-in-your-code)
1. [Error Reporting Features](#error-reporting-features)
- [Attributes](#attributes)
- [File Attachments](#file-attachments)
Expand Down Expand Up @@ -75,11 +77,40 @@ class AppController {
@Post()
public endpoint() {
// Manually send an error
this._cclient.send(new Error('Something broke!'));
this._client.send(new Error('Something broke!'));
}
}
```

#### Configuring the module

By default, the exception handler will exclude:

- all `HttpException` errors that have `status < 500`.

To include or exclude specific error types, pass options to `BacktraceModule`:

```ts
@Module({
imports: [
BacktraceModule.register({
options: {
includeExceptionTypes: [MyException],
excludeExceptionTypes: (error) => error instanceof HttpException && error.getStatus() < 500,
},
}),
],
controllers: [AppController],
})
class AppModule {}
```

As shown in the example above, `includeExceptionTypes` and `excludeExceptionTypes` accept either an array of error
types, or a function that can return a `boolean`. The array types will match using `instanceof`. The function will have
the thrown error passed as the first parameter.

**Note:** include is tested before exclude, so if exception type is both included and excluded, it will be included.

### Upload source maps

Client-side error reports are based on minified code. Upload source maps and source code to resolve minified code to
Expand Down Expand Up @@ -107,7 +138,7 @@ import { BacktraceModule, BacktraceInterceptor } from '@backtrace/nestjs';
providers: [
{
provide: APP_INTERCEPTOR,
useValue: new BacktraceInterceptor(),
useExisting: BacktraceInterceptor,
},
],
controllers: [CatsController],
Expand All @@ -122,37 +153,112 @@ const app = await NestFactory.create(AppModule);
app.useGlobalInterceptors(new BacktraceInterceptor());
```

To use it on a controller, use `UseInterceptors` decorator:
To use it on a specific controller, use `UseInterceptors` decorator:

```ts
@UseInterceptors(BacktraceInterceptor)
export class CatsController {}
```

or

```ts
@UseInterceptors(new BacktraceInterceptor())
@UseInterceptors(new BacktraceInterceptor({ ... }))
export class CatsController {}
```

For more information, consult [NestJS documentation](https://docs.nestjs.com/interceptors#binding-interceptors).

#### Configuring the interceptor

By default, the interceptor will include:
By default, the interceptor will use [options from `BacktraceModule`](#configuring-the-module). You can pass the options also directly to the interceptor:

- all errors that are an instance of `Error`,
```ts
new BacktraceInterceptor({
includeExceptionTypes: [MyException],
excludeExceptionTypes: (error) => error instanceof HttpException && error.getStatus() < 500,
});
```

and exclude:
### Add a Backtrace exception filter

- all `HttpException` errors that have `status < 500`.
The interceptor does not capture all exceptions. For example, exceptions thrown by guards will not be captured by the interceptor. You can use the `BacktraceExceptionFilter` exception filter class.
The filter will capture everything that the interceptor would capture.

To include or exclude specific error types, pass options to `BacktraceInterceptor`:
To add the filter globally, you can register it as `APP_FILTER`:

```ts
new BacktraceInterceptor({
includeExceptionTypes: [Error],
import { Module } from '@nestjs/common';
import { APP_FILTER } from '@nestjs/core';
import { BacktraceModule, BacktraceExceptionFilter } from '@backtrace/nestjs';

@Module({
imports: [BacktraceModule],
providers: [
{
provide: APP_INTERCEPTOR,
useExisting: BacktraceExceptionFilter,
},
],
controllers: [CatsController],
})
export class AppModule {}
```

Or, use `app.useGlobalFilters`:

```ts
const app = await NestFactory.create(AppModule);

// Note that you need to pass httpAdapter to BacktraceExceptionFilter as a second argument
const { httpAdapter } = app.get(HttpAdapterHost);
app.useGlobalInterceptors(new BacktraceExceptionFilter({}, httpAdapter));
```

To use it on a specific controller, use `UseFilters` decorator:

```ts
@UseFilters(BacktraceExceptionFilter)
export class CatsController {}
```

For more information, consult [NestJS documentation](https://docs.nestjs.com/exception-filters#binding-filters).

#### Configuring the filter

By default, the filter will use [options from `BacktraceModule`](#configuring-the-module). You can pass the options also directly to the filter:

```ts
new BacktraceExceptionFilter({
includeExceptionTypes: [MyException],
excludeExceptionTypes: (error) => error instanceof HttpException && error.getStatus() < 500,
});
```

As shown in the example above, `includeExceptionTypes` and `excludeExceptionTypes` accept either an array of error
types, or a function that can return a `boolean`. The array types will match using `instanceof`. The function will have
the thrown error passed as the first parameter.
### Use the Backtrace exception handler in your code

If you want to leverage the exception filtering and handling in your custom logic, e.g. exception filters, you can inject `BacktraceExceptionHandler` to your logic:

```ts
class MyExceptionFilter implements ExceptionFilter {
constructor(private readonly handler: BacktraceExceptionHandler) {}

catch(exception: unknown, host: ArgumentsHost) {
const wasExceptionSent = this.handler.handleException(exception, host);
}
}
```

#### Configuring the handler

By default, the handler will use [options from `BacktraceModule`](#configuring-the-module). You can pass the options also directly to the handler:

```ts
new BacktraceExceptionHandler({
includeExceptionTypes: [MyException],
excludeExceptionTypes: (error) => error instanceof HttpException && error.getStatus() < 500,
});
```

## Error Reporting Features

Expand Down
57 changes: 57 additions & 0 deletions packages/nestjs/src/backtrace.filter.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import { BacktraceClient } from '@backtrace/node';
import { ArgumentsHost, HttpServer, Inject, Injectable, Optional } from '@nestjs/common';
import { BaseExceptionFilter } from '@nestjs/core';
import { BacktraceExceptionHandler, BacktraceExceptionHandlerOptions } from './backtrace.handler.js';

export type BacktraceExceptionFilterOptions = BacktraceExceptionHandlerOptions<ArgumentsHost>;

@Injectable()
export class BacktraceExceptionFilter extends BaseExceptionFilter {
private readonly _handler: BacktraceExceptionHandler;

/**
* Creates a filter with the global client instance.
*
* The instance will be resolved while catching the exception.
* If the instance is not available, an error will be thrown.
*/
constructor(options?: BacktraceExceptionFilterOptions, applicationRef?: HttpServer);
/**
* Creates a filter with the provided client instance.
*/
constructor(
options: BacktraceExceptionFilterOptions | undefined,
client: BacktraceClient,
applicationRef?: HttpServer,
);
constructor(handler: BacktraceExceptionHandler, applicationRef?: HttpServer);
constructor(
@Inject(BacktraceExceptionHandler)
handlerOrOptions: BacktraceExceptionFilterOptions | BacktraceExceptionHandler | undefined,
@Inject(BacktraceClient) @Optional() clientOrApplicationRef?: BacktraceClient | HttpServer,
@Optional() maybeApplicationRef?: HttpServer,
) {
const applicationRef =
(maybeApplicationRef ?? clientOrApplicationRef instanceof BacktraceClient)
? maybeApplicationRef
: clientOrApplicationRef;

super(applicationRef);
if (handlerOrOptions instanceof BacktraceExceptionHandler) {
this._handler = handlerOrOptions;
return;
}

const options = handlerOrOptions;
if (clientOrApplicationRef instanceof BacktraceClient) {
this._handler = new BacktraceExceptionHandler(options, clientOrApplicationRef);
} else {
this._handler = new BacktraceExceptionHandler(options);
}
}

public catch(exception: unknown, host: ArgumentsHost): void {
this._handler.handleException(exception, host);
super.catch(exception, host);
}
}
Loading