Interceptor를 이용해 http요청에 대한 응답시간 계산하기

0

이전에 node.js로 백엔드를 개발할때 api소요시간을 계산한 로직을 짠 적이 있다. 그걸 nest.js에도 적용해보자.


Api 소요시간 계산 api

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들이 들어가야 하는데 이렇게 작성하니까 가독성도 떨어진다.


Interceptor 이용

// 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로 만들어준다.
interceptormiddleware와 비슷하지만 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를 달아주면 된다.

profile
https://www.youtube.com/watch?v=__9qLP846JE

0개의 댓글

관련 채용 정보