Nest 로깅 옵션을 조절하면 다음과 같은 로깅 시스템의 동작을 제어할 수 있다.
내장 로거 인스턴스를 직접 생성하여 사용할 수 있다.
import { Injectable, Logger } from '@nestjs/common';
@Injectable()
export class AppService {
// 로거 인스턴스 생성 시 클래스명을 컨텍스트로 설정하여 로그 메시지 앞에 클래스명이 함께 출력되도록 함.
private readonly logger = new Logger(AppService.name);
getHello(): string {
this.logger.error('this is error');
this.logger.warn('this is warn');
this.logger.log('this is log');
this.logger.verbose('this is verbose');
this.logger.debug('this is debug');
return 'Hello World!';
}
}
아래와 같이 logger 옵션을 false로 하면 로그가 출력되지 않는다.
...
async function bootstrap() {
//const app = await NestFactory.create(AppModule);
// 로그 비활성화
const app = await NestFactory.create(AppModule, {
logger: false,
});
await app.listen(3000);
}
...
프로덕션 환경에서는 debug 로그가 남지 않도록 한다. 이를 동적으로 지정한다.
async function bootstrap() {
//const app = await NestFactory.create(AppModule);
// 로그 비활성화
const app = await NestFactory.create(AppModule, {
logger:
process.env.NODE_ENV === "prod" ? ["error", "warn", "log"] : ["debug"],
});
await app.listen(3000);
}
로그를 저장하는 기능을 내장 로거는 제공하지 않는다. 이를 위해서는 커스텀 로거를 만들어야 한다.
커스텀 로거는 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;
}
커스텀 MyLogger를 만들어보자.
import { LoggerService } from '@nestjs/common';
export class MyloggerService implements LoggerService {
debug(message: any, ...optionalParams: any[]): any {
console.log(message);
}
error(message: any, ...optionalParams: any[]): any {
console.log(message);
this.doSomething();
}
private doSomething() {
// DB 저장과 같은 부가 로직 추가
}
log(message: any, ...optionalParams: any[]): any {
console.log(message);
}
verbose(message: any, ...optionalParams: any[]): any {
console.log(message);
}
warn(message: any, ...optionalParams: any[]): any {
console.log(message);
}
}
로거를 매번 생성해서 사용하는 것이 아니라 모듈로 만들어서 생성자에서 주입바아 사용한다.
먼저 LoggerModule을 만들고 AppModule에 가지고 온다.
import { Module } from "@nestjs/common";
import { MyloggerService } from "./mylogger.service";
@Module({
providers: [MyloggerService],
exports: [MyloggerService],
})
export class LoggerModule {}
...
import { LoggerModule } from './logging/logger.module';
@Module({
imports: [LoggerModule],
...
})
export class AppModule {}
프로바이더로 MyLogger를 주입받아 사용한다.
import { Injectable } from '@nestjs/common';
import { MyloggerService } from './logging/mylogger.service';
@Injectable()
export class AppService {
constructor(private myLogger: MyloggerService) {}
getHello(): string {
this.myLogger.error('test');
return 'Hello World!';
}
}
import { NestFactory } from "@nestjs/core";
import { AppModule } from "./app.module";
import { MyloggerService } from "./logging/mylogger.service";
async function bootstrap() {
const app = await NestFactory.create(AppModule);
app.useLogger(app.get(MyloggerService));
await app.listen(3000);
}
bootstrap();
nodejs 의 winston 을 nest 용으로 만든 패키지인 nest-winston을 사용한다.
nest-winston 을 설치한다.
$ npm i nest-winston winston
AppModule에 WinstonModule을 import 한다. 이때 winston 옵션을 줄 수 있다.
...
import { WinstonModule, utilities } from 'nest-winston';
import * as winston from 'winston';
...
@Module({
imports: [
...
WinstonModule.forRoot({
transports: [
new winston.transports.Console({
level: process.env.NODE_ENV === 'production' ? 'info' : 'silly',
format: winston.format.combine(
winston.format.timestamp(), // 로그 시각 표시
utilities.format.nestLike('MyApp', { // 어디에서 로그를 남겼는지 구분하는 appName('MyApp') 설정
prettyPrint: true,
}),
),
}),
],
}),
...
winston이 지원하는 로그 레벨은 다음과 같다. 설정된 로그 레벨보다 레벨이 높은 로그는 함께 출력된다.
{
error: 0,
warn: 1,
info: 2,
http: 3,
verbose: 4,
debug: 5,
silly: 6
}
WINSTON_MODULE_PROVIDER 토큰으로 Logger 객체를 주입받을 수 있다.
...
import { Logger as WinstonLogger } from 'winston';
import { WINSTON_MODULE_PROVIDER } from 'nest-winston';
...
export class UsersController {
constructor(
@Inject(WINSTON_MODULE_PROVIDER) private readonly logger: WinstonLogger,
private userService: UsersService,
) {}
...
private printWinstonLog(dto) {
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);
}
...
[MyApp] Error 2024. 3. 3. 오후 5:43:45 error: - { name: 'ga', email: 'gmin2i.y@gmail.com', password: '123456788hfds' }
[MyApp] Warn 2024. 3. 3. 오후 5:43:45 warn: - { name: 'ga', email: 'gmin2i.y@gmail.com', password: '123456788hfds' }
[MyApp] Info 2024. 3. 3. 오후 5:43:45 info: - { name: 'ga', email: 'gmin2i.y@gmail.com', password: '123456788hfds' }
[MyApp] Http 2024. 3. 3. 오후 5:43:45 http: - { name: 'ga', email: 'gmin2i.y@gmail.com', password: '123456788hfds' }
[MyApp] Verbose 2024. 3. 3. 오후 5:43:45 verbose: - { name: 'ga', email: 'gmin2i.y@gmail.com', password: '123456788hfds' }
[MyApp] Debug 2024. 3. 3. 오후 5:43:45 debug: - { name: 'ga', email: 'gmin2i.y@gmail.com', password: '123456788hfds' }
[MyApp] Silly 2024. 3. 3. 오후 5:43:45 silly: - { name: 'ga', email: 'gmin2i.y@gmail.com', password: '123456788hfds' }
Nest가 시스템 로깅을 할 때 출력하는 로그와 직접 출력하고자 하는 로깅의 형식을 동일하게 할 수 있다.
먼저 main.ts에 전역 로거로 설정한다.
...
import { WINSTON_MODULE_NEST_PROVIDER } from 'nest-winston';
async function bootstrap() {
...
app.useLogger(app.get(WINSTON_MODULE_NEST_PROVIDER));
...
}
bootstrap();
로깅을 하고자 하는 곳에서 LoggerServicefmf WINSTON_MODULE_NEST_PROVIDER 토큰으로 주입받는다.
...
export class UsersController {
constructor(
@Inject(WINSTON_MODULE_NEST_PROVIDER)
private readonly logger: WinstonLogger,
private userService: UsersService,
) {}
...
// 회원 가입
@Post()
async createUser(@Body() dto: CreateUserDto): Promise<void> {
this.printLoggerServiceLog(dto);
const { name, email, password } = dto;
await this.userService.createUser(name, email, password);
}
...
private printLoggerServiceLog(dto) {
try {
throw new InternalServerErrorException('test');
} catch (e) {
this.logger.error('error: ' + JSON.stringify(dto), e.stack);
}
this.logger.warn('warn: ', JSON.stringify(dto));
this.logger.log('log: ', JSON.stringify(dto));
this.logger.verbose('verbose: ', JSON.stringify(dto));
this.logger.debug('debug: ', JSON.stringify(dto));
}
}
[MyApp] Error 2024. 3. 3. 오후 5:54:07 error: {"name":"ga","email":"gmin22i.y@gmail.com","password":"123456788hfds"} - {
stack: [
'InternalServerErrorException: test\n' +
' at UsersController.printLoggerServiceLog (/Users/jimimyeon/Documents/workspace/nest-basic-project/src/users/users.controller.ts:82:13)\n' +
' at UsersController.createUser (/Users/jimimyeon/Documents/workspace/nest-basic-project/src/users/users.controller.ts:39:10)\n' +
' at /Users/jimimyeon/Documents/workspace/nest-basic-project/node_modules/@nestjs/core/router/router-execution-context.js:38:29\n' +
' at processTicksAndRejections (node:internal/process/task_queues:95:5)\n' +
' at /Users/jimimyeon/Documents/workspace/nest-basic-project/node_modules/@nestjs/core/router/router-execution-context.js:46:28\n' +
' at /Users/jimimyeon/Documents/workspace/nest-basic-project/node_modules/@nestjs/core/router/router-proxy.js:9:17'
]
}
[MyApp] Warn 2024. 3. 3. 오후 5:54:07 [{"name":"ga","email":"gmin22i.y@gmail.com","password":"123456788hfds"}] warn:
[MyApp] Info 2024. 3. 3. 오후 5:54:07 [{"name":"ga","email":"gmin22i.y@gmail.com","password":"123456788hfds"}] log:
[MyApp] Verbose 2024. 3. 3. 오후 5:54:07 [{"name":"ga","email":"gmin22i.y@gmail.com","password":"123456788hfds"}] verbose:
[MyApp] Debug 2024. 3. 3. 오후 5:54:07 [{"name":"ga","email":"gmin22i.y@gmail.com","password":"123456788hfds"}] debug:
nest-winston 모듈이 적용되어 MyAPP 태그가 붙어있다.
이 부분은 책보다 간단하게 설정하는 방법은 NestFactory.create 에 bufferLogs를 true 로 주면 된다. (블로그 참고)
import { NestFactory } from "@nestjs/core";
import { AppModule } from "./app.module";
import { ValidationPipe } from "@nestjs/common";
import { WINSTON_MODULE_NEST_PROVIDER } from "nest-winston";
async function bootstrap() {
const app = await NestFactory.create(AppModule, { bufferLogs: true });
app.useGlobalPipes(new ValidationPipe({ transform: true }));
app.useLogger(app.get(WINSTON_MODULE_NEST_PROVIDER));
await app.listen(3000);
}
bootstrap();
본 포스트는 한용재 저자의 NestJS로 배우는 백엔드 프로그래밍을 기반으로 스터디하며 정리한 내용들입니다.