Winston: Passing multiple parameters to logging functions doesn't behave as expected

Created on 6 Aug 2018  ·  43Comments  ·  Source: winstonjs/winston

Please tell us about your environment:

  • _winston version?_

    • [ ] winston@2

    • [x] winston@3

  • _node -v outputs:_ v8.11.3
  • _Operating System?_ macOS
  • _Language?_ Flow ES6/7

What is the problem?


With 3.x.x:

const winston = require('winston')

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

logger.info('test log', 'with a second parameter')

Outputs:

{"level":"info","message":"test log"}

With 2.x.x:

const winston = require('winston')

const logger = new winston.Logger({
    transports: [new winston.transports.Console()]
})

logger.info('test log', 'with a second parameter')

Outputs:

info: test log with a second parameter

What do you expect to happen instead?


It should output:

{"level":"info","message":"test log with a second parameter"}
important

Most helpful comment

This is a HUGE breaking change for us. If it is intentional, it should be detailed in https://github.com/winstonjs/winston/blob/master/UPGRADE-3.0.md

All 43 comments

I ran into the same issue today. I believe it is a bug since the upgrade documentation also has an example like that here:

logger.info('transaction ok', { creditCard: 123456789012345 });

Currently to get around this you can create a function that parses the arguments before it's passed into winston

@mulligan121 Okay, but I don't want to replace every log statement in my codebase...

@indexzero Is this under your radar? I think this is a major breaking change. It actually prevents from moving from 2.x to 3.x as it requires us to change every log entry

Temporary workaround:

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

This is a HUGE breaking change for us. If it is intentional, it should be detailed in https://github.com/winstonjs/winston/blob/master/UPGRADE-3.0.md

So after some more investigation, I found that you need the splat formatter in order for multiple arguments to be printed. I thought that was just for argument interpolation (i.e. stuff with %s etc. in it), but it seems you need it just to do logger.info("something", value).

But this is still being a bit weird for me - I'm getting something that looks like JSON in the output with a key "meta", even though I am not using a JSON format:

logger.info('Test: %s', 1, 2, 3, 4);

Produces:

info: Test: 1 {"meta":[2,3,4]}

Even the example in examples doesn't produce what it says it will:

https://github.com/winstonjs/winston/blob/master/examples/interpolation.js#L20-L21

info: test message first, second {"meta":{"number":123}}

I've solved it using something along these lines:

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()]
});

Here is a common workaround to allow for multiple parameters:
https://github.com/rooseveltframework/roosevelt/blob/master/lib/tools/logger.js#L29

Related to:
https://github.com/winstonjs/winston/issues/1377

There's just too many things that don't work the same.

Hi, Any updates here? Thanks.

Also waiting for proper way to emulate console.log(...args) in winston...

We fixed a number of the edge / corner cases around splat and meta in 3.2.0, but it appears this is still an issue (see: https://github.com/winstonjs/winston/pull/1576 for the CHANGELOG.md).

Will make sure this gets handled in 3.3.0. Thanks for your patience folks.

This was a workaround for me:

'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' }),
  ],
});

When I try to log with arguments I get a weird output
var s = "Hello" log.info("asdasda", s)

Getting the output

{"0":"H","1":"e","2":"l","3":"l","4":"o","service":"XXXX","level":"info","message":"asdasda","timestamp":"2019-04-15 13:58:51"}

I used the normal quick start code

One thing I noticed is this:

  • got a logger with 2 transports (Console and Rotating file)
  • Both use the splat format
  • The call to the transform function of splat for the first transport clears out the array stored on info under the SPLAT Symbol
  • On the call to the transform function of splat for the second transport, the SPLAT array is empty and thus the extra arguments don't get logged anymore

Hope this can/will be fixed

Note: tested this with the latest released version of Winston, quick look at the code seems to suggest that it's still like this in master (which has other fixes it seems)

