Skip to main content

Command Palette

Search for a command to run...

[ 살펴보기 ] NestJS - Logger

Published
4 min readView as Markdown
[ 살펴보기 ] NestJS - Logger
C

A developer living in Busan, Korea

NestJS는 default로 Logger라는 내장 class를 통해 기본적인 log기능을 제공한다. NestJS application을 실행할 때 terminal에 나오는 log가 Logger class를 통해 제공되는 log다.

기본적으로 제공되는 Logger를 활용할 수도 있고 혹은 다른 third-party log library나 custom logger를 적용할 수도 있다.

내장 Logger을 disable하거나 log level을 수정하고자 한다면 main.ts 파일의 NestFactroy.create method에 log option을 수정해준다. 다음은 error, warn level의 log만 출력하도록 수정한 예제다.

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

logger에 적용할 수 있는 log level은 다음과 같다. 'log', 'fatal', 'error', 'warn', 'debug', 'verbose'

Custom Logger

만약 log내용을 파일로 저장하거나 DB에 저장하는 등 log에 대한 추가 작업을 하고 싶다면 Custom Logger를 만들어 설정할 수 있다. 다음은 NestJS의 default ConsoleLogger를 확장해서 만든 Custom Logger다. log method를 override하고 log level에서 발생하는 log를 file에 작성하는 기능을 추가하였다.

import { ConsoleLogger } from '@nestjs/common';
import * as fs from 'fs';

export class CustomLogger extends ConsoleLogger {
  override log(message: string, stack?: string, context?: string) {
    super.log(message, stack, context);
    this.logToFile(message);
  }

  logToFile(message: string) {
    const logStream = fs.createWriteStream('./nestjs.logs.txt', {
      flags: 'a',
    });
    logStream.write(`[LOG] ${message}\n`);
    logStream.end();
  }
}
...
import { CustomLogger } from 'src/auth/logger/custom.logger';

async function bootstrap() {
  const app = await NestFactory.create(AppModule, {
    logger: new CustomLogger(),
  });
  await app.listen(4000);
}
bootstrap();

만약 위에서 정의한 custom logger를 application system logging뿐만 아니라 필요한 module에 inject하여 application 전체에서 custom logger를 사용하고 싶다면 custom logger를 위한 새로운 module을 생성해야 한다.

import { Module } from '@nestjs/common';
import { CustomLogger } from './logger.service';

@Module({
  providers: [CustomLogger],
  exports: [CustomLogger],
})
export class LoggerModule {}
import { Injectable } from '@nestjs/common';
import { ConsoleLogger } from '@nestjs/common';
import * as fs from 'fs';

@Injectable()
export class CustomLogger extends ConsoleLogger {
  override log(message: string, stack?: string, context?: string) {
    super.log(message, stack, context);
    this.logToFile(message);
  }

  logToFile(message: string) {
    const logStream = fs.createWriteStream('./nestjs.logs.txt', {
      flags: 'a',
    });
    logStream.write(`[LOG] ${message}\n`);
    logStream.end();
  }
}

CustomLogger를 다른 module에 inject할 수 있어야 하기에 CustomLogger를 Injectable로 만들고 LoggerModule의 provider로 제공해준다.

그리고 NestJS system logging 또한 custom logger를 통해 실행될 수 있도록 main.ts 파일을 다음과 같이 변경해준다.

import { CustomLogger } from 'src/logger/logger.service';

async function bootstrap() {
  const app = await NestFactory.create(AppModule, {
    bufferLogs: true,
  });
  app.useLogger(app.get(CustomLogger));
  await app.listen(4000);
}
bootstrap();

여기서 주의할 점은 위에서 생성한 LoggerModule은 application의 다른 모듈에서 import가 되어야 한다.

bufferLogs을 true로 설정하면 custom logger가 적용되기 전까지 log를 buffer에 저장해 두었다가 다른 모듈에서 LoggerModule이 import가 되어 CustomLogger의 instance가 생성되면 NestJS는 생성된 logger instance를 통해 log를 처리한다.

import { LoggerModule } from './logger/logger.module';

@Module({
  imports: [
    ...
    LoggerModule, // LoggerModule을 import 해야한다.
  ],
  controllers: [AppController],
  providers: [AppService],
})
export class AppModule {}

이제 다른 application module에 custom logger를 inject해서 사용해보자.

@Module({
  imports: [LoggerModule],
  ...
})
export class UsersModule {}
...
import { CustomLogger } from 'src/logger/logger.service';

