새소식

반응형
Back-end/NestJS

[NestJS] NestJS에서 로깅(Logging)하기 - 1 (전문적으로 로깅하기)

2024.01.26
  • -
반응형

이번 시간에는 서비스를 운영하면서 상당히 중요하게 작용하는 요소 중 하나인 로깅을 NestJS 어떻게 하면 잘 할 수 있는지에 대해 알아보는 시간을 갖도록 하겠습니다.

 

완벽한 로깅 시스템을 갖추면 버그가 발생하더라도 우리는 로그를 그저 읽고 해당 요청 동안 어떤 일이 발생했는지 완벽히 이해하고 로컬 서버 또는 DB 워크벤치에 무슨 일이 벌어지는지 정확히 시뮬레이션 할 수도 있습니다. 

 

Interceptor의 기본 개념을 이용하여 로깅을 하는 방법은 여기를 참고하세요.

 

1. 로깅 기초

1-1. Middleware + console.log로 로깅하기

요청 로깅

1. 미리 정의된 app.controller의 호출을 가로채는 logger.middleware를 다음과 같이 정의합니다.

import { Injectable, NestMiddleware, RequestMethod } from '@nestjs/common';
import { Request, Response, NextFunction } from 'express';
import { RouteInfo } from '@nestjs/common/interfaces';
import { request } from 'http';
@Injectable()
export class LoggerMiddleware implements NestMiddleware {
  use(req: Request, res: Response, next: NextFunction) {
    // 요청 로그를 얻어옵니다.
    console.log(`req:`, {
      headers: req.headers,
      body: req.body,
      originalUrl: req.originalUrl,
    });
    // 미들웨어 함수가 종료되어도 다음 과정을 이어갑니다. 
    if (next) {
      next();
    }
  }
}

 

2. app.module에서 LoggerMiddleware를 적용합니다.

// app.module.ts
...
export class AppModule implements NestModule {
  configure(consumer: MiddlewareConsumer) {
    consumer
      .apply(LoggerMiddleware)
      .forRoutes({ path: '*', method: RequestMethod.ALL });
  }
}

JSON body가 있는 요청이 어떻게 보일지 표시하기 위해서, app.controller를 POST 요청을 받도록 합니다.

 

이제 이를 테스트해봅시다. Postman이나 Curl을 통해 요청을 날립니다.

curl -X "POST" http://localhost:3000/ -H "access-token:abcd1234" \
-H "Content-Type: application/json" --data '{"id": 1234, "name":"user1"}'

 

 

다음은 결과 요청 로그입니다.

req: {
  headers: {
    host: 'localhost:3000',
    'user-agent': 'curl/7.81.0',
    accept: '*/*',
    'access-token': 'abcd1234',
    'content-type': 'application/json',
    'content-length': '28'
  },
  body: { id: 1234, name: 'user1' },
  originalUrl: '/'
}

 

 

응답 로깅

그렇다면 응답도 로깅되길 원하면 어떻게 해야할까요? 쓰기 가능한 stream(writable stream)으로 방출될 수 있기 때문에 몇가지 작업이 필요합니다.

 

  • steam이 시작할 때(res.write): 응답 body 버퍼 청크를 캡처하기 위해 스트림을 작성합니다.
  • stream이 종료할 때(res.end): 모든 청크를 JSON으로 연결하고 구문 분석합니다.

logger.middleware가 이 작업을 getResponseLog()에서 합니다.

 

