NestJS 공식문서 Logging

GGAE99·2023년 7월 18일
0

NestJS 공식 문서

목록 보기
20/33
post-thumbnail

Logger

Nest는 내장된 텍스트 기반 로거를 제공합니다. 이 로거는 애플리케이션 부트스트래핑 및 시스템 로깅과 같은 여러 상황에서 사용되며, @nestjs/common 패키지의 Logger 클래스를 통해 제공됩니다. 이 로깅 시스템의 동작을 완전히 제어할 수 있으며, 다음과 같은 기능을 포함합니다:

  • 로깅 전체 비활성화
  • 로그 수준 세부 정보 지정(예: 오류, 경고, 디버그 정보 표시 등)
  • 기본 로거에서 타임스탬프 재정의(예: ISO8601 표준을 사용한 날짜 형식)
  • 기본 로거 완전히 재정의
  • 기본 로거 확장을 통한 사용자 정의 로거 사용
  • 의존성 주입을 활용하여 애플리케이션 구성 및 테스트 단순화
  • Winston과 같은 Node.js 로깅 패키지를 사용하여 고급 로깅 기능 구현

고급 로깅 기능을 위해 Winston과 같은 Node.js 로깅 패키지를 사용하여 완전히 사용자 정의된 프로덕션 등급의 로깅 시스템을 구현할 수도 있습니다.

Basic customization

로깅을 비활성화하려면, NestFactory.create() 메서드의 두 번째 인수로 전달되는 (optional) Nest 애플리케이션 옵션 객체에서 logger 속성을 false로 설정하면 됩니다.

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

특정 로깅 수준을 활성화하려면, logger 속성을 표시할 로그 수준을 지정하는 문자열 배열로 설정하면 됩니다.

const app = await NestFactory.create(AppModule, {
  logger: ['error', 'warn'],
});
await app.listen(3000);

배열의 값으로 'log', 'error', 'warn', 'debug', 'verbose'를 조합하여 사용할 수 있습니다.

  • log: 기본적인 로그 메시지를 표시합니다. 애플리케이션의 일반적인 동작 또는 정보를 기록할 때 사용됩니다.

  • error: 오류와 예외 상황을 나타내는 로그 메시지를 표시합니다. 애플리케이션에서 예상치 못한 오류가 발생했거나 처리되지 않은 예외가 발생한 경우에 사용됩니다.

  • warn: 경고 메시지를 표시합니다. 잠재적인 문제를 나타내지만 심각한 오류로 간주되지는 않는 상황에 사용됩니다. 애플리케이션의 실행에는 영향을 주지 않을 수 있지만, 주의해야 할 사항이나 잠재적인 문제를 알려줄 때 사용됩니다.

  • debug: 디버그용 로그 메시지를 표시합니다. 개발 및 디버깅 목적으로 사용되며, 애플리케이션의 내부 상태나 변수 값을 출력하는 데 사용됩니다. 일반적으로 개발 환경에서만 활성화되며, 프로덕션 환경에서는 사용되지 않을 수 있습니다.

  • verbose: 매우 상세한 로그 메시지를 표시합니다. 주로 디버깅 및 세부 정보 추적 목적으로 사용됩니다. 로그의 규모가 크고 자세한 정보가 필요한 경우에 유용합니다. 프로덕션 환경에서는 주로 사용되지 않습니다.

Hint!
기본 로거의 메시지에서 색상을 비활성화하려면, NO_COLOR 환경 변수를 비어 있지 않은 문자열로 설정하면 됩니다.

Custom implementation

Nest에 사용되는 시스템 로깅을 위해 사용자 정의 로거 구현을 제공할 수 있습니다. 이를 위해 logger 속성의 값으로 LoggerService 인터페이스를 충족하는 객체를 설정하면 됩니다. 예를 들어, Nest가 내장된 전역 JavaScript console 객체(이는 LoggerService 인터페이스를 구현함)를 사용하도록 지정할 수 있습니다.

const app = await NestFactory.create(AppModule, {
  logger: console,
});
await app.listen(3000);
// Nest에서 console을 로거로 지정하면 Nest 애플리케이션의 로그가 console 객체를 통해 출력됩니다.

