winston
๋ฒ์ ?_winston@2
winston@3
node -v
์ถ๋ ฅ:_ v8.11.3
3.xx ์ฌ์ฉ ์:
const winston = require('winston')
const logger = winston.createLogger({
transports: [new winston.transports.Console()]
})
logger.info('test log', 'with a second parameter')
์ถ๋ ฅ:
{"level":"info","message":"test log"}
2.xx ์ฌ์ฉ:
const winston = require('winston')
const logger = new winston.Logger({
transports: [new winston.transports.Console()]
})
logger.info('test log', 'with a second parameter')
์ถ๋ ฅ:
info: test log with a second parameter
๋ค์์ ์ถ๋ ฅํด์ผ ํฉ๋๋ค.
{"level":"info","message":"test log with a second parameter"}
๋๋ ์ค๋ ๊ฐ์ ๋ฌธ์ ์ ๋ถ๋ช์ณค๋ค. ๋๋ ์ ๊ทธ๋ ์ด๋ ๋ฌธ์๋ ๊ทธ์ ๊ฐ์ ์๋ฅผ ๊ฐ์ง๊ณ ์๊ธฐ ๋๋ฌธ์์ด ๋ฒ๊ทธ๊ฐ ๋ฏฟ๊ณ ์ฌ๊ธฐ๋ฅผ :
logger.info('transaction ok', { creditCard: 123456789012345 });
ํ์ฌ ์ด ๋ฌธ์ ๋ฅผ ํด๊ฒฐํ๊ธฐ ์ํด winston์ ์ ๋ฌ๋๊ธฐ ์ ์ ์ธ์๋ฅผ ๊ตฌ๋ฌธ ๋ถ์ํ๋ ํจ์๋ฅผ ๋ง๋ค ์ ์์ต๋๋ค.
@mulligan121 ์๊ฒ ์ต๋๋ค. ํ์ง๋ง ์ ์ฝ๋๋ฒ ์ด์ค์ ๋ชจ๋ ๋ก๊ทธ ๋ฌธ์ ๊ต์ฒดํ๊ณ ์ถ์ง๋ ์์ต๋๋ค...
@indexzero ์ด๊ฒ์ด ๋น์ ์ ๋ ์ด๋์ ์์ต๋๊น? ์ ๋ ์ด๊ฒ์ด ํ๊ธฐ์ ์ธ ๋ณํ๋ผ๊ณ ์๊ฐํฉ๋๋ค. ๋ชจ๋ ๋ก๊ทธ ํญ๋ชฉ์ ๋ณ๊ฒฝํด์ผ ํ๋ฏ๋ก ์ค์ ๋ก 2.x์์ 3.x๋ก ์ด๋ํ๋ ๊ฒ์ ๋ฐฉ์งํฉ๋๋ค.
์์ ํด๊ฒฐ ๋ฐฉ๋ฒ:
const wrapper = ( original ) => {
return (...args) => original(args.join(" "));
};
winstonLogger.error = wrapper(winstonLogger.error);
winstonLogger.warn = wrapper(winstonLogger.warn);
winstonLogger.info = wrapper(winstonLogger.info);
winstonLogger.verbose = wrapper(winstonLogger.verbose);
winstonLogger.debug = wrapper(winstonLogger.debug);
winstonLogger.silly = wrapper(winstonLogger.silly);
์ด๊ฒ์ ์ฐ๋ฆฌ์๊ฒ ์์ฒญ๋ ๋ณํ์ ๋๋ค. ์๋์ ์ด๋ผ๋ฉด https://github.com/winstonjs/winston/blob/master/UPGRADE-3.0.md์ ์์ธํ ๋์ ์์ด์ผ ํฉ๋๋ค.
๊ทธ๋์ ์ข ๋ ์กฐ์ฌํ ํ์ ์ฌ๋ฌ ์ธ์๋ฅผ ์ธ์ํ๋ ค๋ฉด ์คํ๋ซ ํฌ๋งทํฐ๊ฐ ํ์ํ๋ค๋ ๊ฒ์ ๋ฐ๊ฒฌํ์ต๋๋ค. ๋๋ ๊ทธ๊ฒ์ด ์ธ์ ๋ณด๊ฐ(์ฆ, %s ๋ฑ์ด ํฌํจ๋ ๊ฒ)์ ์ํ ๊ฒ์ด๋ผ๊ณ ์๊ฐํ์ง๋ง logger.info("something", value)
ํ๊ธฐ ์ํด ํ์ํ ๊ฒ ๊ฐ์ต๋๋ค.
๊ทธ๋ฌ๋ ์ด๊ฒ์ ์ฌ์ ํ โโ๋์๊ฒ ์ฝ๊ฐ ์ด์ํฉ๋๋ค. JSON ํ์์ ์ฌ์ฉํ์ง ์๋๋ผ๋ ์ถ๋ ฅ์์ โโ"๋ฉํ" ํค๊ฐ ์๋ JSON์ฒ๋ผ ๋ณด์ด๋ ๊ฒ์ ์ป๊ณ ์์ต๋๋ค.
logger.info('Test: %s', 1, 2, 3, 4);
์์ฐ:
info: Test: 1 {"meta":[2,3,4]}
์์ ์ ์์์๋ ๋ค์๊ณผ ๊ฐ์ด ๋งํ๋ ๋ด์ฉ์ ์์ฑํ์ง ์์ต๋๋ค.
https://github.com/winstonjs/winston/blob/master/examples/interpolation.js#L20 -L21
info: test message first, second {"meta":{"number":123}}
๋ค์ ๋ผ์ธ์ ๋ฐ๋ผ ๋ฌด์ธ๊ฐ๋ฅผ ์ฌ์ฉํ์ฌ ํด๊ฒฐํ์ต๋๋ค.
const { version } = require('../package');
const { createLogger, format, transports } = require('winston');
const { combine, timestamp, colorize, label, printf, align } = format;
const { SPLAT } = require('triple-beam');
const { isObject } = require('lodash');
function formatObject(param) {
if (isObject(param)) {
return JSON.stringify(param);
}
return param;
}
// Ignore log messages if they have { private: true }
const all = format((info) => {
const splat = info[SPLAT] || [];
const message = formatObject(info.message);
const rest = splat.map(formatObject).join(' ');
info.message = `${message} ${rest}`;
return info;
});
const customLogger = createLogger({
format: combine(
all(),
label({ label: version }),
timestamp(),
colorize(),
align(),
printf(info => `${info.timestamp} [${info.label}] ${info.level}: ${formatObject(info.message)}`)
),
transports: [new transports.Console()]
});
๋ค์์ ์ฌ๋ฌ ๋งค๊ฐ๋ณ์๋ฅผ ํ์ฉํ๋ ์ผ๋ฐ์ ์ธ ํด๊ฒฐ ๋ฐฉ๋ฒ์
๋๋ค.
https://github.com/rooseveltframework/roosevelt/blob/master/lib/tools/logger.js#L29
์ ์ฐ๊ด๋๋ค:
https://github.com/winstonjs/winston/issues/1377
๋๊ฐ์ด ์๋ํ์ง ์๋ ๊ฒ๋ค์ด ๋๋ฌด ๋ง์ต๋๋ค.
์๋ ํ์ธ์, ์ ๋ฐ์ดํธ๊ฐ ์์ต๋๊น? ๊ฐ์ฌ ํด์.
๋ํ winston์์ console.log(...args)
๋ฅผ ์๋ฎฌ๋ ์ดํธํ๋ ์ ์ ํ ๋ฐฉ๋ฒ์ ๊ธฐ๋ค๋ฆฌ๊ณ ์์ต๋๋ค...
splat
๋ฐ meta
in 3.2.0
์ฃผ๋ณ์ ๋ชจ์๋ฆฌ/๋ชจ์๋ฆฌ ์ผ์ด์ค๋ฅผ ์์ ํ์ง๋ง ์ฌ์ ํ ๋ฌธ์ ์ธ ๊ฒ์ผ๋ก ๋ณด์
๋๋ค(https://github.com ์ฐธ์กฐ). /winstonjs/winston/pull/1576 CHANGELOG.md
).
์ด๊ฒ์ด 3.3.0
์์ ์ฒ๋ฆฌ๋๋์ง ํ์ธํฉ๋๋ค. ๊ธฐ๋ค๋ ค ์ฃผ์
์ ๊ฐ์ฌํฉ๋๋ค.
์ด๊ฒ์ ๋๋ฅผ ์ํ ํด๊ฒฐ ๋ฐฉ๋ฒ์ด์์ต๋๋ค.
'use strict';
const { createLogger, format, transports } = require('winston');
const { SPLAT } = require('triple-beam');
const { combine, timestamp, label, printf, colorize } = format;
const formatObject = (param) => {
if (typeof param === 'string') {
return param;
}
if (param instanceof Error) {
return param.stack ? param.stack : JSON.stringify(param, null, 2);
}
return JSON.stringify(param, null, 2);
};
const logFormat = printf((info) => {
const { timestamp: ts, level, message } = info;
const rest = info[SPLAT] || [];
const msg = info.stack ? formatObject(info.stack) : formatObject(message);
let result = `${ts} ${level}: ${msg}`;
if (rest.length) {
result += `\n${rest.map(formatObject).join('\n')}`;
}
return result;
});
const logger = createLogger({
format: combine(
label({ label: 'app' }),
timestamp(),
logFormat,
),
transports: [
new transports.Console({
format: combine(colorize()),
handleExceptions: true,
}),
],
exceptionHandlers: [
new transports.File({ filename: 'exceptions.log' }),
],
});
์ธ์๋ฅผ ์ฌ์ฉํ์ฌ ๊ธฐ๋กํ๋ ค๊ณ ํ๋ฉด ์ด์ํ ์ถ๋ ฅ์ด ๋ํ๋ฉ๋๋ค.
var s = "Hello"
log.info("asdasda", s)
์ถ๋ ฅ ๊ฐ์ ธ์ค๊ธฐ
{"0":"H","1":"e","2":"l","3":"l","4":"o","service":"XXXX","level":"info","message":"asdasda","timestamp":"2019-04-15 13:58:51"}
์ผ๋ฐ ๋น ๋ฅธ ์์ ์ฝ๋๋ฅผ ์ฌ์ฉํ์ต๋๋ค.
๋ด๊ฐ ์ฃผ๋ชฉํ ํ ๊ฐ์ง๋ ๋ค์๊ณผ ๊ฐ์ต๋๋ค.
์ด ๋ฌธ์ ๊ฐ ํด๊ฒฐ๋ ์ ์๊ธฐ๋ฅผ ๋ฐ๋๋๋ค.
์ฐธ๊ณ : Winston์ ์ต์ ๋ฆด๋ฆฌ์ค ๋ฒ์ ์ผ๋ก ์ด๊ฒ์ ํ ์คํธํ์ต๋๋ค. ์ฝ๋๋ฅผ ๊ฐ๋จํ ์ดํด๋ณด๋ฉด ๋ง์คํฐ์์ ์ฌ์ ํ ์ด์ ์ ์ฌํ ๊ฒ์ผ๋ก ๋ณด์ ๋๋ค(๋ค๋ฅธ ์์ ์ฌํญ์ด ์๋ ๊ฒ ๊ฐ์ต๋๋ค).
@luislobo ์ ์๋ฃจ์ ์ ๋ํ ๋ช ๊ฐ์ง ๊ฐ์ ์ฌํญ ๐
logger.info(`hello %s`,'world')
์ ๊ฐ์ ๋ฉ์์ง์ ๋ํด %s %d or %j
๊ฐ ํฌํจ๋ ํ์ ์คํ์ผ์ธ ๊ฒฝ์ฐ ๋ฉ์์ง ๋ฌด์printf
์์ twise formateObject๋ฅผ ์ ๊ฑฐํ์ต๋๋ค.trimEnd
๋ฃจ๊ฑฐ์ ์ถ๊ฐ ์ธ์๊ฐ ์ ๊ณต๋์ง ์์ ๊ฒฝ์ฐconst { version } = require('../package');
const { createLogger, format, transports } = require('winston');
const { combine, timestamp, colorize, label, printf, align } = format;
const { SPLAT } = require('triple-beam');
const { isObject,trimEnd } = require('lodash');
function formatObject(param) {
if (isObject(param)) {
return JSON.stringify(param);
}
return param;
}
const all = format((info) => {
const splat = info[SPLAT] || []
const isSplatTypeMessage =
typeof info.message === 'string' &&
(info.message.includes('%s') || info.message.includes('%d') || info.message.includes('%j'))
if (isSplatTypeMessage) {
return info
}
const message = formatObject(info.message)
const rest = splat
.map(formatObject)
.join(' ')
info.message = trimEnd(`${message} ${rest}`)
return info
});
const customLogger = createLogger({
format: combine(
colorize(),
all(),
label({ label: version }),
timestamp(),
align(),
printf(info => `${info.timestamp} [${info.label}] ${info.level}: ${info.message}`)
),
transports: [new transports.Console()]
});
๋๋ฝ๋ ์ถ๊ฐ ์ ๋ฐ์ดํธ๊ฐ ์์ต๋๊น? ์ด ๊ธฐ๋ฅ์ด ๋ค์ ์ถ๊ฐ๋ ๊ฒ์ด๋ผ๋ ๊ฐ๋ ฅํ ์ง์์ด ์๋ ๊ฒ ๊ฐ์ง๋ง, ์ด์ ๋ํ ๋ง์ง๋ง ์ฝ์์ ๊ฑฐ์ 6๊ฐ์ ์ ์ด์์ต๋๋ค.
์๋ ํ์ธ์, ๊ฑฐ๋ํ ๋ฒํผ์ ์ผ ์คํ ํผ๋ 3.X๋ก ๋ง์ด๊ทธ๋ ์ด์ ํ๋ ค๊ณ ์๋ํ๋ฉฐ 2.x๋ฅผ ํ๋์ ์ ์งํ ๊ฒ์ ๋๋ค.
์กฐ๊ธ ์ค๋ง :(
์ด๊ฒ์ด ๋๋ฅผ ์ํด ์ผํ ๊ฒ์ ๋๋ค.
const { format, createLogger, transports } = require('winston');
const jsonStringify = require('fast-safe-stringify');
const logLikeFormat = {
transform(info) {
const { timestamp, label, message } = info;
const level = info[Symbol.for('level')];
const args = info[Symbol.for('splat')];
const strArgs = args.map(jsonStringify).join(' ');
info[Symbol.for('message')] = `${timestamp} [${label}] ${level}: ${message} ${strArgs}`;
return info;
}
};
const debugFormat = {
transform(info) {
console.log(info);
return info;
}
};
const logger = createLogger({
format: format.combine(
// debugFormat, // uncomment to see the internal log structure
format.timestamp(),
format.label({ label: 'myLabel' }),
logLikeFormat,
// debugFormat, // uncomment to see the internal log structure
),
transports: [
new transports.Console()
]
});
logger.info('foo', 'bar', 1, [2, 3], true, { name: 'John' });
๊ฒฐ๊ณผ: 2019-07-04T21:30:08.455Z [myLabel] info: foo "bar" 1 [2,3] true {"name":"John"}
๊ธฐ๋ณธ์ ์ผ๋ก format.combine
๋ info
๊ฐ์ฒด์ ๋ํ ํ์ดํ๋ผ์ธ์ ์ค์ ํฉ๋๋ค. ๊ฐ ํ์ ํจ์์ ๋ํด transform
๊ฐ ํธ์ถ๋๊ณ ์ต์ข
๋ก๊ทธ ๋ฉ์์ง๋ info[Symbol.for('message')]
์์ฑ๋์ด์ผ ํฉ๋๋ค.
๋์์ด ๋์๊ธฐ๋ฅผ ๋ฐ๋๋๋ค
%d
, %o
๋ฑ์ ์ง์ํ๋ ์๋ฃจ์
์ ์ํ์ง๋ง ์ฌ์ ํ ๋๋จธ์ง ์ธ์๋ฅผ ํฌํจํ๋๋ก ๊ธฐ๋ณธ ์ค์ ๋์ด ์์ผ๋ฏ๋ก logger.info('hello %d %j', 42, {a:3}, 'some', 'more', 'arguments')
๊ฐ ๋ค์๊ณผ ๊ฐ์ด ๋ ๋๋ง๋ฉ๋๋ค.
hello 42 {"a": 3} some more arguments
์ด์ ๋ํ ๋ด ์๋ฃจ์
์ splat
๊ธฐํธ๋ฅผ ์ฌ์ฉํ์ง๋ง ๊ธฐ๋ณธ์ ์ผ๋ก ๋๋จธ์ง ์ธ์๋ฅผ ํฌํจํ๋ printf
์์ ์ง์ util.format()
์๋์ผ๋ก ํธ์ถํ์ต๋๋ค.
const {format} = require('util');
const winston = require('winston');
const {combine, timestamp, printf} = winston.format;
const SPLAT = Symbol.for('splat');
const transport = new winston.transports.Console({
format: combine(
timestamp(),
printf(({timestamp, level, message, [SPLAT]: args = []}) =>
`${timestamp} - ${level}: ${format(message, ...args)}`)
)
})
printf
๋ฅผ ์ํ์ง ์์ผ๋ฉด ๋ฌผ๋ก ๋์ ์ธ์๋ฅผ info.message
๋ก ํ์ฅํ๋ ๋ณํ์ ์ถ๊ฐํ ๋ค์ ์ํ๋ ๋ฐฉ์์ผ๋ก ์ต์ข
๊ฒฐ๊ณผ์ ํ์์ ์ง์ ํ ์ ์์ต๋๋ค.
format: combine(
{
transform(info) {
const {[SPLAT]: args = [], message} = info;
info.message = format(message, ...args);
return info;
}
},
simple()
)
format.splat()
๋ฅผ ์ฌ์ฉํ ๋์ ๋ฌธ์ ๋ ์ผ์นํ๋ ์ธ์๋ฅผ ์๋นํ์ง๋ง ๋๋จธ์ง๋ ๋ฒ๋ฆฌ๋ ๊ฒ์ฒ๋ผ ๋ณด์
๋๋ค. ์ง์ util.format()
๋ฅผ ํธ์ถํ์ฌ ํด๋น ๋์์ ๋ฌด์ํ ์ ์์ต๋๋ค.
๋์ผํ ๋ฌธ์ , ์ด์ ๊ดํ ์
๋ฐ์ดํธ๊ฐ ์์ต๋๊น? ๋๋ Winston์ ์ฌ๋ํ์ง๋ง console.log()
ํ ์ ์๋ ๋ก๊ฑฐ์ ์ ๋ฌ๋ ๋ชจ๋ ์ธ์๋ฅผ ์ธ์ํ ์ ์์ ๋ ๋๋ฅผ ๋ฏธ์น๊ฒ ๋ง๋ญ๋๋ค.
@fr1sk ์์์ ๋ด ์์์ ์๋ํ์ต๋๊น? console.log()
๊ฐ์ ๋์์ ์ ๊ณตํฉ๋๋ค(๋
ธ๋์ console.log()
๊ตฌํ์ ๋ด๋ถ์ ์ผ๋ก util.format()
afaik์ ์ฌ์ฉํฉ๋๋ค).
์ด ๋ฌธ์ ๊ฐ ์ฌ์ ํ ํด๊ฒฐ๋์ง ์๊ณ ๋ด ๊ด๋ จ ๋ฌธ์ ๊ฐ ์ ๊ธด ๊ฒ์ด ์ ๊ฐ์
๋๋ค.
์ ๋ ๋ง์ ๋ค๋ฅธ ํ๊ณผ ํจ๊ป ์ผํ๊ณ ์์ผ๋ฉฐ v3๋ก ๋ง์ด๊ทธ๋ ์ด์
ํ ๋ ์ด์ DX๋ฅผ ์์ด๋ฒ๋ฆฌ๋ ๊ฒ์ ๋ํ ์ข์ ๊ฐ์ ๋ชจ๋ ๊ณต์ ํฉ๋๋ค.
๋๋ ํ๋ฃจ ์ข
์ผ ์ด๊ฒ์ ๊ฒ ์์๋ณด๋๋ผ ์๊ฐ์ ๋ณด๋๋ค.
์์ ์๋ฃจ์
์ ๋น์ทํ์ง๋ง ์ถ๊ฐ ์ธ์์ ์ฑ์ ๋ฐ ์ค ๋ฐ๊ฟ์ด ๋๋ฝ๋์์ต๋๋ค.
IMO, ์ฝ์์ ๋ก๊ทธ์ธํ๋ ๊ฒ์ ์ฆ๊ฒ์ต๋๋ค.
์ฌ๊ธฐ ๊ทธ๊ฒ์ ์คํํ๊ธฐ์ํ ๋์ ์๋๊ฐ ์์ต๋๋ค ...
const winston = require('winston');
const chalk = require('chalk');
const logger = new winston.Logger({
transports: [
new winston.transports.Console({
level: 'info',
colorize: true,
prettyPrint: true,
timestamp: true
})
]
});
logger.info({ one: 1, two: 2, three: 3 });
logger.info(chalk.blue('[TEST]:'), { one: 1, two: 2, three: 3 }, [4, 5, 6]);
logger.info(chalk.blue('[TEST]:'), null, undefined, 'one', 2, { 3: 3, 4: '4' });
logger.info(chalk.blue('[TEST]:'), chalk.yellow('Bombastic'), () => {}, /foo/);
logger.error(chalk.blue('[ERR]:'), new Error('Error number 1'));
logger.error(new Error('Error number 2'));
const { createLogger, format, transports } = require('winston');
const { inspect } = require('util');
const chalk = require('chalk');
const hasAnsi = require('has-ansi');
function isPrimitive(val) {
return val === null || (typeof val !== 'object' && typeof val !== 'function');
}
function formatWithInspect(val) {
const prefix = isPrimitive(val) ? '' : '\n';
const shouldFormat = typeof val !== 'string' || !hasAnsi(val);
return prefix + (shouldFormat ? inspect(val, { depth: null, colors: true }) : val);
}
const logger = createLogger({
level: 'info',
format: format.combine(
format.timestamp(),
format.errors({ stack: true }),
format.colorize(),
format.printf(info => {
const msg = formatWithInspect(info.message);
const splatArgs = info[Symbol.for('splat')] || [];
const rest = splatArgs.map(data => formatWithInspect(data)).join(' ');
return `${info.timestamp} - ${info.level}: ${msg} ${rest}`;
})
),
transports: [new transports.Console()]
});
logger.info({ one: 1, two: 2, three: 3 });
logger.info(chalk.blue('[TEST]:'), { one: 1, two: 2, three: 3 }, [4, 5, 6]);
logger.info(chalk.blue('[TEST]:'), null, undefined, 'one', 2, { 3: 3, 4: '4' });
logger.info(chalk.blue('[TEST]:'), chalk.yellow('Bombastic'), () => {}, /foo/);
logger.error(chalk.blue('[ERR]:'), new Error('Error number 1'));
logger.error(new Error('Error number 2'));
chalk
info.message
๋ ๋ฌธ์์ด์ผ ๋ฟ์ด๊ณ ์คํ ์์ญ์ stack
์์ฑ์ ์๊ธฐ ๋๋ฌธ์
๋๋ค.message
์์ฑ์ด ์๋ ๊ฐ์ฒด๋ฅผ ์ฒซ ๋ฒ์งธ ์ธ์๋ก ์ ๋ฌํ๋ฉด message
๋ง ์ธ์๋๊ณ ๋ค๋ฅธ ์์ฑ์ ๋ฌด์๋ฉ๋๋ค.info.message
(์ผ๋ฐ์ ์ผ๋ก ์ฒซ ๋ฒ์งธ ์ธ์์ ๊ฐ) ์์ ์ค๋ฅ ๋ฉ์์ง๊ฐ ์ฐ๊ฒฐ๋์ด ์ค๋ฅ ๋ฉ์์ง๊ฐ ๋ ๋ฒ ํ์๋ฉ๋๋ค.errors
ํฌ๋งทํฐ๋ฅผ ๊ฐ์ง๊ณ ๋์๋ ๋์์ด ๋์ง ์์์ต๋๋ค.
ํ์ ์ง์ ์ค๋ฅ๋ฅผ ์ฒ๋ฆฌํ ์ ์์ง๋ง ํดํน์ ๋๋ค.
function formatWithInspect(val) {
+ if (val instanceof Error) {
+ return '';
+ }
const prefix = isPrimitive(val) ? '' : '\n';
const shouldFormat = typeof val !== 'string' || !hasAnsi(val);
return prefix + (shouldFormat ? inspect(val, { depth: null, colors: true }) : val);
}
...
format.printf((info) => {
const msg = formatWithInspect(info.message);
const splatArgs = info[Symbol.for('splat')] || [];
const rest = splatArgs.map((data) => formatWithInspect(data)).join(' ');
+ const stackTrace = info.stack ? `\n${info.stack}` : '';
return `${info.timestamp} - ${info.level}: ${msg} ${rest}${stackTrace}`;
})
...
๊ทธ๊ฒ์ info[Symbol.for('splat')]
์ฌ์ฉํ์ฌ ๋๋ฅผ ์ํด ์๋ํฉ๋๋ค
const logger = winston.createLogger({
level: 'debug',
transports: [
...
],
format: winston.format.combine(
winston.format.timestamp(),
winston.format.printf((info: any) => {
const timestamp = info.timestamp.trim();
const level = info.level;
const message = (info.message || '').trim();
const args = info[Symbol.for('splat')];
const strArgs = (args || []).map((arg: any) => {
return util.inspect(arg, {
colors: true
});
}).join(' ');
return `[${timestamp}] ${level} ${message} ${strArgs}`;
})
)
});
์ฃผ์ ๋ฒ์ ์ด ์ฃผ์ ๋ณ๊ฒฝ ์ฌํญ์ ๋์ ํ๋ค๋ ๊ฒ์ ์ดํดํ์ง๋ง, ์ ๋ง์์ฌ...
์ด๋ค ์๋ฃจ์
๋ winston v2์ ๋์ผํ ๋์์ ์ ๊ณตํ์ง ์์์ต๋๋ค.
@henhal ๋ฐ @yamadashy์ ์๋ฃจ์
์๋ ๋ชจ๋ ๋์ผํ ๋ฌธ์ ๊ฐ ์์ต๋๋ค. ๋ฉ์์ง ๊ฐ์ด ๋ฌธ์์ด๋ก ํ์๋ฉ๋๋ค.
logger.debug(err)
๋ก๊ทธ ๋ฉ์์ง ์์ฑ:
debug: Error: ETIMEDOUT
๋ฐ๋ฉด logger.debug('anystringhere', err)
์์ฑํฉ๋๋ค.
debug: anystringhereError: ETIMEDOUT { RequestError: Error: ETIMEDOUT
at new RequestError (/path/to/node_modules/request-promise-core/lib/errors.js:14:15)
<the rest of full error stack here>
๋ ๋ฒ์งธ ๋ฌธ์ ๋ ์ ๋ณด ์์ค๊ณผ ํจ๊ป ์ฌ์ฉํ ๋ ์ถ๊ฐ ์ธ์๊ฐ ์ต์ ๋๋ค๋ ๊ฒ์ ๋๋ค. winston v3๋ ์์ ์ง์ ํ๊ธฐ ์ ์ ๋ค๋ฅธ ๋ฐฉ์์ผ๋ก ์ฒ๋ฆฌํ๋ ๊ฒ ๊ฐ์ต๋๋ค.
์ธ ๋ฒ์งธ ๋ฌธ์ ๋ 2๊ฐ์ ๋ฉ์์ง ์ฌ์ด์ ๊ณต๋ฐฑ์ด ์๋ค๋ ๊ฒ์ ๋๋ค("anystringhereError" ์ฐธ๊ณ ).
๋ด ํ์ฌ v3 ๋ก๊ฑฐ ๊ตฌ์ฑ:
const { format } = require('util');
const winston = require("winston");
const logger = winston.createLogger({
transports: [
new winston.transports.Console({
level: 'debug'
format: winston.format.combine(
winston.format.colorize(),
winston.format.align(),
winston.format.printf(
({level, message, [Symbol.for('splat')]: args = []}) => `${level}: ${format(message, ...args)}`
)
)
}),
]
});
module.exports = logger;
๋๋ ๊ทธ๊ฒ์ผ๋ก ์ถฉ๋ถํ๊ณ ๋ค์๊ณผ ๊ฐ์ด winston v2๋ก ๋ค์ ์ ํํ ๊ฒ์ ๋๋ค.
const Winston = require("winston");
const logger = new Winston.Logger({
transports: [
new Winston.transports.Console({
level: 'debug',
handleExceptions: true,
json: false,
colorize: true,
})
],
});
module.exports = logger;
์ด v2 ๊ตฌ์ฑ์๋ ์์์ ์ธ๊ธํ ๋ฌธ์ ๊ฐ ์์ต๋๋ค.
๋ด winston ์ค์ ์ ๊ณต์ ํ๊ณ ์ถ์ต๋๋ค.
const util = require('util');
const { createLogger, format, transports } = require('winston');
const { combine, colorize, timestamp, printf, padLevels} = format;
const myFormat = printf(({ level, message, label, timestamp, ...rest }) => {
const splat = rest[Symbol.for('splat')];
const strArgs = splat ? splat.map((s) => util.formatWithOptions({ colors: true, depth: 10 }, s)).join(' ') : '';
return `${timestamp} ${level} ${util.formatWithOptions({ colors: true, depth: 10}, message)} ${strArgs}`;
});
const logger = createLogger({
level: process.env.NODE_ENV === 'production' ? 'info' : 'debug',
format: combine(
colorize(),
timestamp({
format: 'YYYY-M-DD HH:mm:ss',
}),
padLevels(),
myFormat
),
transports: [new transports.Console()],
});
logger.info('test info');
logger.error('test error');
logger.debug('test debug');
๋ค์์ ์ฌ๋ฌ ๋งค๊ฐ๋ณ์๋ฅผ ํ์ฉํ๋ ์ผ๋ฐ์ ์ธ ํด๊ฒฐ ๋ฐฉ๋ฒ์ ๋๋ค.
https://github.com/rooseveltframework/roosevelt/blob/master/lib/tools/logger.js#L29
์ ์๋ ์๋ฃจ์
์ ๋ํ ์
๋ฐ์ดํธ๋ ๋งํฌ๊ฐ ์์ต๋๋ค.
https://github.com/rooseveltframework/roosevelt/blob/0.13.0/lib/tools/logger.js
๋ด ๊ฒ์ @alexilyaev ํ๋ฅญํ ์ ์์ ๋ณํ์ ๋๋ค.
const { omit } = require('lodash');
const hasAnsi = require('has-ansi');
function isPrimitive(val) {
return val === null || (typeof val !== 'object' && typeof val !== 'function');
}
function formatWithInspect(val) {
if (val instanceof Error) {
return '';
}
const shouldFormat = typeof val !== 'string' && !hasAnsi(val);
const formattedVal = shouldFormat
? inspect(val, { depth: null, colors: true })
: val;
return isPrimitive(val) ? formattedVal : `\n${formattedVal}`;
}
// Handles all the different log formats for console
function getDomainWinstonLoggerFormat(format) {
return format.combine(
format.timestamp(),
format.errors({ stack: true }),
format.colorize(),
format.printf((info) => {
const stackTrace = info.stack ? `\n${info.stack}` : '';
// handle single object
if (!info.message) {
const obj = omit(info, ['level', 'timestamp', Symbol.for('level')]);
return `${info.timestamp} - ${info.level}: ${formatWithInspect(obj)}${stackTrace}`;
}
const splatArgs = info[Symbol.for('splat')] || [];
const rest = splatArgs.map(data => formatWithInspect(data)).join('');
const msg = formatWithInspect(info.message);
return `${info.timestamp} - ${info.level}: ${msg}${rest}${stackTrace}`;
}),
);
}
๋ค์๊ณผ ๊ฐ์ ๋ก๊ทธ์ ๊ฒฝ์ฐ:
logger.info({
joe: 'blow',
});
logger.info('Single String');
logger.info('With Func ', () => {}, /foo/);
logger.info('String One ', 'String Two');
logger.info('String One ', 'String Two ', 'String Three');
logger.info('Single Object ', {
test: 123,
});
logger.info(
'Multiple Objects ',
{
test: 123,
},
{
martin: 5555,
},
);
logger.error('Error: ', new Error('Boom!'));
๋ค์๊ณผ ๊ฐ์ด ์ถ๋ ฅ๋ฉ๋๋ค. ์ด๋ ๋ชจ๋ ์๋๋ฆฌ์ค์ ์ข์ต๋๋ค.
์๋ ํ์ธ์ ์ ๋ Winston์ผ๋ก ์์ํ๊ณ v2๋ฅผ ์ฌ์ฉํ ์ ์ด ์์ผ๋ฏ๋ก v3.2.1์ ์ฌ์ฉ ์ค์ ๋๋ค.
๋๋ ๋จ์ํํ๋ ค๊ณ ํ์ต๋๋ค :
import winston, { format } from 'winston';
winston.format(format.combine(format.splat(), format.simple()));
winston.info('buildFastify dataPath %s', opts.dataPath);
๊ทธ๋ฆฌ๊ณ ๋ฌธ์์ด ๋ณด๊ฐ์ด ์๋ํ๊ธฐ๋ฅผ ๋ฐ๋๋๋ค. ํ์ง๋ง ์๋์ผ.
{"level":"info","message":"buildFastify dataPath %s"}
๋ด๊ฐ ๊ธฐ๋ํ์ ๋
{"level":"info","message":"buildFastify dataPath /My/Data/Path"}
์ด ๋ฌธ์ ๋ ์ด๋ป๊ฒ ๋ ๊ฐ์ ๊ฒ์
๋๊น? ์๋๋ฉด ๋์ logger.log('info', .....)
ํจ์๋ฅผ ์ฌ์ฉํด์ผ ํฉ๋๊น?
์ด๊ฒ์ ์๋ํ๋ ์ด๋ฒคํธ๊ฐ ์๋ํ์ง ์์ต๋๋ค.
winston.log('info', 'buildFastify dataPath %s', opts.dataPath);
์ฐ์ถ:
{"level":"info","message":"buildFastify dataPath %s"}
์์ ์ ์์ ๊ฒ์ํ ์ฌ๋์๊ฒ ๊ฐ์ฌํฉ๋๋ค.
์ ์ฅ, ๋ด ํ๋ก์ ํธ์ Winston 3๋ฅผ ํตํฉํ๊ธฐ ์ํด ํ๋ฃจ๋ฅผ ๋ฒ๋ ธ์ต๋๋ค./
์ด๊ฒ์ ์ค๋ ๋๋ฅผ ์ณค์ต๋๋ค - ๋๋ ๋ฉ์ง ์ ๋ก๊ฑฐ๊ฐ ๋ด ๋ชจ๋ ...rest
์ธ์๋ฅผ ๋์ง๊ณ ์๋ค๋ ๊ฒ์ ๊นจ๋ฌ์์ต๋๋ค. ์ด๊ฒ์ ๋ชจ๋ ์ฌ๋ ๋๋ ๋ชจ๋ ์ฌ์ฉ ์ฌ๋ก์์ ์๋ํ์ง ์์ ์ ์์ง๋ง ์ ๊ฒฝ์ฐ์๋ ๋ฐฐ์ด๋ก ๊ธฐ๋กํ๋ ค๋ ๋ชจ๋ ๊ฒ์ ๋ํํ๋ ๊ฒ์ด ํ์ฉ๋๋ค๋ ๊ฒ์ ์์์ต๋๋ค. ๊ฐ์ฅ ์์์ง๋ ์์ง๋ง ๋ ๋ง์ ์ฝ๋๊ฐ ํ์ํ [๋งค์ฐ ์๋ฆฌํ] ํด๊ฒฐ ๋ฐฉ๋ฒ๋ณด๋ค ๊ฐ๋ณ์ต๋๋ค. ์ด๊ฒ์ด ๋ค๋ฅธ ์ฌ๋์ ๋๊ธฐ๋ฅผ ๋ฐ๋๋๋ค!
logger.info(["Something happened!", foo, bar]);
์ ๋ง ๊ทธ๋ค์ด ์ฃผ์ ๋ณ๊ฒฝ์ด ๋ํํ๊ธฐ๋ก ๊ฒฐ์ ํ๊ณ STILL ๋ง์ด๊ทธ๋ ์ด์ ์ค๋ช ์ ๋ ๋ฌธ์์ ์ธ๊ธ๋์ง ์์ ์์ ํ๋๊ฒ.
๊ทธ๋ผ์๋ ๋ถ๊ตฌํ๊ณ ๋๋ ๋งค์ฐ ์ปดํฉํธํ๊ณ node js๊ฐ util.format
์ฌ์ฉํ์ฌ console.log๋ฅผ ๊ตฌํํ๋ ๋ฐฉ๋ฒ์ ๋ฐ๋ฅด๋ ์๋ฃจ์
์ ๊ณต์ ํ๊ณ ์ถ์์ต๋๋ค.
const winstonLogger= createLogger(...);
const writeLogType = (logLevel) => {
return function () {
const args = Array.from(arguments);
winstonLogger[logLevel](util.format(...args));
};
};
const logger = {
silly: writeLogType('silly'),
debug: writeLogType('debug'),
verbose: writeLogType('verbose'),
info: writeLogType('info'),
warn: writeLogType('warn'),
error: writeLogType('error'),
};
์ด ๊ธฐ๋ฅ์ ํต์ฌ Winston์ ์ถ๊ฐํ๋๋ก ์์ฒญํ๊ณ ์ถ์ต๋๋ค. ํ์ฌ ์ ๋ ์ด ๊ธฐ๋ฅ์ ๊ตฌํํ๊ธฐ ์ํด splat
์ ํจ๊ป ์ฌ์ฉ์ ์ ์ ํฌ๋งทํฐ๋ฅผ ์ฌ์ฉํ๊ณ ์์ต๋๋ค. console.log
์ ์ผ์นํ๋ ๊ธฐ๋ฅ์ด ์์ผ๋ฉด ์ข์ ๊ฒ์
๋๋ค.
์ ๋ฐ์ดํธ๊ฐ ์์ต๋๊น?
v3์ ๋ํ ์์ ๋์ ์ธ์๋ ์ด๊ฒ์ ์ถ๊ฐํ๊ณ ์ถ์ต๋๋ค.
logger.info('param 1', { propInJson1: 'propInJson 1', propInJson2: 'propInJson 2' });
์ด๊ฒ์ ์์ฐํ ๊ฒ์ด๋ค
{"propInJson1":"propInJson 1","propInJson2":"propInJson 2","level":"info","message":"param 1"}
๋ด๊ฐ ์ฌ์ฉํ๋ ๋ฒ์ : (v3.2.1)
๊ตฌ์ฑ:
winstonLogger.add(new winston.transports.Console());
๋๋ ์ฌ์ ํ Winston์์ ์ฌ๋ฌ ๊ฐ์ ์ถ๋ ฅํ๋ ๋ฐฉ๋ฒ์ ์ดํดํ์ง ๋ชปํฉ๋๋ค.
console.log('Hello', var1, '!')
์ logger.log('Hello', var1, '!')
๋ก ๋ฐ๊พธ๊ณ ์ถ์์ต๋๋ค.
์์งํ ๋งํด์ Winston์ ์ฌ์ฉํ๋ ค๊ณ ํ๋ฉด ํญ์ ๋ง์ ์๊ฐ์ด ๋ญ๋น๋๊ณ ๋ก๊น ์ ๋๋ผ์ด ๋ฌธ์ ๊ฐ ๋ฐ์ํฉ๋๋ค.
์ ๋ง ๊ทธ๋ค์ด ์ฃผ์ ๋ณ๊ฒฝ์ด ๋ํํ๊ธฐ๋ก ๊ฒฐ์ ํ๊ณ STILL ๋ง์ด๊ทธ๋ ์ด์ ์ค๋ช ์ ๋ ๋ฌธ์์ ์ธ๊ธ๋์ง ์์ ์์ ํ๋๊ฒ.
๊ทธ๋ผ์๋ ๋ถ๊ตฌํ๊ณ ๋๋ ๋งค์ฐ ์ปดํฉํธํ๊ณ node js๊ฐ
util.format
์ฌ์ฉํ์ฌ console.log๋ฅผ ๊ตฌํํ๋ ๋ฐฉ๋ฒ์ ๋ฐ๋ฅด๋ ์๋ฃจ์ ์ ๊ณต์ ํ๊ณ ์ถ์์ต๋๋ค.const winstonLogger= createLogger(...); const writeLogType = (logLevel) => { return function () { const args = Array.from(arguments); winstonLogger[logLevel](util.format(...args)); }; }; const logger = { silly: writeLogType('silly'), debug: writeLogType('debug'), verbose: writeLogType('verbose'), info: writeLogType('info'), warn: writeLogType('warn'), error: writeLogType('error'), };
๋ํ util.formatWithOptions({ colors: true }, ...args);
๋ฅผ ์ฌ์ฉํ์ฌ ์ผ๋ฐ console.log
์ ๊ฐ์ ์ปฌ๋ฌ ์ธ์ ์ถ๋ ฅ์ ์ป์ต๋๋ค.
์ด๊ฒ์ ๋๋ฅผ ์ํด ์๋ํฉ๋๋ค. util.format์ ์ฌ์ฉํ์ฌ ๋ฉ์์ง์ splat์ ๊ฒฐํฉํ๋ combineMessageAndSplat
const winston = require("winston");
const util = require('util');
const combineMessageAndSplat = () => {
return {transform: (info, opts) => {
//combine message and args if any
info.message = util.format(info.message, ...info[Symbol.for('splat')] || [] )
return info;
}
}
}
const logger = winston.createLogger({
format:
winston.format.combine(
combineMessageAndSplat(),
winston.format.simple()
)
});
logger.add(new winston.transports.Console({
level: 'info'
})
);
logger.info("string"); // info: string
logger.info({a:1,b:[{c:[1]}]}); // info: { a: 1, b: [ { c: [Array] } ] }
logger.info("1","2",{a:1}); // info: 1 2 { a: 1 }
logger.info([{},""]); // info: [ {}, '' ]
logger.error(new Error()); // error: Error ... at Object.<anonymous>
๋๋ ์์ ๋ชจ๋ ๊ฒ์ ์งํฅํ๋ console.* ๋ฐฉ๋ฒ์ ๋ฐ๋ผ์ผ ํ๊ณ ...
ํ์ง๋ง: ํ์ผ ์ถ๋ ฅ์ ๊ฐ์ฒด ์ธ๋ถ ์ ๋ณด๊ฐ ํ์๋์ด์ผ ํฉ๋๋ค.
๊ทธ๋์ ๋๋ ๊ฒฐ๊ตญ :
// make File and FileList parseable // from: https://stackoverflow.com/a/51939522/1644202
File.prototype.toObject = function () {
return Object({
name: String(this.name),
path: String(this.path),
lastModified: parseInt(this.lastModified),
lastModifiedDate: String(this.lastModifiedDate),
size: parseInt(this.size),
type: String(this.type)
});
};
FileList.prototype.toArray = function () {
return Array.from(this).map(function (file) {
return file.toObject()
});
};
// this fixes: winston console transport to use the original console functions and all the colorization/folding/etc that comes with it
const Transport = require('winston-transport');
class WebDeveloperConsole extends Transport {
constructor(opts) {
super(opts);
}
log(info, callback) {
(window.console[info.level] || window.console.log).apply(window.console, [info.timestamp, ...info.message]);
callback();
}
};
// Electron app console output
class AppConsole extends Transport {
constructor(opts) {
super(opts);
const { remote } = require('electron');
this.electronConsole = remote.getGlobal('console');
}
log(info, callback) {
(this.electronConsole[info.level] || this.electronConsole.log).apply(this.electronConsole, [info.timestamp, ...info.message]);
callback();
}
};
const util = require('util');
const {
createLogger,
transports,
format
} = require('winston');
let logger = createLogger({
level: 'trace',
levels: {
error: 0,
warn: 1,
info: 2,
//http: 3, no console.* methods
//verbose: 4,
debug: 3,
trace: 4
},
format: format.combine(
format.prettyPrint(),
format.timestamp({
format: 'DD-MM-YYYY hh:mm:ss A'
}),
{
transform(info) {
const { timestamp, message } = info;
const level = info[Symbol.for('level')];
const args = [message, ...(info[Symbol.for('splat')] || [])]; // join the args back into one arr
info.message = args; // for all custom transports (mainly the console ones)
let msg = args.map(e => {
if (e.toString() == '[object FileList]')
return util.inspect(e.toArray(), true, 10);
else if (e.toString() == '[object File]')
return util.inspect(e.toObject(), true, 10);
else if (e.toString() == '[object Object]') {
return util.inspect(e, true, 5);
}
else if (e instanceof Error)
return e.stack
else
return e;
}).join(' ');
info[Symbol.for('message')] = `${timestamp} - ${level}: ${msg}`; // for inbuild transport / file-transport
return info;
}
},
),
transports: [
//new transports.Console(),
new WebDeveloperConsole(),
new AppConsole(),
...
],
...
});
@indexzero ์ด๋ ์์ ์์ ์ฌ์ ํ ์ด ๋ฌธ์ ๋ฅผ ํด๊ฒฐํ ๊ณํ
๋ด ํ๋ก์ ํธ์์ ์ฌ์ฉํ winston์ ํ์ ์ค์ ๋๋ค. ์ด์ ์ด ๋ฌธ์ ๊ฐ 2๋ ์ด์ ์ด๋ ค ์์ผ๋ฏ๋ก ์ํ์ ๊ฐ์ํ ๊ฐ์น๊ฐ ์๋์ง ๊ถ๊ธํฉ๋๋ค. ๋์๊ฒ ์ด๊ฒ์ ๋ชจ๋ ๋ก๊น ํ๋ ์ ์ํฌ์ ๊ธฐ๋ณธ ๊ธฐ๋ฅ์ฒ๋ผ ๋ณด์ ๋๋ค.
๊ฐ์ฅ ์ ์ฉํ ๋๊ธ
์ด๊ฒ์ ์ฐ๋ฆฌ์๊ฒ ์์ฒญ๋ ๋ณํ์ ๋๋ค. ์๋์ ์ด๋ผ๋ฉด https://github.com/winstonjs/winston/blob/master/UPGRADE-3.0.md์ ์์ธํ ๋์ ์์ด์ผ ํฉ๋๋ค.