Some improvements over @luislobo's solution 👏

  • Ignore message if it splat style i.e. contains %s %d or %j for messages like logger.info(`hello %s`,'world')
  • reorderd formaters to make colorise come first https://github.com/winstonjs/winston#colorizing-standard-logging-levels
  • Removed twise formateObject in printf
  • trimEnd if addition arguments are not provided in the looger
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()]
});

Any further update to this that I may be missing? It seems like there's pretty strong support for this feature to be re-added, but the last commitment to this was nearly 6 months ago.

Hi there, huge Bumper and show stopper, trying to migrate to the 3.X, probably will keep the 2.x for a while.

A bit disappointed :(

This is what worked for me:

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

which results in: 2019-07-04T21:30:08.455Z [myLabel] info: foo "bar" 1 [2,3] true {"name":"John"}

Basically format.combine sets a pipeline for an info object. For each format function, transform is called and the final log message should be written to info[Symbol.for('message')]
hope this helps

I wanted a solution that supports %d, %o etc, but still defaults to including any rest arguments, so that logger.info('hello %d %j', 42, {a:3}, 'some', 'more', 'arguments') is rendered like this:

hello 42 {"a": 3} some more arguments

My solution for this ended up using the splat symbol but manually invoking util.format() directly from printf, which by default includes any rest arguments:

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)}`)
    )
})

If you don't want printf you could of course instead add a transform that simply expands the args into info.message, and then format the end result the way you like:

format: combine(
  {
    transform(info) {
      const {[SPLAT]: args = [], message} = info;

      info.message = format(message, ...args);

      return info;
    }
  },
  simple()
)  

The problem with using format.splat() is that it consumes the matching arguments but seems to throw away the rest. By invoking util.format() myself I can override that behaviour.

Same issue, any update regarding this one? I love Winston, but it drives me crazy when I cannot print all arguments passed to the logger, which I can do with console.log().

@fr1sk Did you try my formatting above? It gives console.log() like behaviour (node's console.log() implementation internally uses util.format() afaik).

It's disappointing that this issue still has not been resolved, and that my related issue has been locked.
I'm working with many different teams and they all share the frustration of losing previous DX when migrating to v3.

I've spent a whole day trying things out.
The above solutions were close but I was missing the colorization and line breaking of extra arguments.

IMO, logging to the Console should be delightful.
Here's my attempt to make it happen...

v2 setup for comparison

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 setup

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

There are many things to handle, some of which

  • An Object-like value as the first argument
  • Several arguments of mixed values
  • Strings with ANSI escape codes, e.g. using chalk
  • Esoteric values like Function and RegEx
  • An Error as a first argument or anywhere after that
  • Should still work fine alongside other formatters

Current bugs with this solution

  • Passing an Error as the 1st argument does not print the Stack Trace

    • This is because info.message is just a string and the stack tract is in the stack property

  • Passing an Object with a message property as the 1st argument only printsmessage, discarding any other properties

    • Same reason as the one above

  • Passing an Error as the 2nd argument somehow concatenates the error message on top of info.message (which is usually the value of the 1st argument), resulting in seeing the error message twice

    • Tracked in https://github.com/winstonjs/winston/issues/1660

    • This is actually part of the README:

      > NOTE: any { message } property in a meta object provided will automatically be concatenated to any msg already provided: For example the below will concatenate 'world' onto 'hello':

Playing around with the errors formatter didn't help.

Positive enhancements

  • Logging primitive values now gets prettified
  • Logging several Object-like values now adds a newline between them
  • Logging an Array is now prettified instead of looking like an Object with indices keys

Edit:

We can handle the errors formatting but it's hacky:

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}`;
    })
...