@Controller('users')
export class UsersController {
  constructor(
    private readonly usersService: UsersService,
    private readonly customLogger: CustomLogger,
  ) {}

  @Get()
  findAll() {
    this.customLogger.log(' request : user findAll ');
    return this.usersService.findAll();
  }
}

위의 예제는 user module에 custom logger를 제공하여 controller에서 request가 발생했을 때 custom logger의 log method를 호출하고 있다. /users로 request를 발생시키면 로그 파일의 마지막 부분에 “request : user findAll”이라는 text가 추가된 것을 확인할 수 있다.

Winston

NestJS의 내장 logger를 사용하지 않고 다른 logging library를 사용할 수도 있다. logging library 중 하나인 winston을 적용하는 법을 살펴보자.

Library 적용을 위해 winston과 winston을 Nest 모듈로 사용할 수 있는 nest-winston을 설치한다.

npm install nest-winston winston

그리고 App module에 WinstonModule을 import 해준다.

....
import { WinstonModule } from 'nest-winston';

@Module({
  imports: [
    ...
    WinstonModule.forRoot({}),
  ],
  controllers: [AppController],
  providers: [AppService],
})
export class AppModule {}

WinstonModule의 forRoot method를 통해 원하는 option을 설정할 수 있다.

위와 같이 App module에 WinstonModule을 import 했다면 이제 다른 곳에서 winston logger를 바로 inject해서 사용할 수 있다.

...
import {
  ...
  Inject,
} from '@nestjs/common';
import { WINSTON_MODULE_NEST_PROVIDER } from 'nest-winston';
import { Logger } from 'winston';

@Controller('users')
export class UsersController {
  constructor(
    private readonly usersService: UsersService,
    @Inject(WINSTON_MODULE_NEST_PROVIDER) private readonly logger: Logger,
  ) {}

  @Get()
  findAll() {
    this.logger.warn('warning log'); // winston logger 사용
    return this.usersService.findAll();
  }

}

Nest Logger 대체하기

Nest 내장 logger를 winston logger로 변경하고자 한다면 main.ts를 다음과 같이 변경한다.

...
import { WinstonModule } from 'nest-winston';
import * as winston from 'winston';

async function bootstrap() {

  const app = await NestFactory.create(AppModule, {
    logger: WinstonModule.createLogger({
      transports: [
        new winston.transports.Console({
          format: winston.format.combine(
            winston.format.timestamp(),
            winston.format.prettyPrint(),
            winston.format.colorize(),
            winston.format.printf(({ context, level, message, timestamp }) => {
              return `${timestamp} [${context || 'App'}] ${level}: ${message}`;
            }),
          ),
        }),
        new winston.transports.File({
          filename: 'logs/error.log',
          level: 'error',
        }),
      ],
    }),
  });

  await app.listen(4000);
}
bootstrap();

위의 예제에서 winston이 log를 출력하는 format을 변경하고 error level의 log가 발생하면 logs/error.log 파일에 log 내용을 저장하도록 수정하였다.

이제 App module에선 import했던 WinstonModule은 삭제하고 대신 nestjs/common에서 export하는 Logger를 provider에 제공해준다.

...
import {
  ...
  Logger
} from '@nestjs/common';

@Module({
  imports: [
    ...
  ],
  controllers: [AppController],
  providers: [Logger, AppService],
})
export class AppModule {}

그리고 필요한 곳에 logger를 inject하여 사용할 수 있다. 이때도 nestjs/common에서 export하는 Logger를 통해 inject 해준다.

import { WINSTON_MODULE_NEST_PROVIDER } from 'nest-winston';
import {  
  ...
  Logger,
} from '@nestjs/common';


@Controller('users')
export class UsersController {
  constructor(
    private readonly usersService: UsersService,
    private readonly logger: Logger,
  ) {}

  @Get()
  findAll() {
    this.logger.error(' test error ');
    return this.usersService.findAll();
  }

}

More from this blog

[ 살펴보기 ] TypeORM - Transactions, Migration

Transation Database 종류에 따라 detail한 부분은 차이점이 조금씩 있겠지만 각 sql statement는 개별적인 transaction block을 통해 실행되며 Database 설정에 따라 sql statement의 실행 결과가 자동으로 commit되어 영구히 적용되거나 commit을 직접 실행하기 전까지는 영구히 적용되지 않을 수 있다. 대부분의 경우 default로 sql statement 실행 결과가 자동으로 comm...

Feb 9, 20256 min read
[ 살펴보기 ] TypeORM - Transactions, Migration

Dev Diary

184 posts