사용자 정의 로거를 구현하는 것은 간단합니다. 아래의 코드와 같이 LoggerService 인터페이스의 각 메서드를 구현하면 됩니다.

import { LoggerService } from '@nestjs/common';

export class MyLogger implements LoggerService {
  /**
   * Write a 'log' level log.
   */
  log(message: any, ...optionalParams: any[]) {}

  /**
   * Write an 'error' level log.
   */
  error(message: any, ...optionalParams: any[]) {}

  /**
   * Write a 'warn' level log.
   */
  warn(message: any, ...optionalParams: any[]) {}

  /**
   * Write a 'debug' level log.
   */
  debug?(message: any, ...optionalParams: any[]) {}

  /**
   * Write a 'verbose' level log.
   */
  verbose?(message: any, ...optionalParams: any[]) {}
}

그런 다음 Nest 애플리케이션 옵션 객체의 logger 속성을 MyLogger의 인스턴스로 제공하면 됩니다.

const app = await NestFactory.create(AppModule, {
  logger: new MyLogger(),
});
await app.listen(3000);

이 방법은 간단하지만, MyLogger 클래스에 대해 의존성 주입을 활용하지 않습니다. 이는 특히 테스트를 수행하는 데 일부 어려움을 일으킬 수 있으며, MyLogger의 재사용 가능성을 제한할 수 있습니다. 더 나은 솔루션을 위해 조금 이따 나오는 "Dependency injection" 섹션을 참조하십시오.

Extend built-in

내장 로거 확장
로그를 처음부터 작성하는 대신, 내장된 ConsoleLogger 클래스를 확장하고 기본 구현의 선택된 동작을 재정의함으로써 필요한 요구 사항을 충족시킬 수도 있습니다.

import { ConsoleLogger } from '@nestjs/common';

export class MyLogger extends ConsoleLogger {
  error(message: any, stack?: string, context?: string) {
    // add your tailored logic here
    super.error(...arguments);
  }
}

위와 같이 확장된 로거를 사용하여 기능 모듈에서 사용할 수 있습니다. 이에 대해서는 아래의 "Using the logger for application logging" 섹션에서 설명합니다.

시스템 로깅을 위해 Nest에 확장된 로거를 사용하려면, 애플리케이션 옵션 객체의 logger 속성을 통해 해당 인스턴스를 전달하면 됩니다(위의 "Custom implementation" 섹션에서 보여준 대로). 또는 아래의 "Dependency Injection" 섹션에서 보여준 기술을 사용할 수도 있습니다. 이렇게 하면 Nest가 기본적으로 기대하는 내장 기능을 활용할 수 있도록, 특정 로그 메서드 호출을 상위 (내장) 클래스로 위임하기 위해 샘플 코드에서 보여준 대로 super를 호출하는 것에 주의해야 합니다.

Dependency injection

더 고급 로깅 기능을 위해서는 의존성 주입(dependency injection)을 활용하는 것이 좋습니다. 예를 들어, 로거에 ConfigService를 주입하여 로거를 사용자가 정의할 수 있도록 할 수 있으며, 이 사용자 정의 로거를 다른 컨트롤러나 프로바이더에 주입할 수 있습니다. 사용자 정의 로거에 대한 의존성 주입을 활성화하려면, LoggerService를 구현하는 클래스를 생성하고 해당 클래스를 모듈의 프로바이더로 등록해야 합니다. 예를 들어, 다음과 같이 할 수 있습니다.

  1. LoggerService를 구현하는 MyLogger 클래스를 정의합니다. 이 클래스는 내장된 ConsoleLogger를 확장하거나 완전히 덮어쓰는 방식으로 구현합니다. LoggerService 인터페이스를 반드시 구현해야 합니다.
import { LoggerService } from '@nestjs/common';

export class MyLogger implements LoggerService {
  // Implement the methods defined in LoggerService
}
  1. LoggerModule을 생성하고 MyLogger(provider)를 해당 모듈에서 제공합니다.
import { Module } from '@nestjs/common';
import { MyLogger } from './my-logger.service';

@Module({
  providers: [MyLogger],
  exports: [MyLogger],
})
export class LoggerModule {}

