Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

It looks like 3.0 is logging {} for Error objects #1243

Closed
evertpieterse opened this issue Mar 19, 2018 · 13 comments
Closed

It looks like 3.0 is logging {} for Error objects #1243

evertpieterse opened this issue Mar 19, 2018 · 13 comments

Comments

@evertpieterse
Copy link

evertpieterse commented Mar 19, 2018

Hi All
I am having some hassle with winston 3.0 logging empty error objects when it is using a json format.

module.exports = winston.createLogger({
  format: format.combine(
    format.label({ label: 'my label'}),
    format.timestamp(),
    format.prettyPrint()
  ),
  transports: [
    new (winston.transports.Console)({
      level: error
    })
  ]
}); 

logs the expected

 {error: [stack trace here] }

while

module.exports = winston.createLogger({
  format: format.combine(
    format.label({ label: 'my label'}),
    format.timestamp(),
    format.json()
  ),
  transports: [
    new (winston.transports.Console)({
      level: error
    })
  ]
}); 

logs the erroneous

{error: {} } 

when it is passed

logger.log({
  error: new Error('my error message')
 });

Manually creating a format with

winston.format.printf

also returned the erroneous result.

Does anybody know of a workaround for this?

@evertpieterse
Copy link
Author

A workaround:

function errorReplacer(key, value) {
  if (value instanceof Error) {
    return { message: value.message, stack: value.stack };
  }
  return value;
}

const logFormat = winston.format.printf((info) => {
  return `${JSON.stringify(info, errorReplacer)}`;
});

