
모든 것을 로그로 남겨보자!
Nest 자체에서 로깅을 지원해주기는 하지만 몇 가지 제한 사항이 있어 Winston + winston-daily-rotate-file 을 이용하였다.
해당 제한 사항은
로그를 파일로 저장할 수 있는지 여부 확인 불가
공식 문서에 콘솔로 출력하는 예제는 있는데 파일로 저장하는 예제는 보이지 않는다.
날짜별로 데이터 저장 - winston
여러 모듈들을 찾아보는 중 winston이 가장 쓰기 편해보였다. 덤으로 콘솔에 보이는 로그 포맷도 정할 수 있다는 점이 강점!
파일 크기가 일정크기 이상이 되면 파일 나누기 - winston-daily-rotate-file
winston의 문제점이 저장하는 파일 크기를 조정하지 못하는 것. 물론 지금으로서는 처리하는 데이터가 많지 않아 일별로 하나의 파일에 저장을 해도 큰 문제는 없지만 추후 처리하는 데이터가 많아질 것을 고려하여 일정 크기 이상으로 로그파일이 커지면 분리된 파일로 저장되게 한다.
yarn add winston winston-daily-rotate-file nest-winston
winston 모듈과 nest에서 사용하기 위한 nest-winston, 로그 파일 최대 크기를 정하기 위한 winston-daily-rotate-file 모듈을 다운받는다.
import * as winston from 'winston';
import {
utilities as nestWinstonModuleUtilities,
} from 'nest-winston';
import * as DailyRotateFile from 'winston-daily-rotate-file';
const loggerLevel = 'silly';
const loggerFormatter = winston.format.combine(
winston.format.timestamp(),
nestWinstonModuleUtilities.format.nestLike('MyApp', { // `MyApp`은 로그 맨 앞에 뜨는 문구. 맨 위의 사진에서 보면 된다.
prettyPrint: true, // json 데이터의 경우에 예쁘게 저장해줌. 로그 분석에 어려움을 겪을 수 있으니 false로 하자
}),
);
const loggerSetting = {
level: loggerLevel,
format: loggerFormatter,
};
export const loggerConfig = {
transports: [
new winston.transports.Console( // winston에서 console에 띄우는 로그 세팅
loggerSetting
),
new DailyRotateFile({
filename: 'logs/%DATE%.log', // 로그 파일 저장 위치와 이름. 디렉터리가 없으면 새로 생성한다.
datePattern: 'YYYY-MM-DD', // 날짜 저장 형식. filename의 매개변수로 이용된다.
maxSize: '20m', // 로그 파일의 최대 크기
// maxFiles: '14d', // 로그 파일의 최대 저장 기간 설정. 14일이 지나면 삭제된다.
...loggerSetting
}),
],
};
/*
loggerSetting : 해당 딕셔너리 자체를 사용
...loggerSetting : 해당 딕셔너리에 있는 데이터를 뽑아서 사용
즉, { loggerSetting }의 경우
{
{
level: loggerLevel,
format: loggerFormat
}
}
{ ...loggerSetting }의 경우
{
level: loggerLevel,
format: loggerFormat
}
로 나타나게 된다.
*/
import { WinstonModule } from 'nest-winston';
@Module({
imports: [
...
WinstonModule.forRoot(
loggerConfig // 실제로는 transport에 여러가지를 다 넣어야하지만 유지,보수를 위해 다른 파일로 빼놓았다.
),
],
controllers: [],
providers: [],
})
export class AppModule implements NestModule {}
import { WinstonModule } from 'nest-winston';
async function bootstrap() {
const app = await NestFactory.create(AppModule, {
logger: WinstonModule.createLogger( // 시스템 로그를 Winston으로 설정하자!
loggerConfig
),
});
await app.listen(3000);
}
bootstrap();
import { WINSTON_MODULE_PROVIDER } from 'nest-winston/dist/winston.constants';
import { Logger as WinstonLogger } from 'winston';
@Controller('boards')
export class BoardsController {
constructor(
...
@Inject(WINSTON_MODULE_PROVIDER) private readonly logger: WinstonLogger // 로거를 사용하기 위해서는 의존성 주입이 필요
) {};
private printWinstonLog(dto) {
this.logger.error('error: ', dto); // error. 앱 실행이 중단될 수 있는 막대한 오류
this.logger.warn('warn: ', dto); // warining. 잠재적으로 error가 될 수 있는 간단한 오류
this.logger.info('info: ', dto); // info. 일반적인 메시지.
this.logger.debug('debug: ', dto); // 디버깅용 로그 출력
this.logger.verbose('verbose: ', dto); // 가장 낮은 레벨의 로그.
this.logger.http('http: ', dto); // http 요청 및 응답 로그를 남기기 위해 사용. 일반적인 로그 레벨은 아님.
this.logger.silly('silly: ', dto);
}
@Get()
async getAllBoards(@Query('page') page: number = 1) {
const res = await this.boardsService.findAll(page);
this.printWinstonLog(res);
return res;
}
}

없는 데이터를 반환하게 했지만, 로그가 정상적으로 출력되는 것을 볼 수 있다.

파일에 저장하는 것도 성공적으로 이루어졌다!
정규표현식을 이용하여 원하는 타입의 로그만 뽑을 수 있게 하였다!
import os
import re
# 파일 이름 패턴 정규 표현식
file_pattern = r"^2024-02-19"
log_dir = "./nest/logs"
log_pattern = r"\[.*?\]\s+(.*?)\s+(\d{4}\.\s\d{1,2}\.\s\d{1,2}\.\s오후\s\d{1,2}:\d{1,2}:\d{1,2})\s+(.*?)$"
# 현재 디렉토리의 파일 목록 가져오기
files = os.listdir(log_dir)
# 정규 표현식에 따라 파일 필터링
filtered_files = [file for file in files if re.match(file_pattern, file)]
log_messages = []
for file in filtered_files:
with open(os.path.join(log_dir, file), 'r', encoding='utf-8') as file:
lines = file.readlines()
log_messages += [ line.strip().replace('\t', ' ') for line in lines ]
for log_message in log_messages:
match = re.match(log_pattern, log_message)
if match:
level = match.group(1)
time = match.group(2)
message = match.group(3)
if level == 'Warn':
print("Level:", level)
print("Time:", time)
print("Message:", message)
print()