이 구조를 사용하면 다른 모듈에서 사용하기 위해 사용자 정의 로거를 제공할 수 있습니다. MyLogger 클래스가 모듈의 일부이기 때문에 의존성 주입을 사용할 수 있습니다(예: ConfigService 주입). Nest에서 시스템 로깅(예: 부트스트래핑 및 오류 처리)에 사용하기 위해 이 사용자 정의 로거를 제공하기 위해 필요한 기술이 더 있습니다.

NestFactory.create()를 사용하여 애플리케이션을 인스턴스화할 때는 어떤 모듈의 컨텍스트 외부에서 이루어지므로 초기화의 일반적인 의존성 주입 단계에 참여하지 않습니다. 따라서 Nest가 MyLogger 클래스의 싱글톤 인스턴스를 인스턴스화하도록 하기 위해 적어도 하나의 애플리케이션 모듈이 LoggerModule을 import하도록 해야 합니다.

다음과 같이 Nest에게 MyLogger의 동일한 싱글톤 인스턴스를 사용하도록 지시할 수 있습니다.

const app = await NestFactory.create(AppModule, {
  bufferLogs: true,
});
app.useLogger(app.get(MyLogger));
await app.listen(3000);

위 예제에서는 bufferLogstrue로 설정하여 모든 로그가 커스텀 로거(MyLogger)가 첨부되고 애플리케이션 초기화 프로세스가 완료되거나 실패할 때까지 버퍼링되도록 합니다. 초기화 프로세스가 실패하면 Nest는 오류 메시지를 보고하기 위해 원래의 ConsoleLogger를 사용하게 됩니다. 또한, autoFlushLogsfalse로 설정하여 수동으로 로그를 플러시하는 것도 가능하며(기본값은 true), Logger#flush() 메서드를 사용하여 수동으로 플러시할 수 있습니다.

여기서는 NestApplication 인스턴스의 get() 메서드를 사용하여 MyLogger 객체의 싱글톤 인스턴스를 검색합니다. 이 기술은 사실상 Nest에서 로거 인스턴스를 "inject"하는 방법입니다. app.get() 호출은 MyLogger의 싱글톤 인스턴스를 검색하며, 해당 인스턴스가 먼저 다른 모듈에 주입되어야 한다는 가정하에 작동합니다.

MyLogger 프로바이더를 feature 클래스에 주입하여 Nest 시스템 로깅과 애플리케이션 로깅에서 일관된 로깅 동작을 보장할 수도 있습니다. 자세한 내용은 아래의 " Using the logger for application logging" 및 " Injecting a custom logger"을 참조하세요.

Using the logger for application logging

위의 여러 기술들을 결합하여 Nest 시스템 로깅과 우리 자신의 애플리케이션 이벤트/메시지 로깅 사이에서 일관된 동작과 포맷을 제공할 수 있습니다.

각각의 서비스에서 @nestjs/commonLogger 클래스를 인스턴스화하는 것이 좋은 방법입니다. Logger 생성자의 context 인수로 서비스 이름을 제공할 수 있습니다. 아래 예시와 같이 작성할 수 있습니다.

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

@Injectable()
class MyService {
  private readonly logger = new Logger(MyService.name);

  doSomething() {
    this.logger.log('Doing something...');
  }
}

기본 로거 구현에서는 context가 대괄호로 둘러싸인 형태로 출력됩니다. 예를 들어, 아래 예시에서는 NestFactorycontext로 사용되었습니다.

[Nest] 19096   - 12/08/2019, 7:12:59 AM   [NestFactory] Starting Nest application...

app.useLogger()를 통해 사용자 정의 로거를 제공하면 실제로 Nest에서 내부적으로 사용됩니다. 이는 코드가 구현에 구애받지 않으면서 app.useLogger()를 호출하여 기본 로거를 사용자 정의 로거로 간단하게 대체할 수 있다는 것을 의미합니다.

따라서 이전 섹션의 단계를 따르고 app.useLogger(app.get(MyLogger))를 호출한다면, MyService에서의 this.logger.log() 호출은 MyLogger 인스턴스의 log 메서드로 전환될 것입니다.

