coding/Node.js

로그 남기기 1 - winston 사용

JIN_Coder 2022. 9. 9. 02:07

코딩 공부를 하면서 로깅이라는 말을 많이 들었다. 백앤드 개발자는 로그를 가지고 유지 보수할 때 로그 기록을 보면서 보완해가면 좋다는 말을 많이 들어서 로깅에 관심이 많았다.

예를 들어 어떤 사람이, 어떤 이벤트, 어떤 데이터를 요청했는지, 어떤 동작을 하다가, 어떤 트랜잭션을 돌리다가, 어떤 에러가 났는지 로그가 남아있다면 그 부분을 중점적으로 보완해 가면 에러들을 잡을 수 있을 것이다.

그래서 실전 프로젝트를 진행하면서 winston과 morgan을 사용해서 로깅을 구현하였고, 분량이 많아 따로 정리해보겠다.

 

 

winston

winston은 개발 환경보다는 배포 환경에서 로그를 남기기 위해 사용한다.

개발환경에서는 콘솔 로그나 콘솔 에러를 통해 무슨 문제가 어디서 생겼는지 바로 알 수 있지만, 배포 환경에서는 콘솔 창을 쉽게 볼 수가 없고, 서버가 꺼지고 재시작되면 그동안의 기록들이 날아가 버리는 문제가 생긴다.
때문에 로그나 에러가 발생했을 때 외부 파일에 로그들을 기록하여 보존하기 위해 사용한다.

 

 

1. winston 설치

npm i winston winston-daily-rotate-file

 

2. winston 코드 작성하기

./config/winston.js 파일을 만들어 코드를 입력해주었다.

사실 잘 정리해 놓은 블로그의 코드를 가져다가 조금씩 내 입맛에 맞게 수정해서 사용했다.

 

전체 코드는 맨 아래 올려두고 각 부분을 간략히 살펴보겠다.

2.1 로그 경로 & 출력형식 설정

const winston = require('winston');
const winstonDaily = require('winston-daily-rotate-file');

//* 로그 파일 저장 경로 → 루트 경로/logs 폴더
const logDir = './config/logs';
require('dotenv').config();

const { combine, timestamp, label, printf } = winston.format;

//* log 출력 포맷 정의 함수
const logFormat = printf(({ level, message, label, timestamp }) => {
  return `${timestamp} ${level}: ${message}`; // 날짜 로그레벨: 메세지
});

우선 필요한 모듈을 불러와준다.

 

로그파일 저장 경로를./config/logs 폴더로 설정하여 로그 기록 파일들이 저 안에 생성된다.

 

winston.format. 에서 필요한 메서드와 파라미터들을 구조 분해로 저장해준다.

 

logFormat이라는 로그 출력 포맷 모양을 지정해준다.

시간 로그 레벨: 메시지 형식으로 출력될 예정이다.

개발환경에서 찍히는 로그 모습
배포환경에서 찍히는 로그 모습

2.2 로거 생성 (createLogger) / 로깅 형식 (transports) / 예외 로그 (exceptionHandlers)

const logger = winston.createLogger({
  //* 로그 출력 형식 정의
  format: combine(
    timestamp({ format: 'YYYY-MM-DD HH:mm:ss' }),
    logFormat // log 출력 포맷
    //? format: combine() 에서 정의한 timestamp와 label 형식값이 logFormat에 들어가서 정의되게 된다. level이나 message는 콘솔에서 자동 정의
  ),
  //* 실제 로그를 어떻게 기록을 한 것인가 정의
  transports: [
    //* info 레벨 로그를 저장할 파일 설정 (info: 2 보다 높은 error: 0 와 warn: 1 로그들도 자동 포함해서 저장)
    new winstonDaily({
      level: 'info', // info 레벨에선
      datePattern: 'YYYY-MM-DD', // 파일 날짜 형식
      dirname: logDir + '/info', // 파일 경로
      filename: `%DATE%.info.log`, // 파일 이름
      maxFiles: 30, // 최근 30일치 로그 파일을 남김
      zippedArchive: true,
    }),
    //* error 레벨 로그를 저장할 파일 설정 (info에 자동 포함되지만 일부러 따로 빼서 설정)
    new winstonDaily({
      level: 'error', // error 레벨에선
      datePattern: 'YYYY-MM-DD',
      dirname: logDir + '/error', // /logs/error 하위에 저장
      filename: `%DATE%.error.log`, // 에러 로그는 2020-05-28.error.log 형식으로 저장
      maxFiles: 30,
      zippedArchive: true,
    }),
  ],
  //* uncaughtException 발생시 파일 설정
  exceptionHandlers: [
    new winstonDaily({
      level: 'error',
      datePattern: 'YYYY-MM-DD',
      dirname: logDir + '/errorCatch',
      filename: `%DATE%.exception.log`,
      maxFiles: 30,
      zippedArchive: true,
    }),
  ],
});

