Winston: ๋กœ๊น… ํ•จ์ˆ˜์— ์—ฌ๋Ÿฌ ๋งค๊ฐœ๋ณ€์ˆ˜๋ฅผ ์ „๋‹ฌํ•˜๋ฉด ์˜ˆ์ƒ๋Œ€๋กœ ์ž‘๋™ํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.

์— ๋งŒ๋“  2018๋…„ 08์›” 06์ผ  ยท  43์ฝ”๋ฉ˜ํŠธ  ยท  ์ถœ์ฒ˜: winstonjs/winston

๊ท€ํ•˜์˜ ํ™˜๊ฒฝ์— ๋Œ€ํ•ด ์•Œ๋ ค์ฃผ์‹ญ์‹œ์˜ค.

  • _ winston ๋ฒ„์ „?_

    • [ ] winston@2

    • [x] winston@3

  • _ node -v ์ถœ๋ ฅ:_ v8.11.3
  • _์šด์˜ ์ฒด์ œ?_ macOS
  • _์–ธ์–ด?_ Flow ES6/7

๋ฌธ์ œ๊ฐ€ ๋ฌด์—‡์ž…๋‹ˆ๊นŒ?


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"}
important

๊ฐ€์žฅ ์œ ์šฉํ•œ ๋Œ“๊ธ€

์ด๊ฒƒ์€ ์šฐ๋ฆฌ์—๊ฒŒ ์—„์ฒญ๋‚œ ๋ณ€ํ™”์ž…๋‹ˆ๋‹ค. ์˜๋„์ ์ด๋ผ๋ฉด https://github.com/winstonjs/winston/blob/master/UPGRADE-3.0.md์— ์ž์„ธํžˆ ๋‚˜์™€ ์žˆ์–ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.

๋ชจ๋“  43 ๋Œ“๊ธ€

๋‚˜๋Š” ์˜ค๋Š˜ ๊ฐ™์€ ๋ฌธ์ œ์— ๋ถ€๋”ช์ณค๋‹ค. ๋‚˜๋Š” ์—…๊ทธ๋ ˆ์ด๋“œ ๋ฌธ์„œ๋Š” ๊ทธ์™€ ๊ฐ™์€ ์˜ˆ๋ฅผ ๊ฐ€์ง€๊ณ  ์žˆ๊ธฐ ๋•Œ๋ฌธ์—์ด ๋ฒ„๊ทธ๊ฐ€ ๋ฏฟ๊ณ  ์—ฌ๊ธฐ๋ฅผ :

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"}

์ผ๋ฐ˜ ๋น ๋ฅธ ์‹œ์ž‘ ์ฝ”๋“œ๋ฅผ ์‚ฌ์šฉํ–ˆ์Šต๋‹ˆ๋‹ค.

๋‚ด๊ฐ€ ์ฃผ๋ชฉํ•œ ํ•œ ๊ฐ€์ง€๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™์Šต๋‹ˆ๋‹ค.

  • 2๊ฐœ์˜ ์ „์†ก์ด ์žˆ๋Š” ๋กœ๊ฑฐ๋ฅผ ์–ป์—ˆ์Šต๋‹ˆ๋‹ค(์ฝ˜์†” ๋ฐ ํšŒ์ „ ํŒŒ์ผ)
  • ๋‘˜ ๋‹ค splat ํ˜•์‹์„ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค.
  • ์ฒซ ๋ฒˆ์งธ ์ „์†ก์— ๋Œ€ํ•œ splat์˜ ๋ณ€ํ™˜ ํ•จ์ˆ˜์— ๋Œ€ํ•œ ํ˜ธ์ถœ์€ SPLAT ๊ธฐํ˜ธ ์•„๋ž˜์˜ ์ •๋ณด์— ์ €์žฅ๋œ ๋ฐฐ์—ด์„ ์ง€์›๋‹ˆ๋‹ค.
  • ๋‘ ๋ฒˆ์งธ ์ „์†ก์— ๋Œ€ํ•œ splat์˜ ๋ณ€ํ™˜ ํ•จ์ˆ˜ ํ˜ธ์ถœ ์‹œ SPLAT ๋ฐฐ์—ด์ด ๋น„์–ด ์žˆ์œผ๋ฏ€๋กœ ์ถ”๊ฐ€ ์ธ์ˆ˜๊ฐ€ ๋” ์ด์ƒ ๊ธฐ๋ก๋˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.

์ด ๋ฌธ์ œ๊ฐ€ ํ•ด๊ฒฐ๋  ์ˆ˜ ์žˆ๊ธฐ๋ฅผ ๋ฐ”๋ž๋‹ˆ๋‹ค.

