winstonjs/logform

JSON formatter doesn't stringify attributes in a consistent order

jorgelf opened this issue · 4 comments

Issue in Linux with Winston 3.2.1 and Node 10.15.1.

Though this isn't a critical error, I find it annoying that the JSON formatter doesn't stringify the resulting object with the attributes always in the same order.

For example, I have a File transport with the errors, splat, timestamp and JSON formatters and the following lines result in different log lines:

logger.info('TEST TEST');
logger.info('TEST %s', 'TEST');
{"message":"TEST TEST","level":"info","timestamp":"2019-04-05T15:09:52.076Z"}
{"level":"info","message":"TEST TEST","timestamp":"2019-04-05T15:09:52.077Z"}

Not sure if that change in behaviour could be considered a bug in Winston itself, but I think these problems could easily be avoided by having the JSON formatter stringify in a consistent order, most likely by using fastSafeStringify.stableStringify instead of the library's default method. Maybe it could be configurable with a new option?

Right now I'm avoiding this annoyance by setting a custom formatter before the JSON one that resets the options' keys in alphabetical order, in case someone has this problem as well:

winston.format(options => {
        var keys = Object.keys(options).sort();
        for (var i = 0, len = keys.length; i<len; i++) {
            var value = options[keys[i]];
            delete options[keys[i]];
            options[keys[i]] = value;
        }
        return options;
})()
DABH commented

Totally valid request. If you'd like to add a stable option to the JSON formatter that uses stableStringify if true, we'd be happy to accept a PR. I imagine there's some performance cost to doing that sorting, which is why it's off by default.

Thanks @DABH.

PR created, hope everything is fine.

This did this trick for me

    const formatForFile = winston.format.printf((info) => {
      const uid = info.uid ? `,"uid":"${info.uid}"` : ''
      const meta = info.meta ? `,"meta":${JSON.stringify(info.meta)}` : ''
      return `{"timestamp":"${info.timestamp}","level":"${info.level}","context":"${info.context}","message":"${info.message}"${uid}${meta}}`
    })

you can try this one, work for me

format.printf(({ timestamp, level, message, ...leftOver }) =>
                    JSON.stringify({
                        timestamp,
                        level,
                        message,
                        ...(leftOver || {}),
                    }),
                )