const getResponseLog = (res: Response) => {
  const rawResponse = res.write;
  const rawResponseEnd = res.end;
  const chunkBuffers = [];
  res.write = (...chunks) => {
    const resArgs = [];
    for (let i = 0; i < chunks.length; i++) {
      resArgs[i] = chunks[i];
      if (!resArgs[i]) {
        res.once('drain', res.write);
        i--;
      }
    }
    if (resArgs[0]) {
      chunkBuffers.push(Buffer.from(resArgs[0]));
    }
    return rawResponse.apply(res, resArgs);
  };
  console.log(`Done writing, beginning res.end`);
  res.end = (...chunk) => {
    const resArgs = [];
    for (let i = 0; i < chunk.length; i++) {
      resArgs[i] = chunk[i];
    }
    if (resArgs[0]) {
      chunkBuffers.push(Buffer.from(resArgs[0]));
    }
    const body = Buffer.concat(chunkBuffers).toString('utf8');
    res.setHeader('origin', 'restjs-req-res-logging-repo');
     const responseLog = {
      response: {
        statusCode: res.statusCode,
        body: JSON.parse(body) || body || {},
        // Returns a shallow copy of the current outgoing headers
        headers: res.getHeaders(),
      },
    };
    console.log('res: ', responseLog);
    rawResponseEnd.apply(res, resArgs);
    return responseLog as unknown as Response;
  };
};

 

  1. res.write을 객체 모드(object mode)로 수행합니다. 이는 버퍼 청크rest parameter("...")로 받습니다. 객체 모드를 사용하면 res.write 인코딩 인수를 지정할 필요를 없앱니다.
  2. res.write는 모든 청크를 살펴보고 resArgs[]에 저장합니다. 청크가 !resArgs[i]에 의해 결정된 대로 버퍼가 가득 차서 에러가 발생하면 에처 청크에서 스트림을 배출하고 다시 시작합니다.
    첫 번째 청크(chunks[0])가 중요한데, 여기에는 res.body의 버퍼가 들어있습니다. 이를 res.write 외부의 배열(chunkBuffers[])에 저장합니다.
  3. 맨 마지막에 apply()를 통해 rawResponse의 인수로 resArgs[]를 전달합니다. 이는 res.write에 의해 반환된 output boolean을 결장합니다.
  4. res.write가 완료된 상태에서 res.end로 스트림을 종료합니다. 이것도 객체 모드에서 실행하여 인코딩 지정을 생략합니다.
  5. res.write과 마찬가지로 모든 청크를 검토하여 자체 resArgs[]에 저장합니다. 예외는 동일한 외부 배열에 저장된 첫 번째 청크(chunks[0]) 입니다.
  6. 모든 chunk[0]를 처리한 후, 이들을 연결하고 utf8로 인코딩합니다. JSON.parse를 사용하여 파싱하면 응답 객체가 표시됩니다. 이를 responseLog와 함께 포함합니다.
  7. 마지막으로 apply()를 통해 resArgs[]를 rawResponse 인수로 전달하여 종료 이벤트를 발생시킵니다. 이렇게 하면 서비스 응답이 스트림이 종료되기를 기다리는 동안 중단되는 것을 방지할 수 있습니다.

앞서 보낸 Curl과 동일한 요청을 보내고 결과 로그를 확인해보면 다음과 같습니다.

req: {
  headers: {
    host: 'localhost:3000',
    'user-agent': 'curl/7.81.0',
    accept: '*/*',
    'access-token': 'abcd1234',
    'content-type': 'application/json',
    'content-length': '28'
  },
  body: { id: 1234, name: 'user1' },
  originalUrl: '/'
}
...
res:  {
  response: {
    statusCode: 201,
    body: { message: 'Hello World!' },
    headers: [Object: null prototype] {
      'x-powered-by': 'Express',
      'content-type': 'application/json; charset=utf-8',
      'content-length': '26',
      etag: 'W/"1a-iEQ9RXvkycqsT4vWvcdHrxZT8OE"',
      // Declared as a custom header using res.setHeader()   
      origin: 'restjs-req-res-logging-repo'
    }
  }

 

 

1-2. Middleware + Logger로 로깅하기

NestJS에는 애플리케이션 부팅 과정 및 캐치된 exception 표시와 같은 여러 사오항에서 사용되는 텍스트 기반 로거가 내장되어 있습니다. 이 기능은 @nestjs/common 패키지의 Logger 클래스를 통해 제공됩니다. 

 

앞서 console.log로 로깅을 하다보면 날짜도 찍기 힘들고 상황에 맞는 로그를 출력하여도 가독성이 좋지 못해 보통 Logger를 통해 로깅을 많이 하곤 합니다.

 

또한 기본 제공 Logger를 사용하거나 custom logger를 구현하여 애플리케이션 수준의 이벤트와 메세지를 로깅할 수도 있습니다. 더 고도화된 로깅 기능을 사용하려면 Winston과 같은 Node.js 로깅 패키지를 사용하여 한층 더 그 기능을 업그레이드 시킬 수 있습니다. (winston은 아래에서 다루게 됩니다.)

 

main.ts

먼저 main.ts에서 애플리케이션을 부팅할 때 logger에 대한 설정을 지정할 수 있습니다.

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

특정 로깅 level만을 보여주기 위해 위와 같이 지정할 수 있으며 'log', 'fatal', 'error', 'warn', 'debug', verbose' 등의 기능이 있습니다. 이러한 기능들은 색깔로써 구분되어 로깅됩니다.

 

Logger 속성 값을 LoggerService 인터페이스를 충족하는 객체로 설정하여 시스템 로깅에 Nest에서 사용할 Custom Logger를 구현할 수 있습니다.

 

예를 들어, Nest에 내장된 전역 자바스크립트 Console 객체(LoggerService 인터페이스를 구현하는)를 사용하도록 할 수 잇습니다.

// main.ts
...
const app = await NestFactory.create(AppModule, {
  logger: console,
});
await app.listen(3000);

 

사용자 정의 Logger를 구현하는 것은 간단합니다. 아래와 같이 로거 서비스 인터페이스의 각 방법을 구현하기만 하면 됩니다.

// logger.service.ts
import { LoggerService } from '@nestjs/common';

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

  /**
   * Write a 'fatal' level log.
   */
  fatal(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[]) {}
}

 