it works for me using 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}`;
        })
    )
});

I understand major versions introduce breaking changes, but my goodness...

None of the solutions gave me same behavior as winston v2.
Solutions from @henhal and @yamadashy both have same issue: message value is showed as the string.
logger.debug(err) create log message:

debug:  Error: ETIMEDOUT

whereas logger.debug('anystringhere', err) creates:

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>

Second issue is that the additional arguments are suppressed when using with info level - winston v3 seems to handle that the other way before formatting.

Third issue is the missing space between 2 messages (notice "anystringhereError").

My current v3 logger configuration:

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;

I had enough of that and gonna switch back to winston v2 with just this:

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;

This v2 configuration does not have the above-mentioned issues.

I want to share my winston setup:

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

Here is a common workaround to allow for multiple parameters:
https://github.com/rooseveltframework/roosevelt/blob/master/lib/tools/logger.js#L29

here is updated link to the proposed solution
https://github.com/rooseveltframework/roosevelt/blob/0.13.0/lib/tools/logger.js

Mine is a variation of @alexilyaev great suggestions:

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}`;
    }),
  );
}

for logs like:

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

It outputs like this - which is good for all my scenarios:

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

Hey I'm starting with Winston, never used v2, so I'm on v3.2.1

I was trying to simply do:

import winston, { format } from 'winston';
winston.format(format.combine(format.splat(), format.simple()));

winston.info('buildFastify dataPath %s', opts.dataPath);

And hoping the string interpolation works; but nope.

{"level":"info","message":"buildFastify dataPath %s"}

When I was expecting

{"level":"info","message":"buildFastify dataPath /My/Data/Path"}

This issue is the same thing somehow? Or I'm forced to use the logger.log('info', .....) function instead?

Edit

Event trying this doesn't work.

  winston.log('info', 'buildFastify dataPath %s', opts.dataPath);

output:

{"level":"info","message":"buildFastify dataPath %s"}

Thanks to who posted the suggestions above.
Damn I've thrown away one day just to integrate Winston 3 on my project :/

This one hit me today - I realized my fancy new logger was tossing all my ...rest arguments. This may not work for everyone or every use case, but in my case I found it acceptable to just wrap everything I wanted logged as an array. It's not the prettiest, but it's lighter than some of the [very clever] workarounds that require more code. Hope this helps someone else!

logger.info(["Something happened!", foo, bar]);

Really upsetting that they decided to make a breaking change this large and STILL have not mentioned it in the migration guide or the documentation.

Regardless I wanted to share my solution which I found quite compact and follows how node js implements console.log using util.format

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

I would like to chime in and request this feature to be added to the core Winston. Currently, I'm using the custom formatter with splat to achieve this functionality, which honestly feels very hacky. It would be nice to have functionality that matches console.log

Any updates?

Besides the above behaviors for v3, I want to add this as well:

logger.info('param 1', { propInJson1: 'propInJson 1', propInJson2: 'propInJson 2' });

will produce this

{"propInJson1":"propInJson 1","propInJson2":"propInJson 2","level":"info","message":"param 1"}

the version I'm using: (v3.2.1)
The configs:

winstonLogger.add(new winston.transports.Console());

I still don't understand how to output multiple values in Winston.

I just wanted to replace console.log('Hello', var1, '!') with logger.log('Hello', var1, '!').

To be honest, trying to use Winston always lead to a lot of time wasted and surprising problems with logging.

Really upsetting that they decided to make a breaking change this large and STILL have not mentioned it in the migration guide or the documentation.

Regardless I wanted to share my solution which I found quite compact and follows how node js implements console.log using util.format

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

Also, use util.formatWithOptions({ colors: true }, ...args); to get colored print output like with regular console.log

This works for me. combineMessageAndSplat that combines message and splat using util.format

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> 

I had to conform to console.* methods, orientating on all above, and ...

  • default console methods (they colorize by default) for chrome-dev-tools
  • electrons terminal console output (also colored by default)
  • writing to a file

But: The file output should show object details.

So I ended up with:

// 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 Was wondering if you were still planning to address this at some point?

i am exploring winston to use in my project, now wondering is it worth taking the risk as this issue is open for more than 2 years. for me this seems like a basic function for any logging framework

Was this page helpful?
0 / 5 - 0 ratings