[Just Site] 10. node.js 로그 기록 (feat. winston)

2022. 3. 5. 16:43프로젝트/Just Site

728x90

얼마 전에 회사에서 윈스턴을 썼는데,

추가적으로 공부도 할 겸해서 정리해보겠다.

 

winston

A logger for just about everything.. Latest version: 3.6.0, last published: 20 days ago. Start using winston in your project by running `npm i winston`. There are 15878 other projects in the npm registry using winston.

www.npmjs.com


1. 설치

우선 패키지를 설치한다.

npm i winston winston-daily-rotate-file

winston-daily-rotate-file은 매일 로그 파일을 만들어주는 패키지다.


2. 실행해보기

그리고 로거(logger)를 담당할 파일을 하나 만들자.

나는 logger.js로 만들었다.

const winston = require('winston');
const logger = winston.createLogger({
	format: winston.format.simple(),
	level: 'info',
	transports: [
		new winston.transports.File({ filename: 'error.log', level: 'error' }),
		new winston.transports.File({ filename: 'combined.log' }),
	],
});

// test 
logger.info('test');

exports.logger = logger;

이제 logger.js를 실행해보자

node logger.js

같은 폴더에 conbined.log와 error.log 파일이 생성되었다.

// combined.log
info: test

위와 같이 로그가 찍혀서 파일로 출력되었다.

이제 요소 하나하나를 살펴보도록 하자.


3. 로거에 속성 주기

logger객체는 다음의 요소들을 가진다.

{
  level: 'info', // 이 로그 이상만 기록함. info라면 info위에 있는 error, warn, info만 기록
  levels: winston.config.npm.levels, // 커스텀한 레벨을 등록할 수 있다.
  format: winston.format.json, // 로그의 형식(포맷)을 정할 수 있다.
  transports: [], // 로그를 어떻게 처리할건지 정할 수 있다.
  exitOnError: true, // false면 process.exit일 때의 exception을 기록하지 않는다.
  silent: false, // true면, 로그는 찍히지 않는다.
}

이 요소들은 createLogger를 호출하면서 매개변수로 줄 수도 있고,

따로 줄수도 있다. 또는 새로운 설정으로 덮어쓸 수 있다.

// 생성과 동시에
const logger = winston.createLogger({
	transports: [
    	new winston.transports.File({ filename: 'combined.log' }),
    ],
});

// 따로 주기
logger.level = 'warn';
logger.levels = {};
logger.silent = true;

// 이미 생성된 로거 객체의 설정 덮어쓰기
logger.configure({
  transports: [
    new winston.transports.File({ filename: 'somefile.log' }),
  ],
});

3-1. level

암 온더 넥스트 레벨

우선 RFC 5424를 기준 + winston에서 추가된 종류를 살펴보자.

 

RFC 5424 - The Syslog Protocol

 

datatracker.ietf.org

const levels = {
  error: 0,
  warn: 1,
  info: 2,
  http: 3,
  verbose: 4,
  debug: 5,
  silly: 6
};

위와 같이 순서대로 에러, 경고, 정보, http, 주절주절, 디버그, 잡로그 등이 있다.

로그의 성격에 따라서 호출하면 된다.

logger.error(''); // 에러
logger.warn(''); // 경고
logger.info(''); // 정보
logger.http(''); // http
logger.verbose(''); // 주절주절
logger.debug(''); // 디버그
logger.silly(''); // 잡로그

내가 logger에 level을 'info'로 줬다면,

info(2)이상인 error(0), warn(1), info(2)만 로거에 기록된다.


3-2. levels

위에서 살펴본 levels변수를 직접 설정할 수 있다.

그냥 바로 예시를 보고 이해하자.

const myCustomLevels = {
  levels: {
    foo: 0,
    bar: 1,
    baz: 2,
    foobar: 3
  },
  colors: {
    foo: 'blue',
    bar: 'green',
    baz: 'yellow',
    foobar: 'red'
  }
};

const customLevelLogger = winston.createLogger({
  levels: myCustomLevels.levels
});

customLevelLogger.foobar('some foobar level-ed message');

위와 같이 foo, bar, baz, foobar를 로그의 종류로 만들었다.

그리고 호출도 .foobar()같이 하면 된다.


3-3. format

로그의 형식을 정할 수 있다.

우선 기본으로 제공해준 winston.format를 살펴보자.

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

splat(): C언의 printf처럼 %d, %s 스타일로 작성할 수 있다
timestamp(): 메시지를 받은(로그가 찍힌) 시간을 알 수 있다.
label(): 각 메시지에 연결된 커스텀 라벨을 알 수있다.
ms(): 이전 로그메세지 이후에 몇초만에 도착한 로그인지 알려준다

