본문 바로가기

🧑🏻‍💻 Dev/Nest JS

[NestJS] winston 라이브러리를 이용하여 파일에 로그 남기기

Nest로 진행하고 있던 프로젝트가 있었습니다. InternalServerError(500)가 터지면 디버깅을 하거나, 오류에 대응할 때 효율을 높일 수 있도록 로그를 파일에 남기려고 적용하고자 했습니다. Java에서는 LogBack으로 로그를 관리하는 것 같은데, Nest에서는 Winston이라는 라이브러리를 사용한다.

 

Winston Github에 가서 Winston이 제공하는 기능, 사용법을 확인해 볼 수 있습니다.

 

GitHub - winstonjs/winston: A logger for just about everything.

A logger for just about everything. Contribute to winstonjs/winston development by creating an account on GitHub.

github.com

 

Winston 라이브러리 설치


npm install winston nest-winston

 

 

GlobalExceptionFilter에서 Winston 사용하여 파일에 로그 남기기


import { LocalDateTime } from "@js-joda/core";
import { ArgumentsHost, Catch, ExceptionFilter, HttpException, HttpStatus } from "@nestjs/common";
import { HttpArgumentsHost } from "@nestjs/common/interfaces";
import { Response } from "express";
import * as winston from 'winston';

const { simple } = winston.format;

@Catch()
export class GlobalExceptionFilter implements ExceptionFilter {
  private readonly apiLogger;
  private readonly basicLogger;

  constructor() {
    this.apiLogger = winston.createLogger({
      transports: [
        new winston.transports.File({
          level: 'error',
          filename: 'api.error.log',
          dirname: 'logs',
          format: simple()
        })
    ]});

    this.basicLogger = winston.createLogger({
      transports: [
        new winston.transports.File({
          level: 'error',
          filename: 'basic.error.log',
          dirname: 'logs',
          format: simple()
        })
    ]});
  }

  catch(exception: any, host: ArgumentsHost) {
    const httpArgumentHosts: HttpArgumentsHost = host.switchToHttp();
    const response: Response = httpArgumentHosts.getResponse();
    const requestUrl: string = host.getArgs()[0].url;

    if (exception instanceof HttpException) {
      let status: number = exception.getStatus();

      if (status === HttpStatus.INTERNAL_SERVER_ERROR) {
        this.apiLogger.error(`[${LocalDateTime.now()}][${requestUrl}] ${exception}`);
      }

      response
        .status(status)
        .json({
          success: false,
          error: exception.message
        });
      
      return;
    }

    this.basicLogger.error(`[${LocalDateTime.now()}][${requestUrl}] ${exception}`);

    response
      .status(HttpStatus.INTERNAL_SERVER_ERROR)
      .json({
        success: false,
        error: exception.message
      });
  }
}

 

Service에서 발생 시키는 예외들을 한 곳으로 모아서 처리하기 위해서 ExceptionFilter를 구현하는 GlobalExceptionFilter를 구현했습니다.

 

InternalServerError(500)로서 error 로그를 찍는 경우를 아래와 같이 2가지로 정했습니다.

 

  1. throw new InternalServerException()으로 던져져서 Status 코드가 500인 경우
  2. 예상하지 못한 곳에서 예외가 터져서 핸들링하지 못한 Exception인 경우

 

1번의 경우에는 의도적으로 InternalServerException을 이용해서 던지는 경우이기 때문에 api.error.log 파일에 로그를 저장했습니다. 반대로 2번의 경우에는 의도하지 않은 예외를 잡은 경우이기 때문에 basic.error.log 파일에 로그를 저장하도록 했습니다. 추가로 디버깅이 편리하도록 어느 API 요청에서 발생했던 예외인지 확인을 위해 Request URL도 함께 로그에 남겼습니다.

 

포맷에 combine()을 이용해서 timestamp를 찍는 방법도 있었는데, js-joda의 LocalDateTime.now()가 더 편하고, 코드로 봤을 때 직관적인 것 같아서 simple() 포맷을 사용해서 로그를 남겼습니다. Winston Github 링크에 가보면 다양한 포맷에 대한 설명이 있습니다. 의도에 맞게 사용하시는 것을 추천합니다.

 

logs 디렉터리 내부에 생긴 log 파일 2개

이렇게 하고 재시작을 하면 logs/api.error.log와 logs/basic.error.log 파일이 생깁니다. 이제 해당 필터를 거치게할 Controller에 @UseFilters()를 붙여서 테스트를 해봐야 합니다. 500 에러가 발생하도록 해서 로그가 파일에 정상적으로 찍히는지 확인해봅시다.

 

@UseFilters(GlobalExceptionFilter)
@Controller('letters')
export class LettersController {
  // 코드 생략
}

 

 

로그 확인하기


500 Internal Server Error 발생

위에서 Postman을 통해 확인한 Internal Server Error입니다. 이는 throw new InternalServerException을 통해 의도된 에러였기 때문에 api.error.log에 저장됩니다. 정상적으로 저장이 됐는지 확인해 봅니다.

 

logs/api.error.log 파일

정상적으로 저장됐습니다. 의도했던 error 로그가 파일에 찍혀서 저장된 것을 확인할 수 있습니다.

 

 

참고


https://docs.nestjs.com/techniques/logger#use-external-logger

 

Documentation | NestJS - A progressive Node.js framework

Nest is a framework for building efficient, scalable Node.js server-side applications. It uses progressive JavaScript, is built with TypeScript and combines elements of OOP (Object Oriented Programming), FP (Functional Programming), and FRP (Functional Rea

docs.nestjs.com