처음부터 차근차근

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

FrameWork/NestJS

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

HangJu_95 2023. 12. 1. 19:51
728x90

이번 시간에는 NestJS에 Logger를 적용해보겠습니다.

GraphQL 과제에서 Logging 기능을 추가해야 되기 때문에, 공부할 겸 적용시켰습니다.

Log??

컴퓨팅에서 로그파일은 운영체제나 다른 소프트웨어가 실행 중에 발생하는 이벤트나 각기 다른 사용자의 통신 소프트웨어 간의 메시지를 기록한 파일이다. 로그를 기록하는 행위는 logging이라고 한다.
-Wikipidia

Server 구축 시 Logging 작업은 굉장히 중요합니다.

Product 환경에서 에러가 발생 시, 문제가 어디에서 발생했고 해당 코드는 어디에 있는지 추적하기 편하게 해줍니다.

또한 사용자 접속 시 Ip 기록을 하여 접속자 수, 접속 기록 등을 확인할 수도 있습니다.

 

로그 관리는 개발자에게 필수이기 때문에 한번 적용해보겠습니다.

Winston 모듈

Winston은 Node.js에서 Log를 효율적으로 관리할 수 있게 도와주는 모듈입니다.

console.log와 console.error는 개발 중에는 편리하게 서버의 상황을 파악 할 수 있지만, 서버가 종료되는 순간 쌓여 있던 로그들도 사라지고, 언제 호출되었는지 파악하기 힘들기 때문에 실제 배포 시에는 사용하기 어려운 단점이 존재합니다.

Winston은 실제 서버를 운영할 때 console.log과 console.error를 대체하기 위한 모듈이며, 로그를 외부 파일에 저장해서 관리할 수 있도록 도와줍니다.

모듈 설치

NestJS 환경에서 Winston을 사용하려면 필요한 모듈을 설치해줘야 합니다.

$ npm i winston nest-winston nest winston-daily-rotate-file
  • nest-winston : Nest Architechture에 쉽게 적용할 수 있도록 도와주는 모듈
  • winston-daily-rotate-file : 로그 파일을 관리해주는 모듈, 기본적으로 하루 단위로 새 로그파일을 생성해준다.

util 파일 생성

먼저 winston 설정을 위한 TS 파일을 하나 만들겠습니다.

// src/util/winston.util.ts
import { WinstonModule, utilities } from 'nest-winston';
import * as winstonDaily from 'winston-daily-rotate-file';
import * as winston from 'winston';

// 로그 파일 저장소를 위한 path 지정
const logDir = __dirname + '/../../logs';

export const dailyOptions = (level: string) => {
  return {
    // level을 받는다.
    level,
    // date 패턴 작성
    datePattern: 'YYYY-MM-DD',
    // 파일이 생성될 위치
    dirname: logDir + `/${level}`,
    // 파일 이름
    filename: `%DATE%.${level}.log`,
    maxFiles: 30, //30일치 로그파일 저장
    zippedArchive: true, // 로그가 쌓이면 압축하여 관리
    JSON: false,
  };
};

export const winstonLogger = WinstonModule.createLogger({
  // logger를 어디로 기록할 지 보내는 작성
  transports: [
    // console.log로 남기는 방법.
    new winston.transports.Console({
      // level 지정
      level: 'info',
      // 로그가 어떻게 나올지 포맷 지정
      format: winston.format.combine(
        // 시간 표시
        winston.format.timestamp(),
        // nest처럼 나오게 하는 format
        // 첫번째 인자는 app의 이름을 작성
        // 두번째 인자는 속성 작성
        utilities.format.nestLike('Survey', {
          prettyPrint: true,
          colors: true,
        }),
      ),
    }),
    // transport에 winstonDaily 추가
    // 파일 형식 지정
    new winstonDaily(dailyOptions('info')),
  ],
});

코드를 작성할 때 보면 먼저 dailyOption이 보입니다.

매일마다 file을 저장할 때 어떠한 Option으로 저장할 지 설정할 수 있습니다.