combine(): 여러 format을 연결한다.
printf(): 포맷을 커스텀하게 설정할 수 있다.

 

예시로 하나 해보자.

우리는 시간 + 타입 + 메시지를 기록하고 싶다.

const winston = require('winston');
const { combine, printf, timestamp } = winston.format;

const myFormat = printf(({ timestamp, level, message }) => {
	return `${timestamp} [${level}]: ${message}`;
});

const logger = winston.createLogger({
    format: combine(
    	timestamp(),
        myFormat,
    ),
	transports: [
		new winston.transports.File({ filename: 'combined.log '}),
	],
});
2022-03-05T07:02:22.808Z [info]: test

위와 같이 로그가 찍혔다.

보다시피 combined를 사용하면 먼저 호출된 정보가 뒤로 가서 쌓이게 된다.
combined에 들어간 순서대로 처리가 된다는 소리다.

이번엔 ms를 넣어보자.

const winston = require('winston');
const { combine, printf, timestamp, ms } = winston.format;

const myFormat = printf(({ timestamp, ms, level, message }) => {
	return `${timestamp} [${level}]: ${message} - ${ms}`;
});

const logger = winston.createLogger({
	transports: [
		new winston.transports.File({ filename: 'combined.log' }),
	]
});

// test 
logger.info('test');
logger.info('test2');

exports.logger = logger;
2022-03-05T07:04:56.557Z [info]: test - +0ms
2022-03-05T07:04:56.558Z [info]: test2 - +1ms

3-4. transports

가장 중요한 핵심 부분이다.

로그를 받았다. 근데?

'어~ 잘받았다~' 할게 아니라면,

어디론가 출력을 해줘야 하지 않겠는다.

이게 정식 API가 없어서 README를 보고 하는 중인데, 

이것저것 알아보도록 하자


File

가장 기본적으로 파일로 저장하는 방식이다.

한 줄씩 쌓는다.

const logger = winston.createLogger({
	transports: [
		new winston.transports.File({ filename: 'combined.log' }),
	],
});

참고로 transports에 여러개를 넣으면 다 처리된다.

combined와는 다른 구조다.

그리고 특정 레벨의 로그를 분리할 수도 있다.

const logger = winston.createLogger({
	transports: [
		new winston.transports.File({ filename: 'combined.log' }),
		new winston.transports.File({ filename: 'error.log', level: 'error' }),
	]
});

// test 
logger.info('test');
logger.error('test2');
// combined.log
2022-03-05T07:29:51.529Z [info]: test - +0ms
2022-03-05T07:29:51.530Z [error]: test2 - +1ms

// error.log
2022-03-05T07:29:51.530Z [error]: test2 - +1ms

Console

우리가 실행하는 콘솔에 표시하는 방식이다.

const logger = winston.createLogger({
	transports: [
		new winston.transports.Console(),
	]
});

// test 
logger.info('test');
logger.error('test2');
// 콘솔창(cmd)
C:\Users\user\Documents\GitHub\legacy\winston-sample  (winston-sample@1.0.0)
λ node logger.js
2022-03-05T07:33:18.457Z [info]: test - +0ms
2022-03-05T07:33:18.459Z [error]: test2 - +2ms

winstonDaily

우리가 아까 설치했던, 매일매일 파일로 분리를 해준다.

로그 파일이 쌓이면 여는거조차 힘들고, 날짜 찾기도 힘들어서 있으면 좋다.

물론 일반 File과 daily를 섞어도 좋다.

아까 패키지 설치를 안 했다면 패키지부터 설치하자.

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

const logger = winston.createLogger({
	transports: [
        new winstonDaily({
          level: 'info',	// 어떤 정보를 기록할건가
          datePattern: 'YYYY-MM-DD',  // 파일 형식. 2022-03-05
          dirname: 'log,  // 저장할 폴더. 프로젝트 root를 기준으로 생성됨
          filename: `%DATE%.log`,  //파일이름. %DATE%는 위의 datePattern으로 정해진다.
          maxFiles: 30,  // 30일치 로그 파일 저장
          zippedArchive: true,  // 아카이브 압축
        }),
    ]
});

// test 
logger.info('test');
logger.error('test2');

 

이렇게 생겼다. 날짜가 바뀌면 날짜에 맞게 새 파일에 저장된다.


물론 딴짓도 하긴 했는데 찾아보면서 정리하니까

약 2시간 반정도 갔다. 이게 말인가...

그래도 원리가 이해되니까 원하는대로 할 수 있을 것 같다.