BackEnd/Nest.js

[Nest.js] 로깅

Grace 2023. 5. 30. 13:36

서비스에 기능이 늘어나고 사이즈가 커지게 되면 동작 과정을 남기고 추적하는 일이 중요하게 됩니다. 이슈가 발생했을 경우 이슈 증상만으로 원인을 파악하는 데에는 시간과 노력이 많이 들고 코드를 다시 역추적하면서 이해하는 데에 어려움이 따릅니다 .이슈가 발생한 지점과 콜 스택이 함께 제공된다면 빨리 해결이 가능할 것입니다. 또 어떤 기능이 많이 사용되는지와 같이 유저의 사용 패턴을 분석하는 데에도 로그를 활용할 수 있습니다.

서비스를 실행하면 서버 콘솔에는 로그가 출력됩니다. 이미 각 컴포넌트에서는 내장 로거를 이용하여 로그를 출력하고 있습니다.

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

  • 로깅 비활성화
  • 로그 레벨 지정: log, error, warn, debug, verbose
  • 로거의 타임스탬프 재정의
  • 리본 로거를 재정의(오버라이딩)
  • 기본 로거를 확장해서 커스텀 로거를 작성
  • 의존성 주입을 통해 손쉽게 로거를 주입하거나 테스트 모듈로 제공

내장 로거

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

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

@Injectable()
export class AppService {
  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!';
  }
}

로거를 생성할 때 어느 콘텍스트에 로그를 남기는지 이름을 줄 수 있습니다.

설정한 콘텍스트가 AppService로 함께 출력되고 있습니다. 로그 파일을 분석할 때 특정 콘텍스트에서 발생한 로그만 필터링하고자 할 때 사용할 수 있습니다.

로깅 비활성화

NestFactory.create 메서드의 NestApplicationOptions에 로깅을 활성화하는 logger 옵션이 있습니다. 이를 false로 지정하면 로그가 출력되지 않습니다.

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

로그 레벨 지정

일반적으로 프로덕션 환경에서는 debug 로그가 남지 않도록 하는게 좋습니다. 디버그 로그는 테스트 과정에서 디버깅용으로 객체가 가지고 있는 세부 데이터까지 남기는 경우가 많아 사용자의 민감 정보가 포함될 수 있기 때문입니다. 디버깅 로그는 로그의 크기 자체도 큰 경우가 대부분이므로 로그 파일의 사이즈를 줄이기 위한 목적도 있습니다.

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

로그 레벨을 하나만 설정한다면 해당 레벨보다 숫자가 큰 레벨의 로그도 모두 함께 출력됩니다. 따라서 debug로만 설정한다면 모든 로그가 출력됩니다.

const LOG_LEVEL_VALUE: Record<LogLevel, number> = {
  debug: 0,
  verbose: 1,
  log: 2,
  warn: 3,
  error: 4
}

커스텀 로거

로그 분석을 위해서는 어떤 형태든 로그를 저장해두고 검색을 할 수 있어야 하는데 내장 로거는 파일이나 데이터 베이스로 저장하는 기능을 제공하지 않습니다. 이를 위해서는 커스텀 로거를 만들어야 합니다.

커스텀 로거는 @nestjs/common 패키지의 LoggerService 인터페이스를 구현해야 합니다.

export interface LoggerService {
  log(message: any, ...optionalParams: any[]): any;
  error(message: any, ...optionalParams: any[]): any;
  warn(message: any, ...optionalParams: any[]): any;
  debug(message: any, ...optionalParams: any[]): any;
  verbose(message: any, ...optionalParams: any[]): any;
    setLogLevels(levels: LogLevel[]): any;
}
Export class MyLogger implements LoggerService {
  log(message: any, ...optionalParams: any[]) {
    console.log(message);
  }
  error(message: any, ...optionalParams: any[]) {
    console.log(message);
  }
  warn(message: any, ...ontionalParams: any[]) {
    console.log(message);
  }
  debug?(message: any, ...optionalParams: any[]) {
    console.log(message)
  }
  verbose?(message: any, ...optionalParams: any[]) {
    console.log(message)
  }
}

내장 로거와 같이 프로세스 ID, 로깅 시간, 로그 레벨(컬러), 컨텍스트 이름 등을 함께 출력하려면 직접 각 함수 내에 출력 메세지를 구성해야 합니다.

그래서 이렇게 처음부터 작성하기보다는 ConsoleLogger를 상속받으면 더 낫습니다.

export class MyLogger extends ConsoleLogger {
  error(message: any, stack?: string, context?: string) {
    super.error.apply(this, arguments);
    this.doSomething();
  }
  
  private doSomthing() {
    // 여기에 로깅과 관련된 부가 로직을 추가합니다.
    // ex. DB에 저장
  }
}

커스텀 로거 주입해서 사용하기

지금까지는 로거를 사용하고자 하는 곳에서 매번 new로 생성해서 사용했습니다. 이전에 배웠던 것처럼 로거를 모듈로 만들면 생성자에서 주입받을 수 있습니다. 먼저 LoggerModule을 만들고 AppModule에 가져옵니다.

import { Module } from '@nestjs/common';
import { MyLogger } from './my-logger.service';

@Module({
  provider: [MyLogger],
  exports: [MyLogger]
})
export class LoggerModule { }
import { LoggerModule } from './logging/Logger.module';

@Module({
  imports: [LoggerModule],
  ...
})
export class AppModule { }
import { MyLogger } from './logging/my-logger.service';

@Injectable()
export class AppService {
  constructor(private myLogger: MyLogger) { }
  
  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!';
  }
}

커스텀 로거를 전역으로 사용하기

커스텀 로거를 전역으로 사용하려면 main.ts에 지정해줘야 합니다. 이렇게 하면 서비스 부트스트래핑 과정에서도 커스텀 로거가 사용됩니다.

async function bootstrap() {
  const app = await NestFactory.create(AppModule);
  app.useLogger(app.get(MyLogger));
  await app.listen(3000)
}

외부 로거 사용하기

상용 프로젝트에는 커스텀 로거를 매우 정교하게 다듬어 사용해야 합니다. 하지만 Node.js에는 이미 훌륭한 로깅 라이브러리인 winston이 있습니다. 나아가 winston을 Nest의 모듈로 만들어놓은 nest-winston 패키지가 존재합니다.

'BackEnd > Nest.js' 카테고리의 다른 글

[Nest.js] 인터셉터  (0) 2023.05.31
[Nest.js] 예외 필터  (0) 2023.05.30
[Nest.js] JWT 인증/인가  (0) 2023.05.26
[Nest.js] 파이프와 유효성 검사  (0) 2023.05.16
[Nest.js] Config 패키지  (0) 2023.05.16