대부분의 경우에는 이 방법이 적합합니다. 하지만 추가적인 커스터마이즈(사용자 정의 메서드 추가 및 호출 등)가 필요하다면, 다음 섹션으로 넘어가시기 바랍니다.

Injecting a custom logger

먼저, 다음과 같이 내장 로거를 확장하여 시작할 수 있습니다. ConsoleLogger 클래스에 대해 구성 메타데이터로 scope 옵션을 제공하여 MyLogger의 각 피처 모듈에서 고유한 인스턴스를 보장하기 위해 transient 스코프를 지정합니다. 이 예시에서는 log(), warn() 등 개별 ConsoleLogger 메서드를 확장하지는 않았지만, 필요에 따라 확장할 수도 있습니다.

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

@Injectable({ scope: Scope.TRANSIENT })
export class MyLogger extends ConsoleLogger {
  customLog() {
    this.log('Please feed the cat!');
  }
}

다음으로, 다음과 같은 구조로 LoggerModule을 생성합니다.

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

@Module({
  providers: [MyLogger],
  exports: [MyLogger],
})
export class LoggerModule {}

그리고 이 LoggerModule을 피처 모듈에 import합니다. 기본 Logger를 확장했으므로 setContext 메서드를 사용할 수 있는 편리함이 있습니다. 따라서 다음과 같이 컨텍스트를 고려하는 사용자 정의 로거를 사용할 수 있습니다.

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

@Injectable()
export class CatsService {
  private readonly cats: Cat[] = [];

  constructor(private myLogger: MyLogger) {
    // transient 스코프로 인해 CatsService는 MyLogger의 고유한 인스턴스를 가지므로
    // 여기서 설정한 컨텍스트는 다른 서비스의 다른 인스턴스에 영향을 주지 않습니다.
    this.myLogger.setContext('CatsService');
  }

  findAll(): Cat[] {
    // 모든 기본 메서드를 호출할 수 있습니다.
    this.myLogger.warn('About to return cats!');
    // 그리고 사용자 정의 메서드를 호출할 수도 있습니다.
    this.myLogger.customLog();
    return this.cats;
  }
}

마지막으로, 다음과 같이 main.ts 파일에서 Nest에게 사용자 정의 로거의 인스턴스를 사용하도록 지시합니다. 물론 이 예시에서는 실제로 로거 동작을 사용자 정의하지 않았으므로 이 단계는 사실상 필요하지 않습니다. 그러나 log(), warn() 등의 메서드에 사용자 정의 로직을 추가하고 Nest가 동일한 구현을 사용하도록 하려는 경우에는 필요합니다.

참고: 이 예시에서는 bufferLogstrue로 설정하여 일부 초기화 오류 메시지를 놓치지 않도록 보장하고 있습니다. logger: false로 설정하는 대신 로거를 일시적으로 비활성화할 수도 있지만, logger: falseNestFactory.create에 제공하면 useLogger를 호출하기 전까지는 아무 로그도 기록되지 않으므로 일부 중요한 초기화 오류를 놓칠 수 있습니다. 기본 로거로 일부 초기 메시지가 기록되는 것이 문제가 되지 않는다면 logger: false 옵션을 생략할 수 있습니다.

Use external logger

실제 운영 환경에서는 고급 필터링, 포맷팅 및 중앙 집중식 로깅을 포함한 특정 로깅 요구 사항이 있을 수 있습니다. Nest의 내장 로거는 Nest 시스템 동작을 모니터링하는 데 사용되며, 개발 중에는 기능 모듈에서 기본적인 포맷팅된 텍스트 로깅에도 유용할 수 있습니다. 그러나 운영 환경의 애플리케이션은 종종 Winston과 같은 전용 로깅 모듈을 활용합니다. Node.js 애플리케이션과 마찬가지로 Nest에서 이러한 모듈을 완전히 활용할 수 있습니다.

질문 및 생각

  • 빌트 인 빌트 인 하는데 빌트 인이 뭐임

  • Winston

나의 정리

1개의 댓글

comment-user-thumbnail
2023년 7월 19일

좋은 글 감사합니다!

답글 달기