로그 기록을 하는 메서드 생성 부분이다

createLogger 메서드로 logger를 만든다.

format 인자를 통해 메시지에 대한 기본 설정을 한다.

timestamp로 날짜 형식을 정하고, 위에서 지정한 logFormat을 넣어, 설정한 timestamp을 인자로 받아 printf 되게 설정해준다. 나머지 인자의 level과 message는 자동으로 콘솔에서 지정된다.

 

transports는 로그 저장 방식을 정의한다.

각 레벨별로 로그 저장 방식을 다르게 할 수 있다.

info 레벨에선 info이상의 로그들이 한 번에 저장이 된다. 즉, info설정 시 error, warn, info 로그가 한 번에 저장이 된다.

winstonDaily안의 값은 주석 내용을 참고하면 된다.(세부적인 형식을 지정하는 것)

 

보통 로그를 남기는 이유는 err를 확인하기 위함이기 때문에 error 레벨의 로그는 따로 저장해서 모아 본다.그래서 info 레벨의 형식 하나, error 레벨의 형식 하나를 정의하여 기록한다.

 

예외처리로 잡은 에러들 외의 추가적인 발생하는 에러들을 잡기 위해 exceptionHandlers 부분도 설정해주었다.

transports로 형식을 정의해주면 이런식으로 폴더와 파일이 생긴다.

2.3 개발 환경설정

//* Production 환경이 아닌, 개발 환경일 경우 파일 들어가서 일일히 로그 확인하기 번거로우니까 화면에서 바로 찍게 설정 (로그 파일은 여전히 생성됨)
if (process.env.NODE_ENV !== 'production') {
  logger.add(
    new winston.transports.Console({
      format: winston.format.combine(
        winston.format.colorize(), // 색깔 넣어서 출력
        logFormat
      ),
    })
  );
}

module.exports = logger;

배포 환경이 아닌 개발환경의 경우 직접 로그파일을 열어 로그를 보는 것이 번거로우니 콘솔 창에 바로 찍히게 설정하는 부분이다.개발환경이라면 콘솔에도 찍히고, 로그파일에도 로그들을 기록한다.

 

다른 곳에서 logger 모듈을 사용할 수 있게 exprts 한다.

 

 

winston.js 전체 코드

const winston = require('winston');
const winstonDaily = require('winston-daily-rotate-file');

//* 로그 파일 저장 경로 → 루트 경로/logs 폴더
const logDir = './config/logs';
require('dotenv').config();

const { combine, timestamp, label, printf } = winston.format;

//* log 출력 포맷 정의 함수
const logFormat = printf(({ level, message, label, timestamp }) => {
  return `${timestamp} ${level}: ${message}`; // 날짜 로그레벨: 메세지
});

/*
 * Log Level
 * error: 0, warn: 1, info: 2, http: 3, verbose: 4, debug: 5, silly: 6
 */