그러면 MyLogger의 인스턴스를 아래와 같이 Nest 애플리케이션 options 객체의 logger 속성을 통해 공급할 수 있게됩니다.

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

 

이 방식은 간단하기 하지만 MyLogger 클래스에 의존성 주입을 사용하지 않습니다. 의존성 문제는 앞선 포스팅들에서 다루었 듯이 다소 문제를 일으킬 수 있기 때문에 이를 해결하기 위해선 모듈을 활용해 주어야 합니다.

 

그 전에 처음부터 이렇게 Logger를 작성하지 않고 내장된 Console Logger 클래스를 확장하고 기본 구현의 선택된 동작을 우리가 다시 정의함으로써 더 편리하게 할 수 있습니다.

 

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

export class MyLogger extends ConsoleLogger {
  error(message: any, stack?: string, context?: string) {
    // 여기에 맞춤형 로직을 작성합니다.
    super.error(...arguments);
  }
}

 

 

앞선 의존성 주입문제를 해결하기 위해 LoggerService를 구현하는 클래스를 만들어 LoggerModule에 provider로 등록하기만 하면 됩니다.

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

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

 

또한 이 custom logger를 제공하기 위해 아래와 같이 해주어야 합니다. 

const app = await NestFactory.create(AppModule, {
  bufferLogs: true,
});
app.useLogger(app.get(MyLogger));
await app.listen(3000);
  • bufferLogs를 true로 해줌으로써 모든 log가 custom logger가 생성되고 애플리케이션 초기화 과정이 성공하거나 실패할 때까지 버퍼링되는 것을 보장해줍니다.
  • NestApplication 인스턴스의 get() 메소드를 사용하여 MyLogger 객체의 싱글톤 객체를 가져올 수 있습니다. 

 

http request을 위한 logger

http 요청을 로깅하기 위해서는 모든 라우트 핸들에 앞서 동작하는 middleware를 통해 구현할 수 있습니다.

 

import { Injectable, Logger, NestMiddleware } from '@nestjs/common';
import { Request, Response, NextFunction } from 'express';

@Injectable()
export class LoggerMiddleware implements NestMiddleware {
  private logger = new Logger('HTTP');

  use(request: Request, response: Response, next: NextFunction): void {
    const { ip, method, originalUrl } = request;
    const userAgent = request.get('user-agent') || '';

    response.on('finish', () => {
      const { statusCode } = response;
      const contentLength = response.get('content-length');

      this.logger.log(
        `${method} ${originalUrl} ${statusCode} ${contentLength} - ${userAgent} ${ip}`,
      );
    });

    next();
  }
}

 

 

이후 최상위 모듈에 이 미들웨어를 적용해줍니다.

// app.module.ts
...
export class AppModule implements NestModule {
  configure(consumer: MiddlewareConsumer) {
    consumer.apply(LoggerMiddleware).forRoutes('*');
  }
}
  • forRoutes()메소드의 인자로 '*'을 넣음으로써 모든 라우트에 대해 로그를 찍도록 하였습니다.

 

1-3. winston + Interceptor로 로깅하기

@nestjs/common에서 제공하는 Logger 대신 winston을 이용하면 로그 파일을 저장하거나 중요한 로그는 db에 처리하는 작업을 진행할 수 있습니다.

 