저의 경우 Level이 변경될 수 있으니, 이를 level을 받아서 설정할 수 있도록 함수 형식으로 제작하였습니다.

dailyOption에 들어갈 속성은 아래 더보기를 클릭하면 상세하게 나옵니다.

더보기
  • frequency: 회전 빈도를 나타내는 문자열입니다. 이는 특정 시간에 발생하는 회전과 달리 시간이 지정된 회전을 원하는 경우에 유용합니다. 유효한 값은 '#m' 또는 '#h'(예: '5m' 또는 '3h')입니다. 이 null을 남겨두는 datePattern것은 회전 시간 에 의존합니다 . (기본값: null)
  • datePattern: 회전에 사용할 moment.js 날짜 형식 을 나타내는 문자열 입니다. 이 문자열에 사용된 메타 문자는 파일 회전 빈도를 나타냅니다. 예를 들어, datePattern이 단순히 'HH'인 경우 매일 선택하여 추가되는 24개의 로그 파일로 끝납니다. (기본값: 'YYYY-MM-DD')
  • zippedArchive: 아카이브된 로그 파일을 gzip으로 압축할지 여부를 정의하는 부울입니다. (기본값: '거짓')
  • filename: 로그에 사용할 파일 이름입니다. 이 파일 이름은 파일 이름의 %DATE%해당 지점에 서식이 지정된 datePattern을 포함하는 자리 표시자를 포함할 수 있습니다 . (기본값: 'winston.log.%DATE%')
  • dirname: 로그 파일을 저장할 디렉터리 이름입니다. (기본: '.')
  • stream: 사용자 지정 스트림에 직접 쓰고 회전 기능을 우회합니다. (기본값: null)
  • maxSize: 회전할 파일의 최대 크기입니다. 바이트 수 또는 kb, mb 및 GB 단위가 될 수 있습니다. 단위를 사용하는 경우 접미사로 'k', 'm' 또는 'g'를 추가합니다. 단위는 숫자를 직접 따라야 합니다. (기본값: null)
  • maxFiles: 보관할 최대 로그 수입니다. 설정하지 않으면 로그가 제거되지 않습니다. 이는 파일 수 또는 일 수일 수 있습니다. 일을 사용하는 경우 접미사로 'd'를 추가합니다. (기본값: null)
  • options: 파일 스트림에 전달되어야 하는 추가 옵션을 나타내는 'https://nodejs.org/api/fs.html#fs_fs_createwritestream_path_options' 와 유사한 객체 . (기본값: { flags: 'a' })
  • auditFile: 감사 파일의 이름을 나타내는 문자열. 옵션 개체의 해시를 계산하여 생성된 기본 파일 이름을 재정의하는 데 사용할 수 있습니다. (기본값: '..json')
  • utc: 파일 이름의 날짜에 UTC 시간을 사용합니다. (기본값: 거짓)
  • extension : 파일 이름에 추가할 파일 확장자. (기본: '')
  • createSymlink : 현재 활성 로그 파일에 대한 tailable symlink를 만듭니다. (기본값: 거짓)
  • symlinkName: tailable symlink의 이름입니다. (기본값: 'current.log')

이제 winstonLogger를 보겠습니다.

export const winstonLogger = WinstonModule.createLogger({
  // logger를 어디로 기록할 지 보내는 작성
  transports: [
    // console.log로 남기는 방법.
    new winston.transports.Console({
      // level 지정
      level: 'info',
      // 로그가 어떻게 나올지 포맷 지정
      format: winston.format.combine(
        // 시간 표시
        winston.format.timestamp(),
        // nest처럼 나오게 하는 format
        // 첫번째 인자는 app의 이름을 작성
        // 두번째 인자는 속성 작성
        utilities.format.nestLike('Survey', {
          prettyPrint: true,
          colors: true,
        }),
      ),
    }),
    // transport에 winstonDaily 추가
    // 파일 형식 지정
    new winstonDaily(dailyOptions('info')),
  ],
});

WinstonModule.createLogger()를 통해 Logger를 만들고, 속성을 넣어주겠습니다.

