โ ๋๋ ์ต๊ทผ์ ์ปดํจํฐ๊ฐ ๋๋ํด์ ธ์ ์ ์ธ๊ณ๋ฅผ ์ง๋ฐฐํ ๊ฒ์ด๋ผ๋ ๊ณตํฌ๊ฐ ๊ฑฐ์ ์ธ๊ณ ๋ชจ๋ ๊ณณ์์ ์ฌ๋ผ์ก๋ค๋ ๊ฑธ ์๊ฒ ๋์๋ค.
์ด๊ฒ์ ์ ํํ MS-DOS์ ๋ฐฐํฌ ์์ ๊ณผ ์ผ์นํ๋ค. โ- Larry DeLuca
ํ๋ก๊ทธ๋๋จธ

Winston ๋ชจ๋
์ด๋ค ์๋ฒ๋ ์ง ์ค์ ๋ก ์๋น์ค๋ฅผ ์ด์ํ๋ ค๋ฉด ๋ก๊ทธ๋ฅผ ๊ผผ๊ผผํ ๋จ๊ธฐ๋ ๊ฒ์ ํ์์ด๋ค.
Log๋ ์๋ฌ๋ฅผ ํ์ ํ ์ ์๋ ์ด์ ์ด๊ธฐ ๋๋ฌธ์ ์๋ฒ๋ฅผ ์ด์ํ๋ค๊ณ ํ๋ฉด ๋ก๊ทธ ์์คํ ์ ๊ตฌ์ถํด์ ์์คํ ์ ์ด์ํด์ผ ํ๋ค.
์๋ฅผ๋ค์ด ์ด๋ค ์ฌ๋์ด, ์ด๋ค ์ด๋ฒคํธ, ์ด๋ค ๋ฐ์ดํฐ๋ฅผ ์์ฒญํ๋์ง, ์ด๋ค ๋์์ ํ๋ค๊ฐ, ์ด๋ค ํธ๋์ญ์ ์ ๋๋ฆฌ๋ค๊ฐ, ์ด๋ค ์๋ฌ๊ฐ ๋ฌ๋์ง ์ด๋ฅผ ์ ๋ฐ ๊ด๋ฆฌํ ์ ์๋ค.
Node.js์์๋ log๋ฅผ ํจ์จ์ ์ผ๋ก ๊ด๋ฆฌํ ์ ์๊ฒ ๋์์ฃผ๋ ๋ชจ๋์ธ winston.js๊ฐ ์๋ค.
์์คํค(winston.js)๋ ์ค์ ์๋ฒ๋ฅผ ์ด์ํ ๋ console.log ์ console.error ๋ฅผ ๋์ฒดํ๊ธฐ ์ํ ๋ชจ๋์ด๋ค.
console.log ์ console.error ๋ ๊ฐ๋ฐ ์ค์๋ ํธ๋ฆฌํ๊ฒ ์ฝ์ ๋ก๊ทธ๋ก ์๋ฒ์ ์ํฉ์ ํ์
ํ ์ ์์ง๋ง, ์ค์ ๋ฐฐํฌ ์์๋ ์ฌ์ฉํ๊ธฐ ์ด๋ ต๋ค.
์๋ํ๋ฉด console ๊ฐ์ฒด์ ๋ฉ์๋๋ค์ด ์ธ์ ํธ์ถ๋์๋์ง ํ์ ํ๊ธฐ ํ๋ค ๋ฟ๋ง ์๋๋ผ, ์๋ฒ๊ฐ ์ข ๋ฃ๋๋ ์๊ฐ ์์ฌ์๋ ๋ก๊ทธ๋ค๋ ์ฌ๋ผ์ ธ ๋ฒ๋ฆฌ๊ธฐ ๋๋ฌธ์ด๋ค.
์ด์ ๊ฐ์ ์ํฉ์ ๋ฐฉ์งํ๋ ค๋ฉด ๋ก๊ทธ๋ฅผ ์ธ๋ถ๋ก ํ์ผ์ ์ ์ฅํด์ ๊ด๋ฆฌํ๋ ๊ฒ์ด ์ด์์ ์ธ๋ฐ, ์ด๋ winston์ ์ฌ์ฉํ๋ค๊ณ ๋ณด๋ฉด ๋๋ค.

