실 서비스를 런칭하기 전에 사용자들이 마주할 수 있는 에러들을 모니터링하고 기록을 관리하기로 하였다.
1) 자바스크립트 관련 에러나 ORM 관련 에러
2) API 통신 후 Http StatusCode가 400번 / 500번대를 반환시 구분
3) 위 1, 2번을 파일로 기록하고 이를 서버에 저장
4) 에러 발생을 실시간 모니터링하고 Slack을 통해 알림 전송
이번 글에서는 1 ~ 3번까지 진행하고 4번은 다음 글에서 진행하겠습니다
$ npm install winston nest-winston
$ npm install winston-daily-rotate-file
Log를 더 활용하고자 한다면 Winston외에도 Nest가 제공하는 내장 로거를 커스텀해서 사용하거나 할 수 있지만 목표는 Log를 파일로 기록하고 특정기간동안 보유하는 것이 목적이었기 때문에 winston과 이를 기반으로 파일저장을 해주는 winston-daily-rotate-file 라이브러리를 사용하기로 하였다
(./common/logger/winston.util.ts)
import { WinstonModule, utilities } from 'nest-winston';
import * as winston from 'winston';
import * as winstonDaily from 'winston-daily-rotate-file';
const dailyOption = (level: string) => {
return {
level,
datePattern: 'YYYY-MM-DD',
dirname: `./logs/${level}`,
filename: `%DATE%.${level}.log`,
maxFiles: 30,
zippedArchive: true,
format: winston.format.combine(
winston.format.timestamp(),
utilities.format.nestLike(process.env.NODE_ENV, { colors: false, prettyPrint: true }),
),
};
};
export const winstonLogger = WinstonModule.createLogger({
transports: [
new winston.transports.Console({
level: process.env.NODE_ENV === 'production' ? 'http' : 'debug',
format: winston.format.combine(
winston.format.timestamp(),
utilities.format.nestLike(process.env.NODE_ENV, { colors: true, prettyPrint: true }),
),
}),
new winstonDaily(dailyOption('warn')),
new winstonDaily(dailyOption('error')),
],
});
dailyOption에서 로그를 파일로 저장할 때 설정을 하도록 했는데 maxFiles가 파일을 저장하는 기간(위의 코드는 30일)이다. 기간이 지난 파일은 자동삭제되므로 지속적인 로그파일 생성으로 서버 스토리지 용량을 걱정할 필요가 없다.
(예전 회사에서는 스토리지용량 부족으로 세션생성이 안되서 로그인이 안되는 일이 벌어지기도 했다..OMG)
(./main.ts)
import { winstonLogger } from './common/logger/winston.util';
async function bootstrap() {
const app = await NestFactory.create(AppModule, { cors: true, logger: winstonLogger });
await app.listen(process.env.PORT);
}
bootstrap();
위와 같이 app에 logger만 넣어주면 된다.
(이 부분은 필요여부에 따라 옵션으로)
(./common/logger/logger.middleware.ts)
import { Injectable, NestMiddleware } from '@nestjs/common';
import { Request, Response, NextFunction } from 'express';
import { winstonLogger } from './winston.util';
@Injectable()
export class LoggerMiddleware implements NestMiddleware {
constructor() {}
use(req: Request, res: Response, next: NextFunction) {
const { ip, method, originalUrl } = req;
const userAgent = req.get('user-agent');
res.on('finish', () => {
const { statusCode } = res;
if (statusCode >= 400 && statusCode < 500)
winstonLogger.warn(`[${method}]${originalUrl}(${statusCode}) ${ip} ${userAgent}`);
else if (statusCode >= 500) winstonLogger.error(`[${method}]${originalUrl}(${statusCode}) ${ip} ${userAgent}`);
});
next();
}
}
400번대는 비즈니스코드내에서 잘못된 접근이나 권한등 체크를 위해 따로 warn을 발생시켜 따로 로그를 관리하도록 하였고
500번대는 자바스크립트코드 에러나 ORM관련 에러를 따로 분류하기 위해 error를 발생시켜 로그를 관리하게 하였다.
App모듈에 미들웨어 등록
(./app.module.ts)
import { LoggerMiddleware } from './common/logger/logger.middleware';
export class AppModule implements NestModule {
configure(consumer: MiddlewareConsumer) {
consumer.apply(LoggerMiddleware).forRoutes('*');
}
}
이렇게 하면 모든 루트에서 발생하는 API 통신에 대해 Error와 Warn 로그를 남길 수 있게 된다.
개발단계에서는 잘 모르지만 실서비스를 운영하는 단계에서 Log의 중요성은 단순 중요하다는 말로는 표현이 안될정도로 어마어마하다고 생각한다. 버그는 우리를 생각해주지 않는다. 밤에도 터지고.. 새벽에도 터지고.. 퇴근하는 중에도 터지고..
Log를 통해 어떤 시간에 어떤 부분에서 에러가 났는지를 파악할 수 있기때문에 (로그부분에 user-agent를 넣어놓았는데 이는 가끔 아이폰이나 SAFARI에서만 버그가 나는 경우도 있기 때문이기도 하다) 디버깅에 용이하기 때문이다
다음 글에서는 에러가 발생했을 때 이를 모니터링하고 알림까지 보내주는 Sentry - Slack연동에 대해서 남겨볼께요
https://velog.io/@aryang/NestJS-winston%EC%9C%BC%EB%A1%9C-%EB%A1%9C%EA%B7%B8-%EA%B4%80%EB%A6%AC.
https://choseongho93.tistory.com/317