TIL - NestJS(Winston Logger)

eslerkang·2022년 2월 12일
1

TIL - Today I Learned

목록 보기
19/19
post-thumbnail

Logger to Winston

@nestjs/common에서 제공해주는 Logger도 보기에는 나쁘지 않지만 Log 파일을 외부에 저장하거나 다른 툴을 사용해 데이터베이스에 저장하는 식의 활용이 불가능하기 때문에 winston을 사용하기로 했다.

Winston

winston은 로그 다중 전송을 지원하다록 설계된 라이브러리로 로깅 프로세스의 과정들을 분리시켜 더 유연하고 확장 가능한 로깅 시스템을 작성하도록 도와준다. 로그 포맷과 로그 레벨(info, debug, warn 등)을 유연하게 설정할 수 있게 지원하며 로그 전송 API와는 분리되어 있다.

AppModule에서 Import

가장 간단하게 winston을 사용해볼 수 있는 방법은 AppModule에서 winston을 import해서 사용하는 것이다.
AppModule의 imports에

@Module({
  imports: [
        ...
    WinstonModule.forRoot({
      transports: [
        new winston.transports.Console({
          level: process.env.NODE_ENV === 'production' ? 'info' : 'silly',
          format: winston.format.combine(
            winston.format.timestamp(),
            nestWinstonModuleUtilities.format.nestLike('MyApp', { prettyPrint: true }),
          ),
        }),
      ],
    }),
  ],
})
export class AppModule { }

와 같이 추가해서 winston을 사용할 수 있다. 작성한 후 로깅을 하고 싶은 곳에서

import { Logger } from 'winston';

...
constructor(
	@inject(WINSTON_MODULE_PRODIVER) private logger: Logger
) {}

과 같이 의존성 주입을 통해 winston으로 로깅을 할 수 있게 된다.

전역으로 winston 등록

그러나 전역으로 winston을 등록해 모든 logging을 winston으로 하고 싶을 수도 있을 것이다. 그럴 경우엔

// main.ts

app.useLogger(app.get(WINSTON_MODULE_NEST_PROVIDER));

와 같이 전역으로 winston을 등록해준 다음에

import { LoggerService } from '@nestjs/common';
...
constructor(
	@Inject(WINSTON_MODULE_NEST_PROVIDER) private logger: LoggerService
) {}

와 같이 사용할 수도 있다.
이제 다시 Nest 서버를 실행시켜보면 RouterExplorer까지도 winston으로 대체되어 나오는 것을 알 수 있다.

부트스트래핑까지 winston으로 대체

위의 과정을 통해 RouterExplorer까지는 어떻게 winston으로 대체했지만 여전히 부트스트래핑 과정의 것들(InstanceLoader 등)은 기본 로거로 출력되는 것을 알 수 있다. 이는 의존성 주입을 통해 winston을 사용해 생기는 단점으로, 이를 해결하기 위해서는 winston을 import해서 사용하거나 app.useLogger를 통해 사용하지 않고 NestFactory.create에서부터 등록을 해줘야 한다. 즉,

// main.ts

import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
import * as config from 'config';
import { utilities, WinstonModule } from 'nest-winston';
import * as winston from 'winston';
import { Logger } from '@nestjs/common';

async function bootstrap() {
  const logger = new Logger();
  const serverConfig = config.get('server');
  const app = await NestFactory.create(AppModule, {
    logger: WinstonModule.createLogger({
      transports: [
        new winston.transports.Console({
          level: process.env.NODE_ENV === 'production' ? 'info' : 'silly',
          format:
            process.env.NODE_ENV === 'production'
              ? winston.format.simple()
              : winston.format.combine(
                  winston.format.timestamp(),
                  utilities.format.nestLike('Couti', { prettyPrint: true }),
                ),
        }),
      ],
    }),
  });
  await app.listen(serverConfig.port);
  logger.log(`App Listening at localhost:${serverConfig.port}`);
}
bootstrap();

과 같이 NestFactory.create 부분에서 옵션으로 logger 부분을 winston으로 등록해주면 이제 부트스트래핑 부분을 포함한 모든 로깅이 winston을 통해 출력되는 것을 확인할 수 있다.

파일로 transport

로그 파일을 저장해서 로깅하고 싶은 경우에는

winstonModule.createLogger({
  transports: [
    new winston.transports.File({
      filename: 'combined.log',
      level: 'info'
    }),
    new winston.transports.File({
      filename: 'errors.log',
      level: 'error'
    })
  ]
})

와 같은 옵션을 통해 파일로 저장할 수 있다.

winston에 대한 자세한 내용은 공식문서를 참고하도록 하자.

profile
Surfer surfing on the dynamic flow of the world

2개의 댓글

comment-user-thumbnail
2022년 4월 20일

bootstrap에서 부터 WinstonModule 적용해주면 app.module.ts에서는 WinstonModule을 빼줘야 하는건가요?
빼면 다른 모듈의 Controller에서 @Inject(WINSTON_MODULE_NEST_PROVIDER)
private readonly logger: LoggerService 와 같이 사용했을 때 에러나네요.

Potential solutions:

  • If NestWinston is a provider, is it part of the current UploadModule?
  • If NestWinston is exported from a separate @Module, is that module imported within UploadModule?
1개의 답글