설치

npm i nest-winston winston winston winston-daily-rotate-file

 

 

설치가 완료되면, winston 설정과 인스턴스를 생성하는 부분을 아래와 같이 작성합니다.

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

const env = process.env.NODE_ENV;
const logDir = __dirname + '/../../logs'; // log 파일을 관리할 폴더

// rfc5424를 따르는 winston만의 log level
// error: 0, warn: 1, info: 2, http: 3, verbose: 4, debug: 5, silly: 6
export const winstonLogger = WinstonModule.createLogger({
  transports: [
    new winston.transports.Console({
      level: env === 'production' ? 'http' : 'silly',
      // production 환경이라면 http, 개발환경이라면 모든 단계를 로그
      format:
        env === 'production'
      // production 환경은 자원을 아끼기 위해 simple 포맷 사용
          ? winston.format.simple() 
          : winston.format.combine(
              winston.format.timestamp(),
              utilities.format.nestLike('프로젝트이름', {
                prettyPrint: true, // nest에서 제공하는 옵션. 로그 가독성을 높여줌
              }),
            ),
    }),

    // info, warn, error 로그는 파일로 관리
    new winstonDaily(dailyOptions('info')),
    new winstonDaily(dailyOptions('warn')),
    new winstonDaily(dailyOptions('error')),
  ],
});

const dailyOptions = (level: string) => {
  return {
    level,
    datePattern: 'YYYY-MM-DD',
    dirname: logDir + `/${level}`,
    filename: `%DATE%.${level}.log`,
    maxFiles: 30, //30일치 로그파일 저장
    zippedArchive: true, // 로그가 쌓이면 압축하여 관리
  };
};

 

먼저 main.ts 파일에 winston을 사용하겠다고 등록을 해주어야 합니다.

// main.ts
import { NestFactory } from '@nestjs/core';
import { winstonLogger } from './utils/winston.util';

async function bootstrap() {
  const app = await NestFactory.create(AppModule, {
    logger: winstonLogger,
  });

  await app.listen(process.env.PORT || 3000);
}
bootstrap();

 

서버를 시작하면 winstonDaily 라이브러리로 인해 자동으로 logs 폴더가 생성되는 것을 확인할 수 있습니다.

 

다음엔 

import { Logger, MiddlewareConsumer, Module, NestModule } from '@nestjs/common';
import { EnvModule } from './env.module';

import { LoggerMiddleware } from './middlewares/logger.middleware';

export class AppModule implements NestModule {
  configure(consumer: MiddlewareConsumer) {
    consumer.apply(LoggerMiddleware).forRoutes('*');
  }
}

 

 

 

 

2. 로깅 심화 개념

2-1. 로깅으로 봐야 하는 것들

로깅을 함으로써 봐야 하는 것들에는 다음과 같은 것들이 있을 수 있습니다.

  • Request input: 메소드, URL, body, headers - IP, user agent 등의 요청 입력입니다.
  • 각 데이터베이스 쿼리 & 반환된 결과 데이터: 워크벤치에 붙여넣어 실행할 수 있는 SQL 쿼리와 같은 것들입니다.
  • 각 외부 API 요청과 응답: 요청에는 [메소드, URL, 헤더, Body]가 포함되고, 응답에는 [status, body] 등이 포함됩니다.
  • Response output: status, body, headers와 같은 것들이 있습니다.

 

2-2. Logger class

각 API 호출이 자신만의 로그를 저장하기 원하기 때문에 Logger 클래스에는 @Injectable({ scope: Scope.REQUEST }) 이 있으며, 이는 NestJS가 각 API 호출마다 해당 클래스의 인스턴스를 생성하고 해당 인스턴스가 요청 객체에 대한 접근권을 가지게 됨을 의미합니다.

 

보통 해당 클래스에 대한 로그를 배열에 저장하며(각 로그가 시작한 timestamp와 함께), 2가지 경우에 클라우드로 전송합니다.

  • 응답이 성공적으로 도달하였을때
  • exception filter가 에러를 캐치했을 때

이러한 방식이 각 로그를 클라우드에 각각 보내는 것보다 효율적이라고 합니다.

 

2-3. Logger cloud(with monotoring)