transports에는 로그와 file 형식으로 저장할 것이기 때문에 두 가지를 넣어줬습니다.

이를 통해 Logger가 동작 시 Console에 Log가 찍히고, file에 저장될 것입니다.

추가적으로 옵션을 주고 싶다면, winston과 nest-winston을 참고바랍니다.

App에 Winston 적용

이제 nest-winston을 통해 winston 모듈을 nest에 주입하겠습니다.

저는 NestJS가 bootstrapping하는 내역도 Logging하기 위해 app logger 속성에 winstonModule을 주입시켰습니다.

const app = await NestFactory.create(AppModule, {
    // Nest에 내장되어 있는 logger에 winstonLogger 주입
    logger: winstonLogger,
  });

이후 RootModule에 Logger를 provider에 등록시켜줍니다.

import { Logger, Module } from '@nestjs/common';

@Module({
  providers: [Logger],
})
export class AppModule {}

그리고 사용할 Controller나 Service의 생성자에 Logger를 입력해줍니다.

import { Controller, Logger } from '@nestjs/common';

@Controller('cats')
export class CatsController {
  constructor(private readonly logger: Logger) {}
}

 

이를 통해 Logger를 사용할 수 있게 됩니다.

Logger 동작

NestJS에 WinstonModule까지 주입시켰다면, 이제 Logger를 동작시켜보겠습니다.

현재는 NestJS Logger에 WinstonModule을 주입시켰기 때문에 Log Levels는 NestJS의 로그 레벨을 따르고 있습니다.

NestJS의 로그 레벨은 총 6개 입니다.

  • fatal
  • error
  • warn
  • log (Info)
  • verbose
  • debug

Winston의 default Log Levels은 총 7개 입니다. (NpmConfigSetLevels)

  • error : 0
  • warn : 1
  • info: 2
  • http: 3
  • verbose: 4
  • debug: 5
  • silly : 6

Winston의 Log Levels과 다르지만, 추후 Custom LoggerService를 통해 Mapping 시켜보겠습니다.

import { Logger } from '@nestjs/common';
import { Query, Resolver } from '@nestjs/graphql';

@Resolver()
export class AppResolver {
  constructor(private readonly logger: Logger) {}
  @Query(() => String)
  async test() {
    this.logger.fatal('Fatal');
    this.logger.log('Log');
    this.logger.error('Error');
    this.logger.warn('Warn');
    this.logger.verbose('verbose');
    this.logger.debug('debug');
    // throw Error('심각한 에러!');
    return 'hello GQL';
  }
}

NestJS의 logger를 통해 한번 출력시켜보겠습니다.

보이다싶이 매칭되는 Log만 나오고 매칭되지 않는 로그는 나오지 않는 모습입니다.

(Fatal은 나오지 않음)

terminal에 찍힌 Log들

로그 파일도 한번 확인해보면

저장되어 있는 Logfile

정확하게 찍혀져 있는 모습이 보이고 있습니다.

(단, fatal은 찍히지 않음)

 

다음에는 이를 활용하여 Logger를 interceptor에 적용시켜보겠습니다.

참조

https://sjh9708.tistory.com/52

https://inpa.tistory.com/entry/NODE-📚-Winston-모듈-사용법-서버-로그-관리#1._winston_로그_경로__출력형식_설정

https://docs.nestjs.com/techniques/logger

https://www.npmjs.com/package/nest-winston

https://www.npmjs.com/package/winston-daily-rotate-file

https://www.npmjs.com/package/winston

https://velog.io/@aryang/NestJS-winston으로-로그-관리

https://lts0606.tistory.com/637

'FrameWork > NestJS' 카테고리의 다른 글

[NestJS] Custom Exception 구현  (2) 2023.12.03
[NestJS] Winston Logger Interceptor 구현  (0) 2023.12.03
[NestJS] GraphQL Mutation  (1) 2023.11.30
[NestJS] GraphQL Resolvers  (0) 2023.11.29
[NestJS] NestJS에서 GraphQL 초기 설정  (1) 2023.11.29