์ฐธ๊ณ : Winston์˜ ์ตœ์‹  ๋ฆด๋ฆฌ์Šค ๋ฒ„์ „์œผ๋กœ ์ด๊ฒƒ์„ ํ…Œ์ŠคํŠธํ–ˆ์Šต๋‹ˆ๋‹ค. ์ฝ”๋“œ๋ฅผ ๊ฐ„๋‹จํžˆ ์‚ดํŽด๋ณด๋ฉด ๋งˆ์Šคํ„ฐ์—์„œ ์—ฌ์ „ํžˆ ์ด์™€ ์œ ์‚ฌํ•œ ๊ฒƒ์œผ๋กœ ๋ณด์ž…๋‹ˆ๋‹ค(๋‹ค๋ฅธ ์ˆ˜์ • ์‚ฌํ•ญ์ด ์žˆ๋Š” ๊ฒƒ ๊ฐ™์Šต๋‹ˆ๋‹ค).

@luislobo ์˜ ์†”๋ฃจ์…˜์— ๋Œ€ํ•œ ๋ช‡ ๊ฐ€์ง€ ๊ฐœ์„  ์‚ฌํ•ญ ๐Ÿ‘

  • logger.info(`hello %s`,'world') ์™€ ๊ฐ™์€ ๋ฉ”์‹œ์ง€์— ๋Œ€ํ•ด %s %d or %j ๊ฐ€ ํฌํ•จ๋œ ํ‘œ์‹œ ์Šคํƒ€์ผ์ธ ๊ฒฝ์šฐ ๋ฉ”์‹œ์ง€ ๋ฌด์‹œ
  • colorise๋ฅผ ๋จผ์ € ๋งŒ๋“ค๊ธฐ ์œ„ํ•ด ์žฌ์ •๋ ฌ๋œ ํฌ๋งทํ„ฐ https://github.com/winstonjs/winston#colorizing -standard-logging-levels
  • 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, ์ฝ˜์†”์— ๋กœ๊ทธ์ธํ•˜๋Š” ๊ฒƒ์€ ์ฆ๊ฒ์Šต๋‹ˆ๋‹ค.
์—ฌ๊ธฐ ๊ทธ๊ฒƒ์„ ์‹คํ˜„ํ•˜๊ธฐ์œ„ํ•œ ๋‚˜์˜ ์‹œ๋„๊ฐ€ ์žˆ์Šต๋‹ˆ๋‹ค ...

๋น„๊ต๋ฅผ ์œ„ํ•œ v2 ์„ค์ •

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

Winston2

v3 ์„ค์ •

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

Winston3

