Winston을 사용하는 목적은 로그를 꼼꼼히 남기고 싶을때 사용한다.
Log는 에러를 파악할 수 있는열쇠이기 때문에 서버를 운영한다고 하면 Log 시스템을 구축해서 운영해야한다.
🧑🏫 아뉘 선생님 질문이욥 ! 🖐
console.log 나 console.error 가 있눈뒈 왜 귀찮게 Winston을 사용해야하나요?
💡 하하 똑똑한 질문이야 학생 내가 왜 인지 설명해 줄게 !
console.log 와 console.error 는 개발중에 확인할때에 편리하게 콘솔 로그로 서버의 상황을 파악할 수 있어. 아주 편하고 빠르게 확인할 수 있지
다만 각 console 객체의 매서드들이 언제 호출되었는지 파악하기 어렵고 서버가 종료되는 순간 쌓여있던 로그들이 다 사리지기 때문에 우리는 외부로 파일에 저장해서 관리해야하는게 이상적이거든 이때 Winston을 사용하면 되는거지 음하하!!
npm i winston
npm i winston-daily-rotate-file
npm i winston-daily-rotate-file 은 로그 파일을 관리해주는 모듈이야.
기본적으로 하루 단위로 새로운 로그 파일을 생성해주고, 날짜별로 로그파일을 관리하게 구분해주고, 로그 파일의 최대 크기와 최대 저장 파일 개수 등을 설정할 수 있지.
const winston = require('winston');
const winstonDaily = require('winston-daily-rotate-file');
const process = require('process');
const { combine, timestamp, label, printf } = winston.format;
// 로그 파일 저장 경로 → 루트 경로/logs 폴더
const logDir = `${process.cwd()}/logs`;
// log 출력 포맷 정의 함수
const logFormat = printf(({ level, message, label, timestamp }) => {
return `${timestamp} [${label}] ${level}: ${message}`; // 날짜 [시스템이름] 로그레벨 메세지
});
필요 모듈들을 불러와준다.
그리고 winston.format 에서 필요한 메소드와 파라미터들을 객체 변수에 구조분해로 저장해준다.
process 모듈은 process.cwd() 값인 루트 경로를 얻기위해 불러와주었다.
로그 파일 저장 경로를 루트경로/logs 폴더로 설정해준다.
logFormat 이라는 로그 출력 포맷 모양을 지정해준다.
timestamp [label] level: message 형식으로 로그를 출력해줄 것인데, 다음 사진과 같이 출력 포맷으로 기록된다고 보면 된다.
const logger = winston.createLogger({
//* 로그 출력 형식 정의
format: combine(
timestamp({ format: 'YYYY-MM-DD HH:mm:dd' }),
label({ label: '최예닮의 Winston 연습하기 ~~' }), // 어플리케이션 이름
logFormat, // log 출력 포맷
// format: combine() 에서 정의한 timestamp와 label 형식값이 logFormat에 들어가서 정의되게 된다.
//level이나 message는 콘솔에서 자동 정의
),
});
이제 본격적으로 로그 기록(logging)을 하는 메서드를 생성해볼 차례이다.
winston 패키지의 createLogger 메서드로 logger를 만들 수 있다.
그리고 format 인자를 줘서 메세지에 대한 기본 설정을 한다.
//Log Level error: 0, warn: 1, info: 2, http: 3, verbose: 4, debug: 5, silly: 6
const logger = winston.createLogger({
// 로그 출력 형식 정의
format: combine(
// ...
),
// 실제 로그를 어떻게 기록을 한 것인가 정의
transports: [
// info 레벨 로그를 저장할 파일 설정 (info: 2 보다 높은 error: 0 와 warn: 1 로그들도 자동 포함해서 저장)
new winstonDaily({
level: 'info', // info 레벨에선
datePattern: 'YYYY-MM-DD', // 파일 날짜 형식
dirname: logDir, // 파일 경로
filename: `%DATE%.log`, // 파일 이름
maxFiles: 30, // 최근 30일치 로그 파일을 남김
zippedArchive: true, // 아카이브된 로그 파일을 gzip으로 압축할지 여부
}),
// error 레벨 로그를 저장할 파일 설정 (info에 자동 포함되지만 일부러 따로 빼서 설정)
new winstonDaily({
level: 'error', // error 레벨에선
datePattern: 'YYYY-MM-DD',
dirname: logDir + '/error', // /logs/error 하위에 저장
filename: `%DATE%.error.log`, // 에러 로그는 2022-12-07.error.log 형식으로 저장
maxFiles: 30,
zippedArchive: true,
}),
],
});
winston의 로그 레벨은 순서가 있는데, 다음과 같이 구성되어있다.
error: 0 , warn: 1 , info: 2 , http: 3 , verbose: 4 , debug: 5 , silly: 6
숫자가 낮을수록 위험도가 높은것이라고 한다. Error가 제일 위험한거겠쥬?
winston 로그의 level을 설정하면 해당 레벨 이상의 위험도를 가지는, 숫자가 같거나 낮은 로그를 함께 출력하게 됩니다요.
그러니까 level 2인 info: 2 를 설정하게되면 2보다 낮은 error: 0, warn: 1 로그도 같이 출력된다는 말이쥬
Error 핸들링한 코드 에러 말고도 try catch 예상치 못한 에러를 잡기위해 설정해줄 수 있다.
// uncaughtException 발생시 파일 설정
exceptionHandlers: [
new winstonDaily({
level: 'error',
datePattern: 'YYYY-MM-DD',
dirname: logDir,
filename: `%DATE%.exception.log`,
maxFiles: 30,
zippedArchive: true,
}),
],
});
예시로 내가 localhost:3000으로 서버를 하나 켜놓고 또 켜보는거다 그러면 어떻게 에러가 들어오는지 볼 수 있다.
// Production 환경이 아닌, 개발 환경일 경우 파일 들어가서 일일히 로그 확인하기 번거로우니까 화면에서 바로 찍게 설정 (로그 파일은 여전히 생성됨)
if (process.env.NODE_ENV !== 'production') {
logger.add(
new winston.transports.Console({
format: winston.format.combine(
winston.format.colorize(), // log level별로 색상 적용하기
winston.format.simple(), // `${info.level}: ${info.message} JSON.stringify({ ...rest })` 포맷으로 출력 객체를 JSON 문자열로 변환
),
}),
);
}
module.exports = logger;
이 친구는 개발 환경일때 필요해서 만들어 준거다. 일일히 파일에 들어가서 로그 확인하기 번거로우니까 터미널에 바로 찍히게 설정해주는거다.
그렇다고 로그 파일이 생성이 안되는건 아니고 여전히 생성은 된다.
자 이제 우리 작성한 Winston 을 실행해보자구 !!
그럼 이제 간단하게 서버 코드를 작성하고 한번 경로를 타고 들어가보자!!
const express = require('express');
const logger = require('./logger');
const app = express();
app.get('/', (req, res) => {
logger.info('GET /');
res.sendStatus(200);
});
app.get('/error', (req, res) => {
logger.error('Error message');
res.sendStatus(500);
});
app.listen(3000, () => {
logger.info('Server listening on port 3000');
});
자 http://localhost:3000 에 접속하면 logger.info() 가 로그파일에 적히게 된다.
그리고 http://localhost:3000/error 에 접속하면 logger.error 가 /logs/error 폴더에 로그 파일이 적히게 된다.
자! 이렇게해서 오늘 Winston을 정리해보았다.
뭔가 복잡해보일 수 있으나 굉장히 재미있는 기능인거 같아서 작성하면서도 정리하면서도 굉장히 즐거운 시간이었다구 !! ⭐️