이전에 node.js로 백엔드를 개발할때 api소요시간을 계산한 로직을 짠 적이 있다. 그걸 nest.js에도 적용해보자.
app.use((req: Request, res: Response, next: NextFunction) => {
const start = Date.now();
res.on('finish', () => {
const duration = Date.now() - start;
logger.http(`[${req.method}] ${req.url} ${duration}ms`);
// check exceed 2000ms api
if (duration > 2000) {
errorBot.sendMessage(
'latency',
`[${req.method}] ${req.url}`,
req.userUUID ?? null,
req.ip,
duration
);
}
});
next();
});
먼저 node.js에서의 latency 계산 로직이다. node.js는 interceptor
가 따로 존재하지 않아서(만들려면 비슷하게 만들겠지만) app.ts파일에 작성해 주었다.
app.use((req: Request, res: Response, next: NextFunction) => {
const start = Date.now();
const { method, originalUrl } = req;
res.on('finish', () => {
const { statusCode } = res;
const logger = new Logger();
const duration = Date.now() - start;
if (statusCode < 400) {
logger.log(`[${method}] ${originalUrl} ${duration}ms`);
} else if (statusCode < 500) {
logger.warn(`[${method}] ${originalUrl} ${duration}ms`);
} else {
logger.error(`[${method}] ${originalUrl} ${duration}ms`);
}
if (duration > 2000) {
slackClient.sendApiLatency(method, originalUrl, duration);
}
});
next();
});
그리고 nest.js에서 작성한 로직이다. 마찬가지로 main.ts에 작성해 주었다.
저번에는 api소요시간이 2000ms가 넘는 api들을 discord로 알림을 보내주었는데, 이번엔 slack으로 알림을 보내주었고
저번에는 200번대 응답과 400번대 응답을 똑같이 log
로 작성하여 구분하기가 어려웠는데 이번엔 200번대는 log
400번대는 warn
500번대는 error
로 구분하여 로그를 기록하여 응답을 알아보기 쉽게 해주었다.
async function bootstrap() {
const app = await NestFactory.create(AppModule, {
logger: WinstonModule.createLogger(winstonConfig),
});
const config = new DocumentBuilder()
.setTitle('Sleact API')
.setDescription('Sleact 개발을 위한 API 문서입니다.')
.setVersion('1.0')
.addCookieAuth('connect.sid')
.build();
const document = SwaggerModule.createDocument(app, config);
SwaggerModule.setup('api', app, document);
const slackClient = new SlackApiClient();
// api소요시간, 보낸사람 ip 확인하는 middleware
app.use((req: Request, res: Response, next: NextFunction) => {
const start = Date.now();
const { method, originalUrl } = req;
res.on('finish', () => {
const { statusCode } = res;
const logger = new Logger();
const duration = Date.now() - start;
if (statusCode < 400) {
logger.log(`[${method}] ${originalUrl} ${duration}ms`);
} else if (statusCode < 500) {
logger.warn(`[${method}] ${originalUrl} ${duration}ms`);
} else {
logger.error(`[${method}] ${originalUrl} ${duration}ms`);
}
// TODO: production 일때만
if (duration > 2000) {
slackClient.sendApiLatency(method, originalUrl, duration);
}
});
next();
});
// class-validator 글로벌 파이프라인 추가
app.useGlobalPipes(new ValidationPipe());
await app.listen(3000);
}
bootstrap();
이렇게 여기에 작성하니 main.ts가 매우 지저분해졌다. main.ts에는 global로 작동하는 pipe, intercepter, middleware들이 들어가야 하는데 이렇게 작성하니까 가독성도 떨어진다.
// src/interceptors/logging.interceptor.ts
import {
Injectable,
NestInterceptor,
ExecutionContext,
CallHandler,
Logger,
} from '@nestjs/common';
import { Observable } from 'rxjs';
import { tap } from 'rxjs/operators';
import { SlackApiClient } from '../utils/slack.api.client';
@Injectable()
export class ApiTimeInterceptor implements NestInterceptor {
private logger: Logger;
private slackClient: SlackApiClient;
constructor() {
this.logger = new Logger();
this.slackClient = new SlackApiClient();
}
intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
const start = Date.now();
const { method, originalUrl } = context.switchToHttp().getRequest();
return next.handle().pipe(
tap(() => {
const response = context.switchToHttp().getResponse();
const { statusCode } = response;
const duration = Date.now() - start;
if (statusCode < 400) {
this.logger.log(`[${method}] ${originalUrl} ${duration}ms`);
} else if (statusCode < 500) {
this.logger.warn(`[${method}] ${originalUrl} ${duration}ms`);
} else {
this.logger.error(`[${method}] ${originalUrl} ${duration}ms`);
}
if (duration > 2000) {
this.slackClient.sendApiLatency(method, originalUrl, duration);
}
}),
);
}
}
따라서 파일로 따로 작성한다음 interceptor
로 만들어준다.
interceptor
는 middleware
와 비슷하지만 middleware
는 요청이 라우트 핸들러로 전달되기 전에 동작하며
interceptor
는 요청에 대한 라우트 핸들러의 처리 전후로 호출되어 요청과 응답을 다룰 수 있다.
Api소요시간을 계산하기 위해선 요청의 시작지점과 끝지점의 시간을 계산해야하므로 interceptor
를 사용하여야 한다.
async function bootstrap() {
const app = await NestFactory.create(AppModule, {
logger: WinstonModule.createLogger(winstonConfig),
});
const config = new DocumentBuilder()
.setTitle('Sleact API')
.setDescription('Sleact 개발을 위한 API 문서입니다.')
.setVersion('1.0')
.addCookieAuth('connect.sid')
.build();
const document = SwaggerModule.createDocument(app, config);
SwaggerModule.setup('api', app, document);
// api 소요시간 계산 interceptor
app.useGlobalInterceptors(new ApiTimeInterceptor());
// class-validator 글로벌 pipe
app.useGlobalPipes(new ValidationPipe());
await app.listen(3000);
}
그 다음 main.ts에 useGlobalInterceptors
를 이용하여 전역적으로 interceptor
를 달아주면 된다.