Logger

장현욱(Artlogy)·2022년 11월 17일
0

Nest.js

목록 보기
11/18
post-thumbnail

Logger


서비스가 커지면 커질수록 동작 과정을 남기고 추적하는 일이 중요해진다.
이슈가 발생한 지점과 콜스택이 함께 제공된다면 디버깅이 매우 수월해질것이다.

내장 로거

내장 Logger는 @nest/common패키지로 제공되며, 로깅옵션을 조절하면 다음과 같이 로깅 시스템의 동작을 제어할 수 있다.

  • 로깅 비활성화
  • 로그 레벨 지정 : log,error,warn,debug,verbose
  • 로거의 타임스탬프 재정의
  • 기본 로거를 오버라이딩
  • 기본 로거 확장 및 커스텀 로거 작성
  • DI 로그 주입, 테스트 모듈로 제공

내장 로거의 인스턴스는 로그를 남기고자 하는 부분에서 직접 생성해서 사용할 수 있다.

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

@Injectable()
export class AppService {
  //AppService.name으로 로그가 나오는 위치를 지정했다. (물론 다른 문자열로 써도됌)
  private readonly logger = new Logger(AppService.name);

  getHello(): string {
    this.logger.error('level: error');
    this.logger.warn('level: warn');
    this.logger.log('level: log');
    this.logger.verbose('level: verbose');
    this.logger.debug('level: debug');

    return 'Hello World!';
  }
}

로깅 비활성화

const app = await NestFactory.create(AppModule, {
  logger: false,
});

추천은 안하지만 로그가 아에 뜨지 않게 만들 수 있다.

로그 레벨 지정

  const app = await NestFactory.create(AppModule, {
    logger:
      process.env.NODE_ENV === 'prod'
        ? ['error', 'warn', 'log']
        : ['debug', 'error', 'log', 'verbose', 'warn'],
  });

일반적으로 빌드환경에선 debug 로그가 남지 않도록 하는게 좋다.
민감한 정보를 포함되고, 디버그 로그는 그 자체로 사이즈가 크기때문에 상용환경에선 좋은 퍼포먼스를 기대하기 힘들다.

애초에 상용환경에서 디버그를 켜놓는게 아무 의미가 없다.

커스텀 로그

로그가 몇개 안나오면 그냥 좀 살펴보면 되지만, 서비스를 하면 수 많은 로그가 쌓이고 거기에서 내가 원하는 로그를 찾기란 하고싶지도 않고 힘든 일이다. 때문에 로그를 데이터베이스에 저장해 검색을 하는 커스텀로거를 만들어 볼 수 있다. 내 포스팅에선 커스텀 로그를 만들기 위한 기본 재료만 알려줄것이다.

기본 형태

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

@Injectable()
export class MyLogger extends ConsoleLogger {
  error(message: any, stack?: string, context?: string) {
    super.error.apply(this, arguments);
  }

  private doSomething() {
    // log에 대한 부가 로직을 넣어준다.
    // ex. DB에 저장
  }
}

이렇게 해주고 루트모듈이나, 글로벌 모듈에 등록만 해주면 전역적으로 종속성을 주입하여 사용할 수 있다.

Winston

Nestjs에서 공식적으로 패키징하고있는 로그패키지이다.
커스텀로그로 DB에 저장, 추적, 검색을 구현할 수도 있지만 많은 수고가 필요하다 Winston을 이용하면 간단하게 구현이 가능하다.

근데 난 내장 로거를 커스텀해서 사용하는게 더 편하더라..

패키지 설치

#npm
$ npm i -s nest-winston winston

#yarn
$ yarn add nest-winston winston