Winston ๋ชจ๋ ์ฌ์ฉ๋ฒ
winston
A multi-transport async logging library for Node.js. Latest version: 3.7.2, last published: 2 months ago. Start using winston in your project by running `npm i winston`. There are 16378 other projects in the npm registry using winston.
www.npmjs.com
winston-daily-rotate-file
A transport for winston which logs to a rotating file each day.. Latest version: 4.6.1, last published: 3 months ago. Start using winston-daily-rotate-file in your project by running `npm i winston-daily-rotate-file`. There are 1384 other projects in the n
www.npmjs.com
$ npm i winston winston-daily-rotate-file # 2๊ฐ ์ค์น
winston-daily-rotate-file์ ๋ก๊ทธ ํ์ผ์ ๊ด๋ฆฌํด์ฃผ๋ ๋ชจ๋์ด๋ค.
๊ธฐ๋ณธ์ ์ผ๋ก ํ๋ฃจ(1์ผ) ๋จ์๋ก ์ ๋ก๊ทธ ํ์ผ์ ์์ฑํด์ฃผ๊ณ , ๋ ์ง๋ณ๋ก ๋ก๊ทธํ์ผ์ ๊ด๋ฆฌํ๊ฒ ๊ตฌ๋ถํด ์ฃผ๋ฉฐ, ๋ก๊ทธ ํ์ผ์ ์ต๋ ํฌ๊ธฐ์ ์ต๋ ์ ์ฅ ํ์ผ ๊ฐ์ ๋ฑ์ ์ค์ ํ ์ ์๋ค.
1. winston ๋ก๊ทธ ๊ฒฝ๋ก & ์ถ๋ ฅํ์ ์ค์
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 ๋ก๊ฑฐ ์์ฑ (createLogger)
// ...
const logger = winston.createLogger({
//* ๋ก๊ทธ ์ถ๋ ฅ ํ์ ์ ์
format: combine(
timestamp({ format: 'YYYY-MM-DD HH:mm:ss' }),
label({ label: 'Winston ์ฐ์ต ์ดํ๋ฆฌ์ผ์ด์
' }), // ์ดํ๋ฆฌ์ผ์ด์
์ด๋ฆ
logFormat, // log ์ถ๋ ฅ ํฌ๋งท
//? format: combine() ์์ ์ ์ํ timestamp์ label ํ์๊ฐ์ด logFormat์ ๋ค์ด๊ฐ์ ์ ์๋๊ฒ ๋๋ค. level์ด๋ message๋ ์ฝ์์์ ์๋ ์ ์
),
});
์ด์ ๋ณธ๊ฒฉ์ ์ผ๋ก ๋ก๊ทธ ๊ธฐ๋ก(logging)์ ํ๋ ๋ฉ์๋๋ฅผ ์์ฑํด๋ณผ ์ฐจ๋ก์ด๋ค.
winston ํจํค์ง์ createLogger ๋ฉ์๋๋ก logger๋ฅผ ๋ง๋ค ์ ์๋ค.
๊ทธ๋ฆฌ๊ณ format ์ธ์๋ฅผ ์ค์ ๋ฉ์ธ์ง์ ๋ํ ๊ธฐ๋ณธ ์ค์ ์ ํ๋ค.
format์ ๋ก๊ทธ์ ํ์์ ์ง์ ํ ์ ์๋ค.
json, label, timestamp, printf, simple, combine ๋ฑ์ ๋ค์ํ ํ์์ผ๋ก ์ง์ ์ด ๊ฐ๋ฅํ๋ค.
๊ธฐ๋ณธ์ ์ผ๋ก๋ JSON ํ์์ผ๋ก ๊ธฐ๋กํ์ง๋ง, combine์ ์ฌ๋ฌ ํ์์ ํผํฉํด์ ์ฌ์ฉํ ๋ ์ด๋ค.
timestamp๋ก ๋ ์ง ํ์์ ์ ํ๊ณ , label๋ก ์ดํ๋ฆฌ์ผ์ด์ ์ด๋ฆ์ ์ง์ ํด์ค๋ค. ๊ทธ๋ฆฌ๊ณ ์์์ ์ง์ ํ logFormat ์ ๋ฃ์ด, ์ค์ ํ timestamp์ label์ ์ธ์๋ก ๋ฐ์ printf ๋๊ฒ ์ค์ ํด์ค๋ค.
๋๋จธ์ง ์ธ์์ level๊ณผ message๋ ์๋์ผ๋ก ์ฝ์์์ ์ง์ ๋๋ค.
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`, // ์๋ฌ ๋ก๊ทธ๋ 2020-05-28.error.log ํ์์ผ๋ก ์ ์ฅ
maxFiles: 30,
zippedArchive: true,
}),
],
});
๊ทธ๋ฆฌ๊ณ ๋ค์ ํ๋ผ๋ฏธํฐ์ transports ๋ฅผ ์ค์ ํ๋ค.
transports๋ ๋ก๊ทธ ์ ์ฅ ๋ฐฉ์์ ์ ์ํ๋ค.
๊ทธ๋ฆฌ๊ณ winston-daily-rotate-file ์์ ๋ถ๋ฌ์จ new winstonDaily ๋ฅผ ์ ์ํด ์ด๋ค ๋ ๋ฒจ์ ๋ก๊ทธ๋ฅผ ์ ์ฅํ ๋ ์ด๋ค ํ์์ผ๋ก ๋ช์ผ๋์ ๋ณด๊ดํ ์ง๋ฅผ ์์ธํ ์ค์ ํ ์ ์๋ค.
winston์ ๋ก๊ทธ ๋ ๋ฒจ์ ์์๊ฐ ์๋๋ฐ, ๋ค์๊ณผ ๊ฐ์ด ๊ตฌ์ฑ๋์ด์๋ค.
error: 0 , warn: 1 , info: 2 , http: 3 , verbose: 4 , debug: 5 , silly: 6
์ซ์๊ฐ ๋ฎ์ ์๋ก priority๊ฐ ๋๋ค๊ณ ๋ณด๋ฉด ๋๋ค. (error ๊ฐ ๊ฐ์ฅ ์ํํ ๋ก๊ทธ)
winston ๋ก๊ทธ์ level์ ์ค์ ํ๋ฉด ํด๋น ๋ ๋ฒจ ์ด์์ priority๋ฅผ ๊ฐ์ง๋ ์ฆ, ์ซ์๊ฐ ๊ฐ๊ฑฐ๋ ๋ฎ์ ๋ก๊ทธ๋ฅผ ํจ๊ป ์ถ๋ ฅํ๊ฒ ๋๋ค. (๋ง์ผ level์ info๋ก ์ค์ ํ๋ฉด 2๋ณด๋ค ๋ฎ์ error์ warn ๋ก๊ทธ๋ ๊ฐ์ด ์ถ๋ ฅ)
์์ ์ฝ๋์์๋ info์ error๋ฅผ ๋ฐ๋ก ์ง์ ํด์ ๋ก๊น ํ์์ ์ค์ ํด ์ฃผ์๋ค. ๋ณด๋ค ๊ด๋ฆฌ๋ฅผ ์ํด error ๋ก๊น ์ ๋ค๋ฅธ ํ์ผ๋ช ๊ณผ ๋ค๋ฅธ ํด๋์ ์ ์ฅํ๋๋ก ์ถ๊ฐ๋ก ์ง์ ํ๋ค.
์ด์ธ์ winston daily rotate ๋ก๊น ์ค์ ์ ๋ค์๊ณผ ๊ฐ๋ค.
new winstonDaily({
frequency: ํ์ ๋น๋๋ฅผ ๋ํ๋ด๋ ๋ฌธ์์ด์
๋๋ค. ์ด๋ ํน์ ์๊ฐ์ ๋ฐ์ํ๋ ํ์ ๊ณผ ๋ฌ๋ฆฌ ์๊ฐ์ด ์ง์ ๋ ํ์ ์ ์ํ๋ ๊ฒฝ์ฐ์ ์ ์ฉํฉ๋๋ค. ์ ํจํ ๊ฐ์ '#m' ๋๋ '#h'(์: '5m' ๋๋ '3h')์
๋๋ค. ์ด null์ ๋จ๊ฒจ๋๋ datePattern๊ฒ์ ํ์ ์๊ฐ ์ ์์กดํฉ๋๋ค . (๊ธฐ๋ณธ๊ฐ: null)
datePattern: ํ์ ์ ์ฌ์ฉํ moment.js ๋ ์ง ํ์ ์ ๋ํ๋ด๋ ๋ฌธ์์ด ์
๋๋ค. ์ด ๋ฌธ์์ด์ ์ฌ์ฉ๋ ๋ฉํ ๋ฌธ์๋ ํ์ผ ํ์ ๋น๋๋ฅผ ๋ํ๋
๋๋ค. ์๋ฅผ ๋ค์ด, datePattern์ด ๋จ์ํ 'HH'์ธ ๊ฒฝ์ฐ ๋งค์ผ ์ ํํ์ฌ ์ถ๊ฐ๋๋ 24๊ฐ์ ๋ก๊ทธ ํ์ผ๋ก ๋๋ฉ๋๋ค. (๊ธฐ๋ณธ๊ฐ: 'YYYY-MM-DD')
zippedArchive: ์์นด์ด๋ธ๋ ๋ก๊ทธ ํ์ผ์ gzip์ผ๋ก ์์ถํ ์ง ์ฌ๋ถ๋ฅผ ์ ์ํ๋ ๋ถ์ธ์
๋๋ค. (๊ธฐ๋ณธ๊ฐ: '๊ฑฐ์ง')
filename: ๋ก๊ทธ์ ์ฌ์ฉํ ํ์ผ ์ด๋ฆ์
๋๋ค. ์ด ํ์ผ ์ด๋ฆ์ ํ์ผ ์ด๋ฆ์ %DATE%ํด๋น ์ง์ ์ ์์์ด ์ง์ ๋ datePattern์ ํฌํจํ๋ ์๋ฆฌ ํ์์๋ฅผ ํฌํจํ ์ ์์ต๋๋ค . (๊ธฐ๋ณธ๊ฐ: 'winston.log.%DATE%')
dirname: ๋ก๊ทธ ํ์ผ์ ์ ์ฅํ ๋๋ ํฐ๋ฆฌ ์ด๋ฆ์
๋๋ค. (๊ธฐ๋ณธ: '.')
stream: ์ฌ์ฉ์ ์ง์ ์คํธ๋ฆผ์ ์ง์ ์ฐ๊ณ ํ์ ๊ธฐ๋ฅ์ ์ฐํํฉ๋๋ค. (๊ธฐ๋ณธ๊ฐ: null)
maxSize: ํ์ ํ ํ์ผ์ ์ต๋ ํฌ๊ธฐ์
๋๋ค. ๋ฐ์ดํธ ์ ๋๋ kb, mb ๋ฐ GB ๋จ์๊ฐ ๋ ์ ์์ต๋๋ค. ๋จ์๋ฅผ ์ฌ์ฉํ๋ ๊ฒฝ์ฐ ์ ๋ฏธ์ฌ๋ก 'k', 'm' ๋๋ 'g'๋ฅผ ์ถ๊ฐํฉ๋๋ค. ๋จ์๋ ์ซ์๋ฅผ ์ง์ ๋ฐ๋ผ์ผ ํฉ๋๋ค. (๊ธฐ๋ณธ๊ฐ: null)
maxFiles: ๋ณด๊ดํ ์ต๋ ๋ก๊ทธ ์์
๋๋ค. ์ค์ ํ์ง ์์ผ๋ฉด ๋ก๊ทธ๊ฐ ์ ๊ฑฐ๋์ง ์์ต๋๋ค. ์ด๋ ํ์ผ ์ ๋๋ ์ผ ์์ผ ์ ์์ต๋๋ค. ์ผ์ ์ฌ์ฉํ๋ ๊ฒฝ์ฐ ์ ๋ฏธ์ฌ๋ก 'd'๋ฅผ ์ถ๊ฐํฉ๋๋ค. (๊ธฐ๋ณธ๊ฐ: null)
options: ํ์ผ ์คํธ๋ฆผ์ ์ ๋ฌ๋์ด์ผ ํ๋ ์ถ๊ฐ ์ต์
์ ๋ํ๋ด๋ 'https://nodejs.org/api/fs.html#fs_fs_createwritestream_path_options' ์ ์ ์ฌํ ๊ฐ์ฒด . (๊ธฐ๋ณธ๊ฐ: { flags: 'a' })
auditFile : ๊ฐ์ฌ ํ์ผ์ ์ด๋ฆ์ ๋ํ๋ด๋ ๋ฌธ์์ด. ์ต์
๊ฐ์ฒด์ ํด์๋ฅผ ๊ณ์ฐํ์ฌ ์์ฑ๋ ๊ธฐ๋ณธ ํ์ผ ์ด๋ฆ์ ์ฌ์ ์ํ๋ ๋ฐ ์ฌ์ฉํ ์ ์์ต๋๋ค. (๊ธฐ๋ณธ๊ฐ: '..json')
utc : ํ์ผ ์ด๋ฆ์ ๋ ์ง์ UTC ์๊ฐ์ ์ฌ์ฉํฉ๋๋ค. (๊ธฐ๋ณธ๊ฐ: ๊ฑฐ์ง)
extension : ํ์ผ ์ด๋ฆ์ ์ถ๊ฐํ ํ์ผ ํ์ฅ์. (๊ธฐ๋ณธ: '')
createSymlink : ํ์ฌ ํ์ฑ ๋ก๊ทธ ํ์ผ์ ๋ํ tailable symlink๋ฅผ ๋ง๋ญ๋๋ค. (๊ธฐ๋ณธ๊ฐ: ๊ฑฐ์ง)
symlinkName : tailable symlink์ ์ด๋ฆ์
๋๋ค. (๊ธฐ๋ณธ๊ฐ: 'current.log')
}),
4. winston ์์ธ ๋ก๊ทธ (exceptionHandlers)
์ถ๊ฐ๋ก ์ฝ๋ ์๋ฌ ๋ฟ๋ง ์๋๋ผ try catch ์๋ฌ๋ ์ก๊ธฐ ์ํด ๋ฐ์ exceptionHandlers ๋ฅผ ์ค์ ํด ์ฃผ์๋ค.
/*
* 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 ๋ ๋ฒจ์์
// ...
}),
//* error ๋ ๋ฒจ ๋ก๊ทธ๋ฅผ ์ ์ฅํ ํ์ผ ์ค์ (info์ ์๋ ํฌํจ๋์ง๋ง ์ผ๋ถ๋ฌ ๋ฐ๋ก ๋นผ์ ์ค์ )
new winstonDaily({
level: 'error', // error ๋ ๋ฒจ์์
// ...
}),
],
//* uncaughtException ๋ฐ์์ ํ์ผ ์ค์
exceptionHandlers: [
new winstonDaily({
level: 'error',
datePattern: 'YYYY-MM-DD',
dirname: logDir,
filename: `%DATE%.exception.log`,
maxFiles: 30,
zippedArchive: true,
}),
],
});
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 })` ํฌ๋งท์ผ๋ก ์ถ๋ ฅ
),
}),
);
}
module.exports = logger;
Production ํ๊ฒฝ์ด ์๋, ๊ฐ๋ฐ ํ๊ฒฝ์ผ ๊ฒฝ์ฐ ํ์ผ ๋ค์ด๊ฐ์ ์ผ์ผํ ๋ก๊ทธ ํ์ธํ๊ธฐ ๋ฒ๊ฑฐ๋ก์ฐ๋๊น ํ๋ฉด์์ ๋ฐ๋ก ์ฐ๊ฒ ์ค์ ํด์ค๋ค. ๊ทธ๋๋ ๋ก๊ทธ ํ์ผ์ ์ฌ์ ํ ์์ฑ๋๋ค.
๊ทธ๋ฆฌ๊ณ ๋ง์ง๋ง์ผ๋ก ๋ชจ๋ ๋ฐฐํฌ๋ฅผ ์ํด module.exports ๋ฅผ ํด์ค๋ค.
์ด๋ ๊ฒ logger ๊ฐ์ฒด๋ฅผ ๋ง๋ค์ด ๋ค๋ฅธ ํ์ผ์์ ์ฌ์ฉํ๋ฉด ๋๋ค.
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}`; // ๋ ์ง [์์คํ
์ด๋ฆ] ๋ก๊ทธ๋ ๋ฒจ ๋ฉ์ธ์ง
});
/*
* 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' }),
label({ label: 'Winston ์ฐ์ต ์ดํ๋ฆฌ์ผ์ด์
' }), // ์ดํ๋ฆฌ์ผ์ด์
์ด๋ฆ
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, // ํ์ผ ๊ฒฝ๋ก
filename: `%DATE%.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,
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(), // ์๊น ๋ฃ์ด์ ์ถ๋ ฅ
winston.format.simple(), // `${info.level}: ${info.message} JSON.stringify({ ...rest })` ํฌ๋งท์ผ๋ก ์ถ๋ ฅ
),
}),
);
}
module.exports = logger;
const logger = require("./logger");
logger.info("hello world");
logger.error("hello world");
logger.warn("hello world");
logger.debug("hello world");
logger.verbose("hello world");
logger.silly("hello world");