const logger = winston.createLogger({
  //* 로그 출력 형식 정의
  format: combine(
    timestamp({ format: 'YYYY-MM-DD HH:mm:ss' }),
    logFormat // log 출력 포맷
    //? format: combine() 에서 정의한 timestamp와 label 형식값이 logFormat에 들어가서 정의되게 된다. level이나 message는 콘솔에서 자동 정의
  ),
  //* 실제 로그를 어떻게 기록을 한 것인가 정의
  transports: [
    //* info 레벨 로그를 저장할 파일 설정 (info: 2 보다 높은 error: 0 와 warn: 1 로그들도 자동 포함해서 저장)
    new winstonDaily({
      level: 'info', // info 레벨에선
      datePattern: 'YYYY-MM-DD', // 파일 날짜 형식
      dirname: logDir + '/info', // 파일 경로
      filename: `%DATE%.info.log`, // 파일 이름
      maxFiles: 30, // 최근 30일치 로그 파일을 남김
      zippedArchive: true,
    }),
    //* error 레벨 로그를 저장할 파일 설정 (info에 자동 포함되지만 일부러 따로 빼서 설정)
    new winstonDaily({
      level: 'error', // error 레벨에선
      datePattern: 'YYYY-MM-DD',
      dirname: logDir + '/error', // /logs/error 하위에 저장
      filename: `%DATE%.error.log`, // 에러 로그는 2020-05-28.error.log 형식으로 저장
      maxFiles: 30,
      zippedArchive: true,
    }),
  ],
  //* uncaughtException 발생시 파일 설정
  exceptionHandlers: [
    new winstonDaily({
      level: 'error',
      datePattern: 'YYYY-MM-DD',
      dirname: logDir + '/errorCatch',
      filename: `%DATE%.exception.log`,
      maxFiles: 30,
      zippedArchive: true,
    }),
  ],
});

//* Production 환경이 아닌, 개발 환경일 경우 파일 들어가서 일일히 로그 확인하기 번거로우니까 화면에서 바로 찍게 설정 (로그 파일은 여전히 생성됨)
if (process.env.NODE_ENV !== 'production') {
  logger.add(
    new winston.transports.Console({
      format: winston.format.combine(
        winston.format.colorize(), // 색깔 넣어서 출력
        logFormat
      ),
    })
  );
}

module.exports = logger;

 

3. 환경변수 설정하기

위 코드에서 봤듯이 윈스턴은 주로 배포 환경에서 사용하기 때문에 환경변수에서 현재 어떤 상태인지 환경변수를 설정하여 사용한다.

이는 나중에 morgan에서도 사용하기 때문에 정의해주고 간다.

.env 파일에 아래와 같이 코드를 작성한다.

// .env

# log
NODE_ENV = dev # 개발환경
# NODE_ENV = production # 배포환경

위 상태는 배포 환경이 아닌 개발환경에서 사용할 때의 모습이다. 배포 환경을 위해서는 개발환경 부분을 주석 처리하고, 배포 환경 부분의 주석을 해제한다.

 

 

winston 단독사용

winston을 제대로 사용하기 위해선 morgan과 함께 사용해야 좋다.

morgan 미들웨어를 만들어서 모든 api요청에 대해 로그를 작성할 수 있기 때문이다.

하지만 혼자서 사용한다고 하면 아래와 같이 사용한다.

// app.js

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');
});

winston 단독으로 사용하면 각 api안에서 logger.info / logger.error를 사용해서 해당 api에 접근했을 때 로그를 기록한다.

 

하지만 보통 api가 수십 개가 되기 때문에 morgan을 통해 미들웨어를 생성하고 이를 지날 때마다 로그를 기록하면 편하기 때문에 같이 사용하는 것 같다.

 

다음에는 morgan 설치 및 사용과 두 개를 같이 사용하는 방법에 대해서 정리해보겠다.

 

morgan 사용 / 함께 사용하기

 

 

 

참고한 블로그

 

[NODE] 📚 Winston 모듈 사용법 - 서버 로그 관리

Winston 모듈 어떤 서버든지 실제로 서비스를 운영하려면 로그를 꼼꼼히 남기는 것은 필수이다. Log는 에러를 파악할 수 있는 열쇠이기 때문에 서버를 운영한다고 하면 로그 시스템을 구축해서 시

inpa.tistory.com

 

'coding > Node.js' 카테고리의 다른 글

node-schedule  (0) 2022.09.14
로그 남기기 2 - morgan 사용  (0) 2022.09.10
socket.io - 실시간 채팅 / 채팅방 구현  (2) 2022.09.07
소켓 / 웹소켓  (0) 2022.09.05
에러 핸들러  (0) 2022.09.03