[NestJS] - 22. NestJS Logging - Winston 연결하기

2023. 3. 5. 12:54
반응형

Application을 개발 혹은 배포 후 예상치 못한 오류의 추적이나 사용 히스토리 파악을 위해서는 로그를 남기는 작업을 하게 된다.

이번에는 NestJS에서 Winston을 이용해서 Log를 남기는 작업을 포스팅해보겠다

 

 


패키지 설치

 

yarn add nest-winston winston winston-daily-rotate-file

 

Winston 패키지는 로그의 포멧, 파일 형태로 저장 등 로깅을 도와주는 패키지이다. NestJS에서도 해당 패키지를 사용할 수 있다.

위의 명령어를 통해 패키지를 설치하자.

 

 

 

 


Winston Config

 

▶ /src/utils/winston.config.ts

import { utilities, WinstonModule } from 'nest-winston';
import * as winstonDaily from 'winston-daily-rotate-file';
import * as winston from 'winston';
import * as moment from 'moment-timezone';

const env = process.env.NODE_ENV;

const appendTimestamp = winston.format((info, opts) => {
  if (opts.tz) {
    info.timestamp = moment().tz(opts.tz).format();
  }
  return info;
});

// 로그 저장 파일 옵션
const dailyOptions = {
  level: 'http', //http보다 높은 단계의 로그만 기록
  datePattern: 'YYYY-MM-DD', //날짜 포멧 지정
  dirname: __dirname + '/../../../logs', //저장할 URL
  filename: `app.log.%DATE%`,
  maxFiles: 30, //30일의 로그 저장
  zippedArchive: true, // 로그가 쌓였을 때 압축
  colorize: false,
  handleExceptions: true,
  json: false,
};


export const winstonLogger = WinstonModule.createLogger({
  transports: [
  	
    // 콘솔 출력 옵션 지정
    new winston.transports.Console({
      level: env === 'production' ? 'http' : 'silly',
      format:
        env === 'production'
          ? winston.format.simple()	//production 환경에서는 로그를 최소화
          : winston.format.combine(
              winston.format.timestamp(),
              utilities.format.nestLike('NestJS Project', {
                prettyPrint: true, // 로그를 예쁘게 출력해줌
              }),
            ),
    }),

    // 파일 로깅 옵션 지정
    new winstonDaily(dailyOptions),
  ],
  // 포멧 지정
  format: winston.format.combine(
    appendTimestamp({ tz: 'Asia/Seoul' }),
    winston.format.json(),
    winston.format.printf((info) => {
      return `${info.timestamp} - ${info.level} [${process.pid}]: ${info.message}`;
    }),
  ),
});
  • Winston을 사용하기 위한 Config 파일을 생성하였다.
  • Winston의 로그 레벨
    • error: 0, warn: 1, info: 2, http: 3, verbose: 4, debug: 5, silly: 6
    • 숫자가 낮을수록 심각도가 높은 로그이다.
  • LoggerService 객체를 WinstonModule.createLogger()를 통해 생성한다.
    • transports 배열 안에 콘솔 혹은 파일로 출력한 로그들의 종류를 작성한다.
    • new winston.transports.Console() : 콘솔로 출력할 로그를 생성하며, 인자에는 옵션을 넣는다.
      • level은 지정한 레벨보다 심각도가 같거나 높은 로그들을 출력하겠다는 뜻이다.
      • production일때는 간소화하여, 개발 환경일때는 포멧을 지정하여 출력해준다.
    • new winstonDaily() : 디렉터리에 일 간격으로 파일 형태로 저장할 로그를 생성하며, 인자에는 옵션을 넣는다.
    • format에는 전체 로그의 포멧을 지정한다. 특정 포멧이 있지 않으면 해당 포멧을 사용하게 된다. 아래의 포멧으로 로그가 저장된다.
    • 2023-03-05T12:35:58+09:00 - info [12516]: Select Products

 


Global Logger 사용하기

 

다음은 NestJS에서 기본적으로 제공하는 Logger Service이다.

우리는 이 Logger Service를 Winston으로 변환할 수 있다.

Winston을 Logger로 사용하여, 우리가 지정한 포멧으로 로그를 확인하고, 파일에 저장되는 것을 확인해보자.

 

 

 

▶ app.module.ts

import { ValidationPipe } from '@nestjs/common';
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
import * as cookieParser from 'cookie-parser';
import { HttpExceptionFilter } from './commons/filter/http-exception.filter';
import { winstonLogger } from './utils/winston/winston.config';

async function bootstrap() {
  const app = await NestFactory.create(AppModule);
  const logger = winstonLogger;
  app.useLogger(logger);
  //...
  await app.listen(3000);
}
bootstrap();

 

위와 같이 app.useLogger() 메서드에 작성했던 winston config에서 생성했던 LoggerService 객체를 넣어주자.

이러면 우리가 Winston에 설정한 형식의 로그를 사용하게 된다. 

 

 

Winston에서 설정한 포멧으로 로그가 찍혀 나온다.

 

 

logs 폴더에 로그가 저장된 모습

 

 

 


App 내부에서 Logger 사용하기

 

@Injectable()
export class ProductsService {
  private readonly logger = new Logger('ProductService');

  constructor(
	//...
  ) {}


  async findAll(): Promise<Product[]> {
    const result = await this.productRepository.find({
      relations: ['productCategory', 'productTags', 'productSalesLocation'],
    });

    this.logger.log('Select Products');
    return result;
  }
  
}

사용할 로그를 Provider들의 내부에 생성해준 후 사용하는 방식이다.

Logger 안의 생성자 인자로는 현재 App의 context를 넣어주어 어디서 로그를 찍었는지 파악하는 용도로 사용한다.

Context 이름을 지정해 주어야 하기에 NestJS 공식 문서에서도 의존성 주입을 하지 않고 생성하는 방법으로 사용한 것 같다.

 

logger.log(), logger.error(), logger.warn() 등을 이용하여 로그를 남기고 싶은 곳에 메서드를 작성하면 된다.

 

 

 


전역 서비스로 로그 사용하기

 

 

▶ common.module.ts

import { Logger, Module } from '@nestjs/common';
import { JwtRefreshStretagy } from './auth/jwt-refresh.strategy';
import { JwtAccessStrategy } from './auth/jwt-access.strategy';
import { JwtGoogleStrategy } from './auth/jwt-oauth-google.strategy';

@Module({
  imports: [],
  providers: [JwtRefreshStretagy, JwtAccessStrategy, JwtGoogleStrategy, Logger],
  exports: [JwtRefreshStretagy, JwtAccessStrategy, JwtGoogleStrategy, Logger],
})
export class CommonModule {}

 

▶ app.module.ts

@Module({
  imports: [
    ConfigModule.forRoot(),
    CommonModule,
	//...
  ],
  controllers: [],
  providers: [],
})
export class AppModule {}

 

다음과 같이 전역 모듈 안에 서비스를 주입하여 Context 구분 없이 전역으로 로그를 사용할 수도 있다.

 

 

 


Reference

https://velog.io/@aryang/NestJS-winston%EC%9C%BC%EB%A1%9C-%EB%A1%9C%EA%B7%B8-%EA%B4%80%EB%A6%AC

 

[NestJS] winston으로 로그 관리

 

velog.io

 

반응형

BELATED ARTICLES

more