구글에서 제공하는 "Google cloud logging"이라는 로그 시스템이 존재합니다. 해당 서비스에서는 한 달에 무료로 50GB의 로그 저장 공간을 제공합니다. 그리고 첫 달에는 추가 GB당 0.5달러이며, 원하는 경우 더 많은 기간 사용하고 싶다면 월 0.1GB당 0.5달러의 비용이 듭니다.

 

해당 시스템에서는 로그의 어떠한 문자열도 검색하는 훌륭한 도구를 갖췄고, 500이나 501에러가 발생한다거나 메모리 문제가 발견되는 것과 같이 특정 조건 발생 시에, 이메일이나 푸시 알람을 받을 수도 있습니다.

 

만약 서버에 대용량 트래픽을 다뤄야 하고 비용을 절약하고 싶다면, 로그를 exception filter에서만 보내고 다른 모든 성공한 요청에서 100개 중 1개만 무작위로 전송하는 로직이나 이와 비슷한 로직을 구축할 수도 있습니다.

 

2-4. Error reporting

에러 보고서에 대해서도 "Google error reporting" 툴이 존재합니다. 코드 상에서 해당 툴로 error reporting을 보낼 수 있고, 처음 오류가 발생하면 이메일을 받게 됩니다.(각 에려 타입 당 딱 한 번)

 

얼마나 많이 에러가 발생했는지 확인하고 각 에러를 살펴볼 수 있는 대시보드가 존재합니다. 그리고 이러한 에러를 knowledge 또는 solved 상태로 표시하여 solved로 표시한 경우 나중에 다시 그 에러가 발생했을 때 새로운 이메일을 받을 수 있습니다.

 

2-5. Request-Response Interceptor

Nest의 Interceptor는 middleware와는 다르게 request와 response를 가로채는 데 사용됩니다. 라우터가 요청을 처리하기 전에, 그리고 응답이 호출된 곳으로 다시 전송되기 직전에 실행될 코드를 구현할 수 있는 것입니다.

 

같은 방식으로 외부 API 요청을 전송하는 서비스를 구축하여 요청 매개변수와 response status 및 데이터를 자동 로깅할 수도 있습니다. 그러한 서비스는 Google 지도나 다른 서비스와 같은 외부 API를 필요로할 때 굉장히 유용합니다.

 

2-6. TypeORM 문제 로그

이번엔 굉장히 주된 문제를 다뤄볼텐데요.

 

바로 TypeORM의 쿼리 결과를 자동 로그 찍는 것입니다.

 

우리는 "TypeORMLogger" 인터페이스를 구현하여 TypeORM으로 우리가 만든 로거를 주입할 수 있습니다. 하지만 문제는 우리가 모~든 요청 로그를 한 곳에 동기적으로 저장하기 위해 필요한 request scope를 주입한다는 점인데요.

이는 TypeORM 모듈이 전역 singleton service이기 때문이며, 우리는 이 서비스를 request scope과 결합할 수 없습니다.

 

TypeORM Logger로서 request scope을 가진 logger 서비스를 주입하려할 때, 해당 요청 객체에 대한 접근을 주지만 서버가 받았던 첫 요청 객체에 대한 접근만 가질 수 있습니다.

 

이를 해결하려면

이를 해결하기 위해선 Nest의 공식문서 중 interceptor 부분에서 볼 수 있는 AOP 기법이 사용될 수 있습니다. 해당 아이디어는 어떠한 함수를 감싸서 각 호출 전/후로 코드의 일부만을 실행하는 것입니다.

 

Node 서버는 동기적으로 작동하므로 항상 실행되는 코드는 하나뿐이며(예를 들어 자바나 파이썬 서버와 같은 스레드와 병렬적이지 않음), 데이터베이스에서 데이터를 읽는 것과 같이 OS에서 명령을 실행해야 할 때마다 JS 엔진은 이벤트 루프에서 해당 명령을 기다리는 다른 작업을 자유롭게 처리할 수 있습니다.

 

데이터베이스로부터 응답이 돌아오면 이벤트 루프에 등록된 다음, 코드가 현재 작업을 완료합니다.

 

예시 코드

// log-queries.decorator.ts
import {Repository} from "typeorm";
import {LoggingService} from "../app/shared-services/logging.service";

type IClass<T = any> = new (...args: any[]) => T;

type ILoggerAndRepository = { logger: LoggingService, repository: Repository<any> };

