NestJS로 작성된 과거 프로젝트를 리팩토링하려고 다시금 뜯어보니 로깅 작업을 안해줘서 모니터링하기가 매우 불편했습니다.
기존에 Express
에서 사용했던 Morgan
+ Winston
조합으로 로깅 환경을 구현하려고 했으나 nest-morgan
모듈 지원이 종료되었습니다.
그리하여 대체제로 nest-pino
를 고민했으나 미들웨어로 작성하여 사용이 가능해서 미들웨어 + winston으로 로깅 환경을 구성해보려고 합니다.
logger 미들웨어 파일을 생성해주고 NestMiddleWare 모듈을 오버라이딩 하기 위해 Implements한 class를 아래와 같이 작성 해줍니다.
// logger.middleware.ts
@Injectable()
export class LoggerMiddleware implements NestMiddleware { //implements -> 반드시 구현하도록 강제하는거임
private logger = new Logger('HTTP');
use(request: Request, response: Response, next: NextFunction): void {
const { ip, method, originalUrl } = request;
response.on('finish', () => {
const { statusCode } = response;
this.logger.log(`${method} ${originalUrl} ${statusCode} ${ip}`);
});
next();
}
}
이후 app.module.ts에서 아래와 같이 설정해줍니다.
NestModule을 한 AppModule 아래 MiddlewareConsumer를 설정한 뒤 모든("*") 경로로 인해 들어오는 곳에 LoggerMiddleware를 apply 시켜줍니다.
export class AppModule implements NestModule {
configure(consumer: MiddlewareConsumer): any {
consumer.apply(LoggerMiddleware).forRoutes("*");
}
}
이후 HTTP를 통해 들어오는 요청이 잘 들어오는 것을 확인할 수 있습니다.
이번엔 Winston을 적용시켜보겠습니다.
먼저 필요한 모듈을 설치해주겠습니다.
npm install winston nest-winston
그 다음 AppMoudle에 WinstonModule
을 import 해줍니다.
// app.module.ts
import * as winston from 'winston';
import {
utilities as nestWinstonModuleUtilities,
WinstonModule,
} from 'nest-winston';
@Module({
imports: [
...
WinstonModule.forRoot({
transports: [
new winston.transports.Console({
level: process.env.NODE_ENV === 'production' ? 'info' : 'silly',
format: winston.format.combine(
winston.format.colorize(),
winston.format.timestamp(),
nestWinstonModuleUtilities.format.nestLike('MyApp', { prettyPrint: true }),
),
}),
],
}),
],
})
export class AppModule { }
로그 레벨 지정 코드(production ? info : silly)는 아래 winston의 로그 레벨을 단계를 먼저 살펴봐야합니다.
지정된 로그 레벨 이상의 레벨만 출력이 가능하기에 production 단계에서는 info(2)
단계 이상, 그 외에는 silly(6)
이상인 모든 레벨의 로그가 출력되는 형태입니다. (숫자가 작아질수록 상위 레벨)
{
error: 0,
warn: 1,
info: 2,
http: 3,
verbose: 4,
debug: 5,
silly: 6
}
그리고 레벨별 색상을 적용시키기 위해 colorize format을 사용합니다.
이제는 로그를 찍기 위해 필요한 곳에 WINSTON_MODULE_PROVIDER
토큰을 주입한 뒤 Logger 객체를 주입받아 winston log를 사용할 수 있습니다.
// example.controller.ts
import { Logger } from 'winston';
import { WINSTON_MODULE_PROVIDER } from 'nest-winston';
export class UsersController {
constructor(
private usersService: UsersService,
@Inject(WINSTON_MODULE_PROVIDER) private readonly logger: Logger,
) { }
@Post('/login')
@HttpCode(200)
async login(@Body() loginDto: LoginDto): Promise<object> {
this.logger.info("로그인 시도!")
this.printLoginDto(loginDto)
return this.usersService.login(loginDto);
}
private printLoginDto(loginDto: LoginDto): void {
this.logger.error(`error, ${JSON.stringify(loginDto)}`);
this.logger.warn(`warn, ${JSON.stringify(loginDto)}`);
this.logger.info(`info, ${JSON.stringify(loginDto)}`);
this.logger.http(`http, ${JSON.stringify(loginDto)}`);
this.logger.verbose(`verbose, ${JSON.stringify(loginDto)}`);
this.logger.debug(`debug, ${JSON.stringify(loginDto)}`);
this.logger.silly(`silly, ${JSON.stringify(loginDto)}`);
}
아래와 같이 색상이 변경된 채 나오는 걸 확인할 수 있습니다.
이정도면 기본적인 로그를 보기에는 충분하나 조금 아쉬운 감이 있어서
다음에는 색상 변경과 로그 파일까지 저장하여 관리하는 법을 알아보겠습니다.