์ฒ˜๋ฆฌํ•ด์•ผ ํ•  ๋งŽ์€ ๊ฒƒ๋“ค์ด ์žˆ์œผ๋ฉฐ ๊ทธ ์ค‘ ์ผ๋ถ€๋Š”

  • ์ฒซ ๋ฒˆ์งธ ์ธ์ˆ˜๋กœ ๊ฐ์ฒด์™€ ๊ฐ™์€ ๊ฐ’
  • ํ˜ผํ•ฉ ๊ฐ’์˜ ์—ฌ๋Ÿฌ ์ธ์ˆ˜
  • ANSI ์ด์Šค์ผ€์ดํ”„ ์ฝ”๋“œ๊ฐ€ ์žˆ๋Š” ๋ฌธ์ž์—ด(์˜ˆ: chalk
  • Function ๋ฐ RegEx์™€ ๊ฐ™์€ ๋‚œํ•ดํ•œ ๊ฐ’
  • ์ฒซ ๋ฒˆ์งธ ์ธ์ˆ˜ ๋˜๋Š” ๊ทธ ์ดํ›„์˜ ๋ชจ๋“  ์œ„์น˜์—์„œ ์˜ค๋ฅ˜
  • ์—ฌ์ „ํžˆ ๋‹ค๋ฅธ ํฌ๋งทํ„ฐ์™€ ํ•จ๊ป˜ ์ž˜ ์ž‘๋™ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.

์ด ์†”๋ฃจ์…˜์˜ ํ˜„์žฌ ๋ฒ„๊ทธ

  • ์ฒซ ๋ฒˆ์งธ ์ธ์ˆ˜๋กœ ์˜ค๋ฅ˜๋ฅผ ์ „๋‹ฌํ•˜๋ฉด ์Šคํƒ ์ถ”์ ์ด ์ธ์‡„๋˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.

    • info.message ๋Š” ๋ฌธ์ž์—ด์ผ ๋ฟ์ด๊ณ  ์Šคํƒ ์˜์—ญ์€ stack ์†์„ฑ์— ์žˆ๊ธฐ ๋•Œ๋ฌธ์ž…๋‹ˆ๋‹ค.

  • message ์†์„ฑ์ด ์žˆ๋Š” ๊ฐœ์ฒด๋ฅผ ์ฒซ ๋ฒˆ์งธ ์ธ์ˆ˜๋กœ ์ „๋‹ฌํ•˜๋ฉด message ๋งŒ ์ธ์‡„๋˜๊ณ  ๋‹ค๋ฅธ ์†์„ฑ์€ ๋ฌด์‹œ๋ฉ๋‹ˆ๋‹ค.

    • ์œ„์™€ ๊ฐ™์€ ์ด์œ 

  • ๋‘ ๋ฒˆ์งธ ์ธ์ˆ˜๋กœ ์˜ค๋ฅ˜๋ฅผ ์ „๋‹ฌํ•˜๋ฉด info.message (์ผ๋ฐ˜์ ์œผ๋กœ ์ฒซ ๋ฒˆ์งธ ์ธ์ˆ˜์˜ ๊ฐ’) ์œ„์— ์˜ค๋ฅ˜ ๋ฉ”์‹œ์ง€๊ฐ€ ์—ฐ๊ฒฐ๋˜์–ด ์˜ค๋ฅ˜ ๋ฉ”์‹œ์ง€๊ฐ€ ๋‘ ๋ฒˆ ํ‘œ์‹œ๋ฉ๋‹ˆ๋‹ค.

    • https://github.com/winstonjs/winston/issues/1660 ์—์„œ ์ถ”์ 

    • ์ด๊ฒƒ์€ ์‹ค์ œ๋กœ README์˜ ์ผ๋ถ€์ž…๋‹ˆ๋‹ค.

      > ์ฐธ๊ณ : ์ œ๊ณต๋œ ๋ฉ”ํƒ€ ๊ฐ์ฒด์˜ ๋ชจ๋“  { message } ์†์„ฑ์€ ์ด๋ฏธ ์ œ๊ณต๋œ ๋ชจ๋“  ๋ฉ”์‹œ์ง€์— ์ž๋™์œผ๋กœ ์—ฐ๊ฒฐ๋ฉ๋‹ˆ๋‹ค. ์˜ˆ๋ฅผ ๋“ค์–ด ์•„๋ž˜๋Š” 'world'๋ฅผ 'hello'์— ์—ฐ๊ฒฐํ•ฉ๋‹ˆ๋‹ค.

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

image

๋‹ค์Œ์€ ์—ฌ๋Ÿฌ ๋งค๊ฐœ๋ณ€์ˆ˜๋ฅผ ํ—ˆ์šฉํ•˜๋Š” ์ผ๋ฐ˜์ ์ธ ํ•ด๊ฒฐ ๋ฐฉ๋ฒ•์ž…๋‹ˆ๋‹ค.
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!'));

๋‹ค์Œ๊ณผ ๊ฐ™์ด ์ถœ๋ ฅ๋ฉ๋‹ˆ๋‹ค. ์ด๋Š” ๋ชจ๋“  ์‹œ๋‚˜๋ฆฌ์˜ค์— ์ข‹์Šต๋‹ˆ๋‹ค.

Screen Shot 2020-02-06 at 10 54 31 pm

์•ˆ๋…•ํ•˜์„ธ์š” ์ €๋Š” 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.* ๋ฐฉ๋ฒ•์„ ๋”ฐ๋ผ์•ผ ํ–ˆ๊ณ  ...

  • chrome-dev-tools์˜ ๊ธฐ๋ณธ ์ฝ˜์†” ๋ฉ”์„œ๋“œ(๊ธฐ๋ณธ์ ์œผ๋กœ ์ƒ‰์ƒ ์ง€์ •)
  • ์ „์ž ํ„ฐ๋ฏธ๋„ ์ฝ˜์†” ์ถœ๋ ฅ(๊ธฐ๋ณธ์ ์œผ๋กœ ์ƒ‰์ƒ๋„ ์ง€์ •๋จ)
  • ํŒŒ์ผ์— ์“ฐ๊ธฐ

ํ•˜์ง€๋งŒ: ํŒŒ์ผ ์ถœ๋ ฅ์— ๊ฐœ์ฒด ์„ธ๋ถ€ ์ •๋ณด๊ฐ€ ํ‘œ์‹œ๋˜์–ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.

๊ทธ๋ž˜์„œ ๋‚˜๋Š” ๊ฒฐ๊ตญ :

// 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๋…„ ์ด์ƒ ์—ด๋ ค ์žˆ์œผ๋ฏ€๋กœ ์œ„ํ—˜์„ ๊ฐ์ˆ˜ํ•  ๊ฐ€์น˜๊ฐ€ ์žˆ๋Š”์ง€ ๊ถ๊ธˆํ•ฉ๋‹ˆ๋‹ค. ๋‚˜์—๊ฒŒ ์ด๊ฒƒ์€ ๋ชจ๋“  ๋กœ๊น… ํ”„๋ ˆ์ž„ ์›Œํฌ์˜ ๊ธฐ๋ณธ ๊ธฐ๋Šฅ์ฒ˜๋Ÿผ ๋ณด์ž…๋‹ˆ๋‹ค.

์ด ํŽ˜์ด์ง€๊ฐ€ ๋„์›€์ด ๋˜์—ˆ๋‚˜์š”?
0 / 5 - 0 ๋“ฑ๊ธ‰