자 ! 오늘은 Winston 에 대해 알아보는 시간을 가집시다.

Winston을 사용하는 목적은 로그를 꼼꼼히 남기고 싶을때 사용한다.

Log는 에러를 파악할 수 있는열쇠이기 때문에 서버를 운영한다고 하면 Log 시스템을 구축해서 운영해야한다.

Node.js 에서 Log를 효율적으로 관리할 수 있게 도와주는 모듈이 바로 오늘 사용해볼 Winston 이다 !

🧑‍🏫 아뉘 선생님 질문이욥 ! 🖐

console.logconsole.error 가 있눈뒈 왜 귀찮게 Winston을 사용해야하나요?

💡 하하 똑똑한 질문이야 학생 내가 왜 인지 설명해 줄게 !

console.logconsole.error 는 개발중에 확인할때에 편리하게 콘솔 로그로 서버의 상황을 파악할 수 있어. 아주 편하고 빠르게 확인할 수 있지
다만 각 console 객체의 매서드들이 언제 호출되었는지 파악하기 어렵고 서버가 종료되는 순간 쌓여있던 로그들이 다 사리지기 때문에 우리는 외부로 파일에 저장해서 관리해야하는게 이상적이거든 이때 Winston을 사용하면 되는거지 음하하!!

자 이제 이유를 알았으니까 우리 모듈을 설치해 볼까~? ^_^

npm i winston 
npm i winston-daily-rotate-file

npm i winston-daily-rotate-file 은 로그 파일을 관리해주는 모듈이야.
기본적으로 하루 단위로 새로운 로그 파일을 생성해주고, 날짜별로 로그파일을 관리하게 구분해주고, 로그 파일의 최대 크기와 최대 저장 파일 개수 등을 설정할 수 있지.

1. Winston을 설치한 뒤, logger.js 파일을 생성해주고

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 형식으로 로그를 출력해줄 것인데, 다음 사진과 같이 출력 포맷으로 기록된다고 보면 된다.

2. Winston 로거 생성

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 인자를 줘서 메세지에 대한 기본 설정을 한다.

3. winston 로깅 형식 (transports)

 //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 로그도 같이 출력된다는 말이쥬

어때요 ? 참...쉽...ㅈ...👊

(죄송합니다.... 저도... 어려워요...)

4. winston 예외 로그 (exceptionHandlers)

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으로 서버를 하나 켜놓고 또 켜보는거다 그러면 어떻게 에러가 들어오는지 볼 수 있다.

서버는 꺼지면서 이미 서버가 켜져있다고 들어오게 된다. 와우 ! 이런 예상치 못한 에러도 DB에 쌓을 수 있다니 대박....

5. winston 개발 환경설정

// 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을 정리해보았다.
뭔가 복잡해보일 수 있으나 굉장히 재미있는 기능인거 같아서 작성하면서도 정리하면서도 굉장히 즐거운 시간이었다구 !! ⭐️

profile
산을 오르려고 하는데 이제 주차장에 막 주차한 초보개발자

0개의 댓글