처음부터 차근차근

[NestJS] Winston Logger Interceptor 구현 본문

FrameWork/NestJS

[NestJS] Winston Logger Interceptor 구현

HangJu_95 2023. 12. 3. 11:11
728x90

이번에는 Winston을 통한 NestJS Logger Interceptor를 구현해보겠습니다.

저번에 Winston을 NestJS App에 초기 설정을 하였는데, 이 부분을 먼저 읽고 오시면 도움이 됩니다.

 

[NestJS] Winston Logger 초기 설정 및 Logger 사용

이번 시간에는 NestJS에 Logger를 적용해보겠습니다. GraphQL 과제에서 Logging 기능을 추가해야 되기 때문에, 공부할 겸 적용시켰습니다. Log?? 컴퓨팅에서 로그파일은 운영체제나 다른 소프트웨어가 실

hangju95.tistory.com

사용 의도

  • 접속한 사람의 IP와 자주 사용하는 정보를 기록하기 위해
  • brutal force 공격을 통해 접속을 많이 하는 경우 Ban 처리도 가능할 것으로 기능 구현 예정(Throttler 모듈을 통해 rate limit도 가능)

Logger Interceptor 구현

GraphQL로 Interceptor로 구현할 때는, ExecutionContext를 Gql 전용 ExecutionContext로 전환해야 하며, Guard나 Interceptor를 사용할 때는 필수적으로 적용해야 합니다.

// NestJS 공식 문서 예시

import { CanActivate, ExecutionContext, Injectable } from '@nestjs/common';
import { GqlExecutionContext } from '@nestjs/graphql';

@Injectable()
export class AuthGuard implements CanActivate {
  canActivate(context: ExecutionContext): boolean {
    const ctx = GqlExecutionContext.create(context);
    return true;
  }
}

현재 과제는 GraphQL로 구현하고 있기 때문에, gqlContext로 interceptor를 구현하였습니다.

저번 시간에 Logger를 적용하였기 때문에 이번 시간에는 Logger를 한번 불러오겠습니다.

Logger의 내용으로는 Path와 fieldName 그리고 IP와 ResponseTime을 작성하였습니다.

// src/common/interceptor/logger.interceptor.ts

import {
  CallHandler,
  ExecutionContext,
  Injectable,
  LoggerService,
  NestInterceptor,
} from '@nestjs/common';
import { GqlExecutionContext } from '@nestjs/graphql';
import { Observable } from 'rxjs';
import { tap } from 'rxjs/operators';

@Injectable()
export class LoggingInterceptor implements NestInterceptor {
  constructor(private readonly logger: LoggerService) {}
  intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
    // Response까지 timed 계산하기 위한 시간
    const now = Date.now();
    // gqlContext로 전환
    const gqlContext = GqlExecutionContext.create(context);
    // Ip 접근용 Context 변환
    const ip = gqlContext.getContext().req.ip;
    // path와 fieldName을 얻기 위한 info
    const info = gqlContext.getInfo();

    // tap을 통해 Response가 끝나는 경우 응답 처리 진행
    return next.handle().pipe(
      tap(() => {
        // Response time 계산
        const responseTime = Date.now() - now;

        // info level로 logger 진행
        this.logger.log({
          // context는 path와 fieldName으로
          context: `${info.path.typename}: ${info.fieldName}`,
          // message는 IP 주소와 Response time으로 진행
          message: `IP: ${ip} - ${responseTime}ms`,
        });
      }),
    );
  }
}

다만, 에러가 발생할 경우는 Logger를 적용시키지 않았습니다.

그것은 다음 시간 Custom Exception을 통해 구현할 예정입니다.

참고

https://docs.nestjs.com/graphql/other-features