info, warn, error ๋ฑ์ ๋ฉ์๋๋ฅผ ์ฌ์ฉํ๋ฉด ํด๋น ์ฌ๊ฐ๋๊ฐ ์ ์ฉ๋ ๋ก๊ทธ๊ฐ ๊ธฐ๋ก๋๊ฒ ๋๋ค.
๋ง์ผ ๋ฉ์ธ์ง๋ฅผ ์ฌ๋ฌ๋ฒ ์จ์ผํ ๊ฒฝ์ฐ์๋ ๋ค์๊ณผ ๊ฐ์ด ๊ตฌ์ฑํด์ค๋ค.
// console.log('naver profile : ', profile);
logger.info('naver profile : ', { message: profile }); // console.log์ ๋ฌ๋ฆฌ ๋ค์ message ๊ฐ์ฒด๋ก ์จ์ฃผ์ด์ผ ํ๋ค.
๋ชจ๋ ์๋์ ๋ฌธ์ ์๋ค๋ฉด, ๋ค์๊ณผ ๊ฐ์ด ๊ฐ๋จํ๊ฒ ์๋ฒ ์ฝ๋๋ฅผ ์์ฑํด์ฃผ๊ณ ๋ ธ๋ ์๋ฒ๋ฅผ ์คํํด๋ณธ๋ค.
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 ํด๋์ ๋ก๊ทธ ํ์ผ์ด ์ ํ๊ฒ ๋๋ค.


