Sentry Integration With NestJS

Abiral Sthapit
4 min readJun 7, 2021

--

Sentry is Open-source error tracking that helps developers to monitor, fix crashes in real time. It has support for many languages, for this project we need node support since nestjs is a node framework:
https://docs.sentry.io/platforms/node/

Installing Sentry

yarn add @sentry/node @sentry/tracing

Configuring in our NestJS App

First login to the sentry dashboard and get the sentry dsn, then follow the steps below

.env

APP_ENV=dev
APP_NAME="MY APP NAME"
APP_PORT=3000
# Add Sentry dsn here
SENTRY_DSN=https://examplePublicKey@o0.ingest.sentry.io/0

src/app/config.ts

import { registerAs } from '@nestjs/config';export default registerAs('app', () => ({
env: process.env.APP_ENV,
name: process.env.APP_NAME,
port: process.env.APP_PORT,
sentryDsn: process.env.SENTRY_DSN
}));

src/app/app-config.service.ts

import { Injectable } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
@Injectable()
export class AppConfigService {
constructor(private configService: ConfigService) {}
get env(): string {
return this.configService.get<string>('app.env');
}
get name(): string {
return this.configService.get<string>('app.name');
}
get port(): number {
return this.configService.get<number>('app.port');
}
get sentryDsn(): string {
return this.configService.get<string>('app.sentryDsn');
}
}

src/app-config.module.ts

import * as Joi from '@hapi/joi';
import { Module } from '@nestjs/common';
import { ConfigModule, ConfigService } from '@nestjs/config';
import config from './config';
import { AppConfigService } from './app-config.service';
@Module({
imports: [
ConfigModule.forRoot({
load: [config],
validationSchema: Joi.object({
APP_ENV: Joi.string()
.valid('dev', 'stage', 'test', 'prod')
.default('dev'),
APP_NAME: Joi.string().default('MY APP'),
APP_PORT: Joi.number().default('3000'),
SENTRY_DSN: Joi.string().uri().default(''),
}),
}),
],
providers: [ConfigService, AppConfigService],
exports: [ConfigService, AppConfigService],
})
export class AppConfigModule {}

Initializing the Sentry in the main

src/main.ts

import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
import { AppConfigService } from './config/app/app-config.service';
import * as Sentry from '@sentry/node';
async function bootstrap() {
const app = await NestFactory.create(AppModule);
// Get app config for cors settings and starting the app.
const appConfig: AppConfigService = app.get('AppConfigService');
//Sentry Config
Sentry.init({
dsn: appConfig.sentryDsn,
tracesSampleRate: 1.0,
});
await app.listen(appConfig.port);
}
bootstrap();

Now we need to Configure Sentry in a way that it can catch errors and exception and send it to the sentry dashboard. Also we need to enable it only in the Prod env (only see if anything breaks unknowingly in prod), as we do not want to be overwhelm with error that occurs in the dev phase, which we eventually fix.

To do so, Nest already provide us with interceptors that we can use on global scope, module scope, controller scope and service scope. so lets create our 2nd folder in config called interceptors and add the sentry interceptor there.
https://docs.nestjs.com/interceptors

src/common/interceptors/sentry.interceptor.ts

import { CallHandler, ExecutionContext, Injectable, NestInterceptor } from "@nestjs/common";
import { Observable, throwError } from "rxjs";
import { catchError } from 'rxjs/operators';
import * as Sentry from '@sentry/node';
const enableSentry = err =>{
Sentry.captureException(err)
return throwError(err)
}
@Injectable()
export class SentryInterceptor implements NestInterceptor {
intercept(context: ExecutionContext, next: CallHandler<any>): Observable<any> | Promise<Observable<any>> {
return next.handle().pipe(
catchError(enableSentry)
)
}
}

src/main.ts

import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
import { AppConfigService } from './config/app/app-config.service';
import * as Sentry from '@sentry/node';
import { SentryInterceptor } from './common/interceptors/sentry.interceptor';
async function bootstrap() {
const app = await NestFactory.create(AppModule);
// Get app config for cors settings and starting the app.
const appConfig: AppConfigService = app.get('AppConfigService');
//Sentry Config
Sentry.init({
dsn: appConfig.sentryDsn,
tracesSampleRate: 1.0,
});
app.useGlobalInterceptors(new SentryInterceptor());
await app.listen(appConfig.port);
}
bootstrap();

You can also specify the Sentry for specific module only instead of global scope

import { UseInterceptors } from '@nestjs/common';
import { SentryInterceptor } from 'src/common/interceptors/sentry.interceptor';
@UseInterceptors(SentryInterceptor)
// Or
@Module({
imports: [AppConfigModule],
controllers: [AppController],
providers: [
{
provide: APP_INTERCEPTOR,
useValue: new SentryInterceptor(),
},
{
provide: APP_FILTER,
useValue: new HttpExceptionFilter(),
},
AppService,
],
})
export class AppModule {}

But using it in global scope is a lot easier, rather than specifying it in every controller. So lets use of Exception Filter in the Global Scope as well and keeping the app module as clean & relatable as possible.

We can leave here but we want to filter the specific exception and error types and leave the rest of the error, also we do not want to send errors from dev or local setup so let us see how we can do it.

src/main.ts

// appConfig should be reterived first
app.useGlobalInterceptors(new SentryInterceptor(appConfig.env));

src/common/interceptors/sentry.interceptor.ts

import {
CallHandler,
ExecutionContext,
Injectable,
NestInterceptor,
} from '@nestjs/common';
import { Observable, throwError } from 'rxjs';
import { catchError } from 'rxjs/operators';
import * as Sentry from '@sentry/node';
const enableSentry = (err) => {
Sentry.captureException(err);
return throwError(err);
};
@Injectable()
export class SentryInterceptor implements NestInterceptor {
constructor(private env) {
this.env = env;
}
intercept(
context: ExecutionContext,
next: CallHandler<any>,
): Observable<any> | Promise<Observable<any>> {
if (this.env == 'prod') return next.handle().pipe(catchError(enableSentry));
else return next.handle();
}
}

Further, we can Filter the unwanted Error to be sent in the sentry dashboard, what I would like to do is list out the errors that I want to track in sentry and check if that error occurred or not, but later what I found convenient was to make a list of error that I don't want to track in sentry and check from it.

src/common/interceptors/sentry.interceptor.ts

import {
CallHandler,
ExecutionContext,
Injectable,
InternalServerErrorException,
NestInterceptor,
} from '@nestjs/common';
import { Observable, throwError } from 'rxjs';
import { catchError } from 'rxjs/operators';
import * as Sentry from '@sentry/node';
const errorsToTrackInSentry = [InternalServerErrorException, TypeError];
const enableSentry = (err) => {
let sendToSentry = errorsToTrackInSentry.some(
(errorType) => err instanceof errorType,
);
if (sendToSentry) Sentry.captureException(err);
return throwError(err);
};
@Injectable()
export class SentryInterceptor implements NestInterceptor {
constructor(private env) {
this.env = env;
}
intercept(
context: ExecutionContext,
next: CallHandler<any>,
): Observable<any> | Promise<Observable<any>> {
if (this.env == 'prod') return next.handle().pipe(catchError(enableSentry));
else return next.handle().pipe(catchError((err) => throwError(err)));
}
}

--

--

No responses yet