### 설정
```ts
WinstonModule.forRoot({
      transports: [
        new winston.transports.Console({
          //로그 레벨 지정
          level: process.env.NODE_ENV === 'prod' ? 'info' : 'silly',
          format: winston.format.combine(
            //로그 남긴 시간 추가
            //로그에 appName('ArtlogyApp')남기며, 로그에 색조합을 주는 prettyPrint true
            winston.format.timestamp(),
 
            nestWinstonModuleUtilities.format.nestLike('ArtlogyApp', {
              prettyPrint: true,
            }),
          ),
        }),
      ],
    }),

레벨

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

숫자가 낮을 수록 레벨이 높으며 설정된 레벨보다 낮은 로그가 출력된다.

사용

import { Logger as WinstonLogger } from 'winston';
import { WINSTON_MODULE_NEST_PROVIDER } from 'nest-winston';
...

export class UsersController {

  constructor(
    @Inject(WINSTON_MODULE_NEST_PROVIDER) private readonly logger: WinstonLogger,
  ) { }

  @Post()
  async createUser(@Body() dto: CreateUserDto): Promise<void> {
        this.printWinstonLog(dto);
        ...
  }

    private printWinstonLog(dto) {
    console.log(this.logger.name);

    this.logger.error('error: ', dto);
    this.logger.warn('warn: ', dto);
    this.logger.info('info: ', dto);
    this.logger.http('http: ', dto);
    this.logger.verbose('verbose: ', dto);
    this.logger.debug('debug: ', dto);
    this.logger.silly('silly: ', dto);
  }
    ...
}

WINSTON_MODULE_NEST_PROVIDER토큰으로 winston에 제공하는 Logger객체를 주입 받을 수 있다.

Nest Logger 대체하기

forRoot나 forRootAsync으로 모듈을 만들면 부트스트랩까지 대체할 수 없기 때문에
다음과 같이 만들어준다.

main.ts
import { AuthGuard } from '@config/guard/jwt.guard';
import { swaggerConfig } from '@config/swagger.config';
import { ValidationPipe } from '@nestjs/common';
import { NestFactory } from '@nestjs/core';
import { SwaggerModule } from '@nestjs/swagger';
import { AppModule } from './app/app.module';
import * as winston from 'winston';
import {
  WINSTON_MODULE_NEST_PROVIDER,
  utilities as nestWinstonModuleUtilities,
  WinstonModule,
} from 'nest-winston';
async function bootstrap() {
  const app = await NestFactory.create(AppModule, {
    logger: WinstonModule.createLogger({
      transports: [
        new winston.transports.Console({
          level: process.env.NODE_ENV === 'prod' ? 'info' : 'silly',
          format: winston.format.combine(
            winston.format.timestamp(),
            winston.format.ms(),
            nestWinstonModuleUtilities.format.nestLike('ArtlogyApp', {
              prettyPrint: true,
            }),
          ),
        }),
      ],
    }),
  });
  app.useGlobalPipes(
    new ValidationPipe({
      // 변환 허용
      transform: true,
      // 유효성 검사 데코레이터가 없는 필드를 요청 할 경우 건너뜀
      whitelist: true,
      // 유효성 검사 데코레이터가 없는 필드가 있을 경우 에러를 반환
      forbidNonWhitelisted: true,
      // null, undefined값을 가진 객체는 유효성 검사를 건너뜀
      skipMissingProperties: true,
    }),
  );
  const authGuard: AuthGuard = app.get<AuthGuard>(AuthGuard);
  app.useGlobalGuards(authGuard);
  const swaggerDocument = SwaggerModule.createDocument(app, swaggerConfig);
  SwaggerModule.setup('api', app, swaggerDocument);
  await app.listen(8084, () => {
    console.log('server running');
  });
}
bootstrap();

로그를 남길 모듈에 Logger 서비스를 프로바이더로 선언하면 된다.

import { Logger } from '@nestjs/common';
...
@Module({
    ...
  providers: [Logger]
})
export class UsersModule { }

이제 @nestjs/common 패키지의 Logger 또는 LoggerService를 다음처럼 주입받을 수 있다.

import { Logger } from '@nestjs/common';
...
export class UsersController {
  constructor(
    @Inject(Logger) private readonly logger: LoggerService,
  ) { }
    ...
}

로그 활용

winston을 이용하거나 커스텀을 이용하는 가장 큰 이유는 파일이나 DB에 저장해서 로그를 활용하고자 함이다. 최근에는 New Relic, DataDog같은 외부 유로 서비스에 전송해서 분석과 시각화 툴을 활용하기도 한다.

winston-transport라이브러리로 지속적인 로그 전달이 가능하다.
난 돈이 없기 때문에 이 부분은 그냥 넘어 갈 것인데 관심있음 레퍼런스 많으니 찾아보면 좋다.
(물론 DB에 넣고 웹으로 내가 만들어도 된다. )

💡 상용 서비스에서 에러 정보, 호출 스택 등의 정보와 사용자가 추가한 로그를 파일에 기록하고 기록된 파일을 외부 서비스에 다시 전달하여 검색과 시각화를 한다. 구현시 웹프레임워크나 서비스 업체에서 제공하는 라이브러리를 활용한다.

0개의 댓글

관련 채용 정보