Sentry는 코드를 진단하고 고치며 최적화하여 개발자를 돕는 모니터링 플랫폼 애플리케이션(오픈소스) 입니다.
NestJS에서 에러가 발생했을때 sentry
에 전달하고, sentry client인 raven
을 통한 인터셉터로 slack에 알리는 애플리케이션을 구축해봅니다.
참고 🔍
(이 포스팅의 주요 핵심 개념은 NestJS의 Interceptor 기능 입니다.)
NestJS - Interceptor
Sentry.io
🙈[Spring] Interceptor (1) - 개념 및 예제🐵
$ nest new sentry-test
$ cd sentry-test
$ npm i @sentry/node nest-raven
Sentry 웹 → Project → Create a new Project → Node 선택 → 프로젝트 생성후 DSN
복사
main.ts
에 sentry 연결
import { NestFactory } from '@nestjs/core';
import { init as SentryInit } from '@sentry/node';**
import { AppModule } from './app.module';
async function bootstrap() {
const app = await NestFactory.create(AppModule);
// connect Sentry
SentryInit({
dsn:
'https://940633ab27b94fdd8afc6ae73b986030@o561269.ingest.sentry.io/이히히',
});
await app.listen(3000);
}
bootstrap();
NOTION
sentry는 무료버전으로 request 수가 제한되어있습니다. 되도록NODE_ENV == 'production'
환경에서 적용하도록 합시다.
// app.module.ts
import { HttpException, Module } from '@nestjs/common';
import { ConfigModule } from '@nestjs/config';
import { APP_INTERCEPTOR } from '@nestjs/core';
import { RavenInterceptor, RavenModule } from 'nest-raven';
import { AppController } from './app.controller';
import { AppService } from './app.service';
@Module({
imports: [
RavenModule,
ConfigModule.forRoot({
envFilePath: '.env',
}),
],
controllers: [AppController],
providers: [
AppService,
{
provide: APP_INTERCEPTOR, // 전역 인터셉터로 지정
useValue: new RavenInterceptor({
filters: [
{
type: HttpException,
// Filter exceptions of type HttpException.
// Ignore those that have status code of less than 500
filter: (exception: HttpException) => {
return 500 > exception.getStatus();
},
},
],
}),
},
],
})
export class AppModule {}
Slack API 홈페이지
→ Create New App
→ 이름 및 workspace 지정 → Feature/Incoming Webhooks
메뉴 → Activate Incoming Webhooks 를 On
→ Webhook URL .env
파일에 저장
// .env
SLACK_WEBHOOK=https://hooks.slack.com/services/T01SZ44S95J/B01TGPASYDN/이히히
$ npm i @slack/client
import {
CallHandler,
ExecutionContext,
Injectable,
NestInterceptor,
} from '@nestjs/common';
import { captureException } from '@sentry/minimal';
import { Observable } from 'rxjs';
import { catchError } from 'rxjs/operators';
import { IncomingWebhook } from '@slack/client';
@Injectable()
export class WebhookInterceptor implements NestInterceptor {
intercept(_: ExecutionContext, next: CallHandler) /** : Observable<any>*/ {
return next.handle().pipe(
catchError((error) => {
const webhook = new IncomingWebhook(**process.env.SLACK_WEBHOOK**);
webhook.send({
attachments: [
{
color: 'danger',
text: '회사 드가자~ 드가자~!',
fields: [
{
title: `Request Message: ${error.message}`,
value: error.stack,
short: false,
},
],
ts: Math.floor(new Date().getTime() / 1000).toString(), // unix form
},
],
});
return null;
}),
);
}
}
import { HttpException, Module } from '@nestjs/common';
import { ConfigModule } from '@nestjs/config';
import { APP_INTERCEPTOR } from '@nestjs/core';
import { RavenInterceptor, RavenModule } from 'nest-raven';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { WebhookInterceptor } from './webhook.interceptor';
@Module({
// ...
providers: [
// ...
// Webhook Interceptor 적용
{
provide: APP_INTERCEPTOR,
useClass: WebhookInterceptor,
},
],
})
export class AppModule {}
import { Controller, Get } from '@nestjs/common';
import { AppService } from './app.service';
@Controller()
export class AppController {
constructor(private readonly appService: AppService) {}
@Get()
getHello(): string {
return this.appService.getHello();
}
@Get('error')
getError() {
throw new Error('this is error!!');
}
}
$ curl -o http://127.0.0.1:3000/error