[NestJS] Winston Logger 적용기

주형(Jureamer)·2022년 12월 2일
0
post-thumbnail

개요

NestJS로 작성된 과거 프로젝트를 리팩토링하려고 다시금 뜯어보니 로깅 작업을 안해줘서 모니터링하기가 매우 불편했습니다.

기존에 Express에서 사용했던 Morgan + Winston 조합으로 로깅 환경을 구현하려고 했으나 nest-morgan 모듈 지원이 종료되었습니다.

그리하여 대체제로 nest-pino를 고민했으나 미들웨어로 작성하여 사용이 가능해서 미들웨어 + winston으로 로깅 환경을 구성해보려고 합니다.

Logger Middleware 모듈 작성

logger 미들웨어 파일을 생성해주고 NestMiddleWare 모듈을 오버라이딩 하기 위해 Implements한 class를 아래와 같이 작성 해줍니다.

// logger.middleware.ts

@Injectable()
export class LoggerMiddleware implements NestMiddleware { //implements -> 반드시 구현하도록 강제하는거임
    private logger = new Logger('HTTP');

    use(request: Request, response: Response, next: NextFunction): void {
        const { ip, method, originalUrl } = request;

        response.on('finish', () => {
          const { statusCode } = response;
          this.logger.log(`${method} ${originalUrl} ${statusCode} ${ip}`);
        });
		next();
    }
}

이후 app.module.ts에서 아래와 같이 설정해줍니다.
NestModule을 한 AppModule 아래 MiddlewareConsumer를 설정한 뒤 모든("*") 경로로 인해 들어오는 곳에 LoggerMiddleware를 apply 시켜줍니다.

export class AppModule implements NestModule { 
  configure(consumer: MiddlewareConsumer): any {
    consumer.apply(LoggerMiddleware).forRoutes("*");
  }
}

이후 HTTP를 통해 들어오는 요청이 잘 들어오는 것을 확인할 수 있습니다.

Winston Logger 적용

이번엔 Winston을 적용시켜보겠습니다.
먼저 필요한 모듈을 설치해주겠습니다.

npm install winston nest-winston

그 다음 AppMoudle에 WinstonModule을 import 해줍니다.

// app.module.ts
import * as winston from 'winston';
import {
  utilities as nestWinstonModuleUtilities,
  WinstonModule,
} from 'nest-winston';

@Module({
  imports: [
        ...
    WinstonModule.forRoot({
      transports: [
        new winston.transports.Console({
                    level: process.env.NODE_ENV === 'production' ? 'info' : 'silly',
          format: winston.format.combine(
            winston.format.colorize(),
            winston.format.timestamp(),
            nestWinstonModuleUtilities.format.nestLike('MyApp', { prettyPrint: true }),
          ),
        }),
      ],
    }),
  ],
})
export class AppModule { }

로그 레벨 지정 코드(production ? info : silly)는 아래 winston의 로그 레벨을 단계를 먼저 살펴봐야합니다.

지정된 로그 레벨 이상의 레벨만 출력이 가능하기에 production 단계에서는 info(2)단계 이상, 그 외에는 silly(6)이상인 모든 레벨의 로그가 출력되는 형태입니다. (숫자가 작아질수록 상위 레벨)

{ 
  error: 0, 
  warn: 1, 
  info: 2, 
  http: 3,
  verbose: 4, 
  debug: 5, 
  silly: 6 
}

그리고 레벨별 색상을 적용시키기 위해 colorize format을 사용합니다.

이제는 로그를 찍기 위해 필요한 곳에 WINSTON_MODULE_PROVIDER 토큰을 주입한 뒤 Logger 객체를 주입받아 winston log를 사용할 수 있습니다.

// example.controller.ts
import { Logger } from 'winston';
import { WINSTON_MODULE_PROVIDER } from 'nest-winston';

export class UsersController {
    constructor(
        private usersService: UsersService,
        @Inject(WINSTON_MODULE_PROVIDER) private readonly logger: Logger,
        ) { }

@Post('/login')
    @HttpCode(200)
    async login(@Body() loginDto: LoginDto): Promise<object> {
        this.logger.info("로그인 시도!")
        this.printLoginDto(loginDto)
        return this.usersService.login(loginDto);
    }

    private printLoginDto(loginDto: LoginDto): void {
        this.logger.error(`error, ${JSON.stringify(loginDto)}`);
        this.logger.warn(`warn, ${JSON.stringify(loginDto)}`);
        this.logger.info(`info, ${JSON.stringify(loginDto)}`);
        this.logger.http(`http, ${JSON.stringify(loginDto)}`);
        this.logger.verbose(`verbose, ${JSON.stringify(loginDto)}`);
        this.logger.debug(`debug, ${JSON.stringify(loginDto)}`);
        this.logger.silly(`silly, ${JSON.stringify(loginDto)}`);
    }

아래와 같이 색상이 변경된 채 나오는 걸 확인할 수 있습니다.

이정도면 기본적인 로그를 보기에는 충분하나 조금 아쉬운 감이 있어서
다음에는 색상 변경과 로그 파일까지 저장하여 관리하는 법을 알아보겠습니다.

참고

profile
작게라도 꾸준히 성장하는게 목표입니다.

0개의 댓글