module.exports = winston.createLogger({
  format: format.combine(
    format.label({ label: 'my label' }),
    format.timestamp(),
    logFormat
  ), ...

Hope it helps.

@pcothenet
Copy link

pcothenet commented Mar 26, 2018

Workaround didn't work for me (info.message is getting lost somewhere)

Variant workaround:

const winston = require('winston')
const MESSAGE = Symbol.for('message')

/*
 * function replacer (key, value)
 * Handles proper stringification of Buffer output.
 */
const replacer = function (key, value) {
  return value instanceof Buffer
    ? value.toString('base64')
    : value
}

var json_log = winston.format(function (info, opts) {
  var display = info
  if (info._error) {
    display = Object.assign({}, info, { message: info._error })
    delete display._error
  }
  info[MESSAGE] = JSON.stringify(display, opts.replacer || replacer, opts.space)
  return info
})

var preserve_error_message = format(function (info, opts) {
  if (_.isError(info)) {
    info._error = info.message
  }
  return info
})

const logger = winston.createLogger({
  levels: ...,
  format: format.combine(format.timestamp(), preserve_error_message()),
  transports: [
    new transports.Console({
    level: this.config.console.level,
    format: json_log({ space: 2 })
  })
]

That doesn't seem particularly elegant, so an official to preserve info.message when info is an Error would be ideal (it works when info is a regular Object)

@dhensen
Copy link

dhensen commented Mar 26, 2018

I'm running into this as well. Shouldn't winston handle this out of the box? @indexzero what do you think about this?

@dhensen
Copy link

dhensen commented Mar 26, 2018

After looking at the logform json implementation I now see how the opts.replacer is supposed to be used and I came up with another solution when combining this stackoverflow post with the existing Buffer stringification:

'use strict';

const winston = require('../');

const logger = winston.createLogger({
    format: winston.format.combine(
        winston.format.json({ replacer: replaceErrors })
    ),
    transports: [
        new winston.transports.Console(),
    ]
});

function replaceErrors(key, value) {
    if (value instanceof Buffer) {
        return value.toString('base64');
    } else if (value instanceof Error) {
        var error = {};

        Object.getOwnPropertyNames(value).forEach(function (key) {
            error[key] = value[key];
        });

        return error;
    }

    return value;
}


var error = new Error('foooo');
error.my_custom_stuff = 'bar';

logger.log('error', 'Hello, this is a raw logging event', { error });

which prints:

{"error":{"stack":"Error: foooo\n    at Object.<anonymous> (/home/dino/work/winston/examples/json.js:31:13)\n    at Module._compile (module.js:662:30)\n    at Object.Module._extensions..js (module.js:673:10)\n    at Module.load (module.js:575:32)\n    at tryModuleLoad (module.js:515:12)\n    at Function.Module._load (module.js:507:3)\n    at Function.Module.runMain (module.js:703:10)\n    at startup (bootstrap_node.js:193:16)\n    at bootstrap_node.js:660:3","message":"foooo","my_custom_stuff":"bar"},"level":"error","message":"Hello, this is a raw logging event"}

I no longer think this should be handled outside the box, because I think a logging library should not determine how your error should be serialized into a log message, you define that yourself.

@DABH
Copy link
Contributor

DABH commented Apr 1, 2018

Not sure of your exact use case but why aren't you logging errors directly:

logger.log(new Error('my error message'));

(That works as of #1234).

If you do need the error object inside a larger object you're logging, then it's worth noting Node.js Errors are not JSON objects, but there are things like https://www.npmjs.com/package/utils-error-to-json or perhaps doing a parse(stringify(error)) that would make this work with the JSON formatter. It seems like that's what @dhensen 's solution does, converting the Error to JSON -- so you could do something like that, use a package, etc., but this generally shouldn't be the winston json formatter's concern, because who knows what kind of custom types you might be passing to the json formatter :)

@DABH
Copy link
Contributor

DABH commented Apr 5, 2018

Going to go ahead and mark this closed, feel free to re-open or make a new issue if new problems/questions arise! Thanks!

@DABH DABH closed this as completed Apr 5, 2018
@lzambarda
Copy link

lzambarda commented Aug 8, 2018

Not even parse(stringify(error)) works as you then still lose the stack trace.
I managed to make this work by creating a custom formatter like this one:

const formatErrorConverter = winston.format(
    info =>
        info instanceof Error
            ? Object.assign({ level: info.level, message: info.message, stack: info.stack }, info)
            : info,
);

And it works quite well!

@mir3z
Copy link

mir3z commented Sep 21, 2018

instance of Error works only for top-level formatter. It does NOT work for transport-level formatters. Can anyone explain why?

@LukasBombach
Copy link

More concise version:

function format() {
  const replaceError = ({ label, level, message, stack }) => ({ label, level, message, stack });
  const replacer = (key, value) => value instanceof Error ? replaceError(value) : value;
  return combine(label({ label: 'ssr server log' }), format.json({ replacer }));
}

const logger = createLogger({
  format: format(),
});

But this is what I actually do to have pretty logs locally and JSON errors in production:

function prodFormat() {
  const replaceError = ({ label, level, message, stack }) => ({ label, level, message, stack });
  const replacer = (key, value) => value instanceof Error ? replaceError(value) : value;
  return combine(label({ label: 'ssr server log' }), format.json({ replacer }));
}

function devFormat() {
  const formatMessage = info => `${info.level} ${info.message}`;
  const formatError = info => `${info.level} ${info.message}\n\n${info.stack}\n`;
  const format = info => info instanceof Error ? formatError(info) : formatMessage(info);
  return combine(colorize(), printf(format))
}

const logger = createLogger({
  level: process.env.LOG_LEVEL || 'info',
  exitOnError: false,
  transports: [new transports.Console()],
  format: isProd ? prodFormat() : devFormat(),
});

which gives me this in production

bildschirmfoto 2018-09-21 um 16 46 35

and this in development

bildschirmfoto 2018-09-21 um 16 47 04

@slsriehl
Copy link

I wrote this to support custom error classes with unknown properties, and also, to clean up the stack because those newlines are horrible in json-based logging.

const replacer = (key, value) => {
  if (value instanceof Error) {
    return Object.getOwnPropertyNames(value).reduce((all, valKey) => {
      if(valKey === 'stack') {
        return {
          ...all,
          at: value[valKey].split('\n').filter(va => va.trim().slice(0, 5) != 'Error').map((va, i) => `stack ${i} ${va.trim().slice(3).trim()}`)
        }
      } else {
        return {
          ...all,
          [valKey]: value[valKey]
        }
      }
      
    }, {});

  } else {
    return value;
  }
}

result:

{"at":["stack 0 /Users/sarahriehl/Documents/code/boilerplate/logging/routes/log.js:24:15","stack 1 Layer.handle [as handle_request] (/Users/sarahriehl/Documents/code/boilerplate/logging/node_modules/express/lib/router/layer.js:95:5)","stack 2 next (/Users/sarahriehl/Documents/code/boilerplate/logging/node_modules/express/lib/router/route.js:137:13)","stack 3 Route.dispatch (/Users/sarahriehl/Documents/code/boilerplate/logging/node_modules/express/lib/router/route.js:112:3)","stack 4 Layer.handle [as handle_request] (/Users/sarahriehl/Documents/code/boilerplate/logging/node_modules/express/lib/router/layer.js:95:5)","stack 5 /Users/sarahriehl/Documents/code/boilerplate/logging/node_modules/express/lib/router/index.js:281:22","stack 6 Function.process_params (/Users/sarahriehl/Documents/code/boilerplate/logging/node_modules/express/lib/router/index.js:335:12)","stack 7 next (/Users/sarahriehl/Documents/code/boilerplate/logging/node_modules/express/lib/router/index.js:275:10)","stack 8 Function.handle (/Users/sarahriehl/Documents/code/boilerplate/logging/node_modules/express/lib/router/index.js:174:3)","stack 9 router (/Users/sarahriehl/Documents/code/boilerplate/logging/node_modules/express/lib/router/index.js:47:12)"],"message":"argh!!!","level":"error","timestamp":"Thu 2019-02-14 01:58:58 -0600"}

@mrdulin
Copy link

mrdulin commented May 7, 2019

I made an example for local development environment and GCP Stackdriver Logging.

mrdulin/blog#75

@Atishay96
Copy link

Atishay96 commented May 14, 2019

I was facing the same issue so i wrote this package, utils-deep-clone. Check it out.

@cuzzlor
Copy link
Contributor

cuzzlor commented Oct 3, 2022

You can fix this via a replacer on the json format options that pulls out the non-enumerable message and stack props into a new plain enumerable object for logging.

import { createLogger, format } from 'winston'
import { Console } from 'winston/lib/winston/transports'

const serializeError = ({ message, stack, ...rest }: Error): object => ({
  message,
  stack,
  ...rest,
})

const logger = createLogger({
  transports: [
    new Console({
      format: format.json({ replacer: (_key, value) => (value instanceof Error ? serializeError(value) : value) })
    }),
  ],
})

danmoorenhs added a commit to nhsconnect/prm-repo-ehr-repository that referenced this issue Oct 5, 2022
Sequelize does its magic deleted_at paranoid thing and causes
non obvious failure.

Winston 3 it looks like won't log an error if you try, see:
winstonjs/winston#1243

Co-authored-by: Rosie Amphlett <rosie.amphlett1@nhs.net>
Co-authored-by: syedsadiqali <syed.ali69@nhs.net>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests