2019년 3월 8일 금요일

Node.js 로그파일 기록하기 - Winston

Node.js 로그파일 기록하기 - Winston

어플리케이션을 실행할 때 로그를 기록하는 것은 실사용에서 꼭 필요한 작업이다.  Node에서 일반적으로 사용하는 로그 모듈인 Winston을 이용하여 Console과 File에 로그를 만드는 방법에 대해 인터넷을 찾아보고 내가 원하는 방식으로 로그파일을 만들어 본다.
내가 원하는 방식:

  1. 로그파일은 \logs 디렉토리 밑에 날짜별로 생성
  2. 로그는 express의 미들웨어를 통해 client call을 로깅
  3. 호출파일에 대한 기록 // 향후 어떤 파일을 호출했는지 찾아보기 위해 적용

단순해 보이지만 이를 모두 구현한 예제를 찾기 힘들었고 여러가지 예제를 조합하여 다음과 같이 예제 프로그램을 구현해 보았다.

디렉토리는 다음과 같이 구성된다.
-- test.js (테스트 파일)
 + configs\winston.js (Winston 설정파일)
 + logs\yyyy-mm-dd-cmpapi.log (로그 파일)

configs\winston.js 파일은 다음과 같다.
  • // Log file lotation - 날짜에 따라 새로운 로그를 생성
  • const { createLogger, format, transports, handleExceptions } = require('winston');

  • const path = require('path');

  • // 시스템의 환경 변수를 확인해서 NODE_ENV값을 확인
  • // 사용은 PM2 의 ecosystem.yaml의 env항목 혹은 docker의 environment 항목에 설정
  • // 이런 설정이 없으면 'development' 로 지정
  • const env = process.env.NODE_ENV || 'development';

  • // logs 디렉토리를 생성하기 위해서 사용
  • const fs = require('fs');

  • // 현재 위치에 log 디렉토리를 지정 : const logDir = __dirname + '/logs';
  • const logDir = 'logs';

  • // Create the log directory if it does not exist
  • if (!fs.existsSync(logDir)) {
  •   fs.mkdirSync(logDir);
  • };

  • // File 모드 옵션 설정 - 날짜별로 로그파일의 이름을 변경
  • require('winston-daily-rotate-file');
  • const dailyRotateFileTransport = new transports.DailyRotateFile({
  • //  level: env === 'development' ? 'verbose' : 'info',
  •   handleExceptions: true,  // Exception 발생 시 로그에 기록
  •   filename: `${logDir}/%DATE%-cmpapi.log`, // 로그파일명
  •   datePattern: 'YYYY-MM-DD', // 로그에 사용되는 날짜 포멧
  •   json: false,
  •   colorize: false,
  •   zippedArchive: true, // 오래된 로그파일 압축
  •   maxSize: 5242880, // 5MB
  •   maxFiles: 5, // 5MB - 5개 파일까지 생성 후 이후부터는 오래된 파일 삭제
  •   format: format.combine(
  •     format.printf(
  •       info =>
  •         `${info.timestamp} ${info.level} [${info.label}]: ${info.message}`
  •     )
  •   ) // 로그의 포멧 정의
  • });

  • // Console 모드 옵션 설정 - 로그의 패턴별로 색상을 다르게 지정함
  • const consoleTransport = new transports.Console({
  •   level: env === 'development' ? 'verbose' : 'info',
  •   handleExceptions: true,  // Exception 발생 시 로그에 기록
  •   json: false,
  •   colorize: true,
  •   format: format.combine(
  •     format.printf(
  •       info =>
  •         `${info.timestamp} ${info.level} [${info.label}]: ${info.message}`
  •     )
  •   ) // 로그의 포멧 정의
  • });

  • // logger를 호출할 때 callback으로 filename을 추가하기 위해 구조를 변경
  • const logger = caller => {
  •   return createLogger({
  •     level: env === 'development' ? 'verbose' : 'info',
  •     format: format.combine(
  •       format.label({ label: path.basename(caller) }), //로그에 호출된 파일이름 기록
  •       // format.colorize(),
  •       format.timestamp({
  •         format: 'YYYY-MM-DD HH:mm:ss'  // 로그의 Timestamp 포멧을 지정
  •       }),
  •     ),
  •     transports: [
  •       consoleTransport,  // Console에 표시하는 형태
  •       dailyRotateFileTransport  // File에 표시하는 형태
  •     ],
  •     exitOnError: true // handleExceptions 실행
  •   });
  • };

  • //handleExceptions(new transports.Console({ colorize: true, json: true }));

  • module.exports = logger; 

test.js파일은 다음과 같다.

  • // 테스트를 위하여 express 모듈을 사용
  • const express = require('express')
  • const app = express()
  • const port = 3000
  • let queryString = '';

  • // Winston Setup - Winston은 로그를 기록할 때 일반적으로 사용하는 모듈
  • // configs 디렉토리의  wiston.js에 winston설정을 지정하여 import하여 사용
  • let logger = require('./configs/winston')(__filename);

  • //express에서 logger.steam 호출 시 표시하는 형태
  • logger.stream = {
  •   write: (message, encoding) => {
  •     logger.info(message);
  •   }
  • };

  • // Morgan Setup - Morgan은 Rest API의 로그(req,res)를 보기 좋게 구성
  • let morgan = require('morgan');

  • // Morgan Setup - dev는 날짜 표시를 제외한 Standard Apache common log output
  • app.use(morgan('dev', { "stream": logger.stream }));

  • // Level 별로 다른 로그 지정
  • logger.debug('Debugging info');
  • logger.verbose('Verbose info');
  • logger.info('Hello world');
  • logger.warn('Warning message');
  • logger.error('Error info');

  • // 아래는 테스트를 위한 express route call 수행
  • app.get('/', (req, res) => res.send('Hello World!'))

  • app.get('/pathCall', (req, res) => {
  •   console.log(req.query.queryStr);
  •   switch (req.query.queryStr) {
  •     case 'Location':
  •       queryString = ' - Location Call';
  •       break;
  •     case 'Country' :
  •       queryString = ' - Country Call';
  •       break;
  •     case 'SubDiv' :
  •       queryString = ' - Subdivision Call';
  •       break;
  •     default:
  •       break;
  •   };
  •   res.send(req.path + queryString);
  • });

  • app.listen(port, () => console.log(`Example app listening on port ${port}!`))


코드가 깔끔하지는 않지만 원하는 기능에 대해 모두 반영을 해 보았다.
winston 공식 메뉴얼을 이용해서 가능한 deprecated 된 기능이 없도록 구성하였다.


[참고 사이트]


콘솔, 파일 혹은 데이터베이스로 로그를 남기는 방법에 대해 설명한다.
리모트 프로세스의 로그를 통합하는데 필요한 기능을 설명한다.
https://mcpaint.tistory.com/198

주로 참고한 사이트로 날짜표시, 파일이름 표시방법을 설명한다.
https://www.digitalocean.com/community/tutorials/how-to-use-winston-to-log-node-js-applications

깔끔한 로그 만들기, 화면과 파일에 2중으로 로그남기기, 날짜별로 다른 이름 지정을 한다.
https://thisdavej.com/using-winston-a-versatile-logging-library-for-node-js/

Rest API를 위한 Log 관리를 설명한다.
https://medium.com/aha-official/%EC%95%84%ED%95%98-rest-api-%EC%84%9C%EB%B2%84-%EA%B0%9C%EB%B0%9C-13-b90f6007a8f9

morgan의 Stream을 winston의 log파일에 사용한다.
https://stackoverflow.com/questions/27906551/node-js-logging-use-morgan-and-winston

process.env 사용에 대해 설명한다.
https://codeburst.io/process-env-what-it-is-and-why-when-how-to-use-it-effectively-505d0b2831e7

댓글 없음:

댓글 쓰기