/* This magic decorator works on service (class) with Repository and LoggingService.
It binds the Logging service (that have the request scope) to the TypeORM repository (singleton)
And it did it before every call of every function that exists in that service.
*/
const classNames = []
export function LogTypeOrmCycle() {
  return function <T extends { new(...args: any[]): {} }>(oConstructor: T) {
    return class extends oConstructor {
      constructor(...args: any[]) {
        super(...args);
        const {repository, logger} = getLoggerAndRepositoryFromClass(this);
        if (repository && logger && !classNames.includes(oConstructor.name)) {
          classNames.push(oConstructor.name)
          interceptServiceMethodsWithLoggerBeforeAndAfter(oConstructor);
        }
      }
    };
  };
}

function interceptServiceMethodsWithLoggerBeforeAndAfter(cls: IClass) {
  Object.getOwnPropertyNames(cls.prototype).forEach(key => {
    const prop = cls.prototype[key];
    if (typeof prop === "function" && key !== "constructor") {
      cls.prototype[key] = new Proxy(prop, {
        apply: (target, thisArg, argumentsList) => {
          const {logger, repository} = getLoggerAndRepositoryFromClass(thisArg);
          // @ts-ignore
          repository.manager.connection.logger = logger;
          const context = `${cls.name}.${target.name}`;
          logger.setContext(context);
          const argumentNames = target.toString()?.split(") {")[0]?.split(`${target.name}(`)?.[1]?.split(",");
          if (argumentNames) {
            const argumentValues = argumentsList.map((arg, i) => typeof arg === "object" ? arg?.constructor?.name : arg)
            logger.log(`Arguments: (${argumentNames.map((name, i) => `${name}: ${argumentValues[i]}`).join(",")})`)
          }
          const newFunc: Promise<any> = Reflect.apply(target, thisArg, argumentsList);
          if (newFunc.then)
            return newFunc.then(results => logger.logDbResults(results, context));
          else
            return newFunc;
        }
      });
    }
  });
}

function getLoggerAndRepositoryFromClass(cls: IClass | {}): ILoggerAndRepository {
  return Object.getOwnPropertyNames(cls).reduce((state, key) => {
    if (cls[key] instanceof Repository)
      state.repository = cls[key];
    else if (cls[key] instanceof LoggingService)
      state.logger = cls[key];
    return state;
  }, {logger: null, repository: null});
}

 

// pages.service.ts
import { InjectRepository } from "@nestjs/typeorm";
import { PageEntity } from "./pages.entity";
import { LoggingService } from "../../shared-services/logging.service";
import { Repository } from "typeorm";
import { Injectable } from "@nestjs/common";
import { LogTypeOrmCycle } from "../../decorators/log-queries.decorator";

@LogTypeOrmCycle()
@Injectable()
export class PagesService {
  constructor(
    @InjectRepository(PageEntity)
    private repository: Repository<PageEntity>,
    private logger: LoggingService
  ) {
  }

  async getPage(pageId: number): Promise<PageEntity> {
    const pageEntity = await this.repository.findOne({ where: { id: pageId, is_deleted: false } });
    return pageEntity;
  }

  async getPageByFacebookPageId(facebookPageId: number): Promise<PageEntity> {
    const pageEntity = await this.repository.findOne({
      where: {
        facebook_page_id: facebookPageId,
        is_deleted: false
      }
    });
    return pageEntity;
  }
}

 

@LogTypeOrmCycle 데코레이터가 작동하려면 클래스 내부에서 사용하지는 않지만 클래스의 생성자 부분에 LogginService를 주입해야 합니다. (데코레이터에서만 사용됩니다.)

 

마치며

로깅을 하는 방식은 굉장히 다양하며, 상황에 맞게 하는 것이 중요한 것 같습니다. 또한 운영 단계의 애플리케이션은 구체적인 로깅을 해야하는 경우가 있습니다(고도화된 filtering, formatting, 중앙화된 로깅 등...). nest의 내장 Logger는 Nest 시스템 행동을 모니터하는 데 사용되고, 개발 중인 기능 모듈의 가장 기본 형태의 텍스트 로깅을 하는 데에는 유용할 수 있지만 실제 운영 단계에서는 Winston과 같은 전용 로깅 모듈을 활용하는 경우가 많다고 합니다.

 

 

읽어주셔서 감사합니다.

 

반응형
Contents

포스팅 주소를 복사했습니다

이 글이 도움이 되었다면 공감 부탁드립니다.