info ์ด์ ๋จ๊ณ์ ๋ชจ๋ ๋ก๊ทธ๋ฅผ ๊ธฐ๋กํ๋๋ก ๋์ด ์๋ 2022-05-30.log ํ์ผ์๋ info์ error ๋จ๊ณ์ ๋ก๊ทธ๊ฐ ์ถ๋ ฅ ๋๋ค.
๊ทธ๋ฆฌ๊ณ error ๋จ๊ณ์ ๋ก๊ทธ๋ง ๊ธฐ๋กํ๋๋ก ๋์ด ์๋ 2022-05-30.error.log์๋ error ๋จ๊ณ์ ๋ก๊ทธ๋ง ์ถ๋ ฅ ๋์๋ค.
์ด๋ ๊ฒ ๋ก๊ทธ๋ฅผ ์ฝ์์๋ง ์ถ๋ ฅํ๋ ๊ฒ์ด ์๋๋ผ, ํ์ผ๋ก๋ ์ ์ฅํ ์ ์์ด ์ค์ ์๋น์ค๋ฅผ ์ด์ํ ๋ ์ ์ฉํ๋ค.


.42ebfd898bd6a617661d9d083ce1a25eb6d87249-audit.json์ Winston์์ ๋ก๊ทธ ํ์ผ์ ๋งต์ ๋ง๋๋ ๋ฐ ์ฌ์ฉํ๋ ์ค์ํ ํ์ผ์ด๋ค. ๋ง์ผ ์ด ํ์ผ์ด ์์ผ๋ฉด ๋งค์ผ ๋ ์ง ๊ฐฑ์ ๊ธฐ๋ฅ ์ ์ฌ์ฉํ ์ ์๋ค.
Winston + Morgan ๊ฐ์ด ์ฌ์ฉํ๊ธฐ
http ์์ฒญ ์๋ต์ ์ํ ๋ก๊น ์ธ morgan์ winston์ผ๋ก ๊ด๋ฆฌํด ํด๋น ๋ก๊ทธ๋ฅผ ์ธ๋ถ ๋ก๊ทธ ํ์ผ์ ์ถ๋ ฅ๋๋๋ก ํด๋ณด์.
์์์ winston ๋ก๊ทธ level์ http ๋ ๋ฒจ์ด ์๋ค๋๊ฑธ ๋ดค์ํ ๋ฐ ๋ฐ๋ก ์ฌ๊ธฐ์ ์ฐ์ด๋ ๊ฒ์ด๋ค.
๋ง์ผ ์์ ์ฝ๋์ morgan ํ์ฅ ์ต์ ์ ๋ํด ์ ๋ชจ๋ฅธ๋ค๋ฉด ๋ค์ ํฌ์คํ ์ ์ฐธ๊ณ ํ๊ธธ ๋ฐ๋๋ค.
[EXPRESS] ๐ morgan ๋ฏธ๋ค์จ์ด ๐ฏ ์ฌ์ฉ๋ฒ ์ ๋ฆฌ
morgan ๋ชจ๋ morgan์ ์ฐ๊ฒฐ ํ ํฌํธ์ ์ ์ํ๋ฉด ๊ธฐ์กด ๋ก๊ทธ ์ธ์ ์ถ๊ฐ์ ์ธ ๋ก๊ทธ๋ฅผ ๋ณผ ์ ์๋ค. ์ ์ฝ๋๋ฅผ ์คํํ์ฌ 3000๋ฒ ํฌํธ์ ๋ค์ด๊ฐ ํ ์ฝ์์ ๋ณด๋ฉด ์๋์ ๊ฐ์ ๋ก๊ทธ๊ฐ ์ฐํ์๋ ๊ฒ์ ๋ณผ ์ ์๋ค.
inpa.tistory.com
๋ค์๊ณผ ๊ฐ์ด custom morgan middleware๋ฅผ ๋ง๋ ๋ค.
const morgan = require('morgan');
const logger = require('../config/logger');
const dotenv = require('dotenv');
dotenv.config(); // ๋
ธ๋ ํ๊ฒฝ ๋ณ์ ์ฌ์ฉ
const format = () => {
const result = process.env.NODE_ENV === 'production' ? 'combined' : 'dev';
return result;
};
// ๋ก๊ทธ ์์ฑ์ ์ํ Output stream์ต์
.
const stream = {
write: (message) => {
// console.log(message);
logger.info(message);
},
};
// ๋ก๊น
์คํต ์ฌ๋ถ (๋ง์ผ ๋ฐฐํฌํ๊ฒฝ์ด๋ฉด, ์ฝ๋๊ฐ 400 ๋ฏธ๋ง๋ผ๋ฉด ํจ์๋ฅผ ๋ฆฌํดํด ๋ฒ๋ ค์ ๋ก๊ทธ ๊ธฐ๋ก ์ํจ. ์ฝ๋๊ฐ 400 ์ด์์ด๋ฉด ๋ก๊ทธ ๊ธฐ๋กํจ)
const skip = (_, res) => {
if (process.env.NODE_ENV === 'production') {
return res.ststusCode < 400;
}
return false;
};
//? ์ ์ฉ๋ moran ๋ฏธ๋ค์จ์ด ํํ
const morganMiddleware = morgan(format(), { stream, skip });
/*
morgan('dev', {
stream = {
write: (message) => {
// console.log(message);
logger.info(message);
},
},
skip = (_, res) => {
if (process.env.NODE_ENV === 'production') {
return res.ststusCode < 400;
}
return false;
};
})
*/
module.exports = morganMiddleware;
๊ทธ๋ฆฌ๊ณ custom morgan middleware๋ฅผ ์๋ฒ ํ์ผ app.js์ ๋ค์๊ณผ ๊ฐ์ด ์ ์ฉํด์ฃผ๋ฉด ๋๋ค.
//app.js
const morganMiddleware = require('morganMiddleware');
// ...
app.use(morganMiddleware)
์ด์ํ ํน์ ๋ฌธ์ ์ถ๋ ฅ ํด๊ฒฐํ๊ธฐ
๋ง์ผ winston๊ณผ morgan์ ํจ๊ป ์ ์ฉํ๋์ด ์๋ ์ฌ์ง๊ณผ ๊ฐ์ด ์ด์ํ ํน์๋ฌธ์ ESC[0m ๊ฐ ์ถ๋ ฅ ๋๋ค๋ฉด, logger.info(message)์ message ๋ถ๋ถ ๋ก๊ทธ ๋ฌธ์์ด์ ์ ๊ท์์ผ๋ก ์ ๊ฑฐํ๋ฉด ๋๋ค.
(์ง์ ํด์ฃผ์ ๊ฐ๋ฐ ํ ๋ผ ๋๊ป ๊ฐ์ฌ์ ์ธ์ฌ๋ฅผ ๋๋ฆฐ๋ค)

// ๋ก๊ทธ ์์ฑ์ ์ํ Output stream์ต์
.
const stream = {
write: (message) => {
logger.info(
message.replace(/[\u001b\u009b][[()#;?]*(?:[0-9]{1,4}(?:;[0-9]{0,4})*)?[0-9A-ORZcf-nqry=><]/g, "")
);
},
};
์ด ๊ธ์ด ์ข์ผ์ จ๋ค๋ฉด ๊ตฌ๋ & ์ข์์
์ฌ๋ฌ๋ถ์ ๊ตฌ๋
๊ณผ ์ข์์๋
์ ์์๊ฒ ํฐ ํ์ด ๋ฉ๋๋ค.