winston: [3.0.0] Error object is not parsed or printed
Please tell us about your environment:
- winstonversion?- [-] winston@3.0.0-rc5
 
- [-] 
- node -voutputs: v8.11.1
- Operating System? (Windows, macOS, or Linux) macOS
- Language? (all | TypeScript X.X | ES6/7 | ES5 | Dart) All
What is the problem?
Logging a node Error object results in an empty message:
Example:
const winston = require('winston');
const { createLogger, format, transports } = winston;
const logger = createLogger({
  transports: [
    new transports.Console()
  ]
});
let err = new Error('this is a test');
logger.log({level: 'error', message: err});
Resulting output:
% node test.js
{"level":"error","message":{}}
Also:
logger.error(new Error('hello'))
Results in:
{"level":"error"}
What do you expect to happen instead?
I Expect the message key to have at least the error message included in it. If I try a custom formatter, info also doesn’t have the error object in it so it must be stripped out somewhere?
Other information
Let me know how I can help - happy to flick a PR but I don’t know my way around winston@3.0.0 enough yet to find it
About this issue
- Original URL
- State: closed
- Created 6 years ago
- Reactions: 96
- Comments: 76 (8 by maintainers)
Commits related to this issue
- [fix] Better handling of `new Error(string)` throughout the pipeline(s). Fixes #1338, #1486 (#1562) * [tiny dist] Whitespace. package-lock.json * [test] Add E2E integration tests with logform `err... — committed to Mizumaki/winston by indexzero 5 years ago
- winston: Add workaround for https://github.com/winstonjs/winston/issues/1338 format.errors({ stack: true }) doesn't seem to work when using a different formatter per transport (https://github.com/win... — committed to bmaupin/graylog-syslog-examples by bmaupin 3 years ago
- Enhance Loggin This will enhance the logging of error messages in catch blocks (the javascript Error object). Due to a problem on wiston, when logging an error object, the winston just prints the ... — committed to rrg92/wiki by rrg92 2 years ago
No really, this is unacceptable for a logging library. Maintainer should simply put a well highlighted example on docs where it is shown how to log an error, even defining custom printf format and non json format and where you can log error with something like logger.error(“something”, err) and logger.error(err) Winston seemed to be great but this issue is incredibly unacceptable
Am I missing something, or is it a complete headache (or even impossible?) to get the same output one easily gets from console.log/error/warn?
What is the equivalent winston code to get the same output as
console.error('Caught error:', error);?And where is the documentation for the parameters taken by the convenience methods on the logger object?
The feature is supported since winston@3.2.0
Example usage:
cc @HRK44 @the-vampiire
I switched to pino and never looked back instead of wasting so much time trying to make something work that should have been working out of the box since it’s the most common use case.
@dandv
This doesn’t work because, unlike
console.log(), the winston’slogger.<level>(message)takes only one parameter called message. That message parameter is either an object or a string (someone correct me if I’m wrong but that’s my understanding).Note that you can also use
logger.log({level: <level>, message: <message>}). To learn more about these two functions I would recomend reading this part of the docs: Winston Docs on Log Levels. Be sure to read through Using Logging LevelsI can not definitively say why this does not output the stack, but I do know this is not a problem with winston. If you try
console.log(`Caught error: ${e}`)it also does not include the stack. I haven’t worked with template literals much so either template literals doesn’t work well with objects, or javascript’s console.log recognizes the object as an error object and thus only outputs the the message property. That’s my best guess.This one gets to the heart of what this bug thread is about. First you must understand some technical details about javascript. Note that if you try
console.log(`Caught error: ${JSON.stringify(e)}`)you also get that same outputCaught error: {}. As @indexzero explained:Basically, because
messageandstackproperties are non-enumerable,JSON.stringifyskips over those properties which is how you end up with an empty object{}. To understand enumerability better I recommend reading this Enumerability and ownership of properties.Luckily, because of the way winston 3.0 was designed (props to the winston team) we have a workaround for this that @indexzero gave. I’ll help explain it. First you create this function:
From the docs Streams, objectMode, and info objects the info object has two properties,
info.level, andinfo.message. Thatinfo.messageproperty IS the error object if that is all you passed in. So we create a new object wheremessage.stackandmessage.message(Think of it asError.stackandError.message) are now enumerable, and we include what ever other properties that may also be attached to that error object.Next you’ll create this logger which uses the
enumerateErrorFormat()function above:This will take whatever
messageyou pass in and check if it is an error object. If it is then it will fix the enumeration problem. Then it passes message toformat.jsonwhich will stringify any object (error or not). If it’s not an object then it’s a string andformat.jsoneffectivley does nothing, and you’re home free!Still, it would be nice if we didn’t have to create that
enumerateErrorFormatsince error objects are commonly logged. As I understand it the winston team is working on a fix that will be released in a later version.Some final notes. This only works if you use
logger.log({level: <level>, message: <message>})where message is the error object. Example:There is another bug in winston where this code does not work, as I explained in my other post above:
For some reason the
info.messageproperty is undefined when we uselogger.error(err). Hopefully @indexzero can figure this one out.If anyone is looking for a quick work-around you can include
enumerateErrorFormatin your logger’s format for now. We will hopefully have a fix for this out before3.0.0next week (or shortly after in3.0.1)Can this please get fixed for logger.error, etc?
It is cumbersome and verbose to use
logger.log, especially since withlogger.erroryou can easily add multiple arguments.@will093 same here. Been on that issue again and don’t get it why my
console.logis nice and clean and winston format is shit.using @tiagonapoli and @will093’s solution of adding it to just the parent seems to be the easiest way to support directly logging errors and still logging messages – here’s a full example of a minimal setup w/ timestamps:
works with a stack when called with an error like:
logger.error(error), works with a string when called likelogger.error('a regular message'), looks like this in my logs:this does not attempt to solve winston’s
logger.error('message here', error)-incompatibility w/console.log, which @tiagonapoli’s more involved solution seems to do.Also, if you like json logs you can drop the
logFormatterhere and usewinston.format.json()in its place, which will still include the stack – but it isn’t pretty.Update: because there are still issues with this I have been doing the following for a while and its been working great
I’m not a committer but I’m probably correct to say that this is not going to be “fixed” because it’s not broken. Winston is worth using. You just need to configure it - the best advice for me is above at https://github.com/winstonjs/winston/issues/1338#issuecomment-506354691
@OBrown92 I have faced the same issue today. I can confirm, that it works, if
format.errors({ stack: true }), is applied to logger, not to transport. In this case it’s possible to use bothlogger.error(error);andlogger.error('Something went wrong', error). However, there is a problem, when I try to applyformat.errors({ stack: true })to chosen transport. In this case I getundefinedforlogger.error(error);, butlogger.error('Something went wrong', error)works properly.I’m not sure if it’s expected behavior or it’s a bug, but I spent a lot of time to find the cause, so please fix it or mention about that somewhere in your documentation. It would be really helpul.
Anyway, I am very grateful for your work on this great project.
Yeah this gave me enough headaches that Winston started to seem like way more trouble than it was worth for my relatively simple use case… I ended up just writing my own small logger class, and I’d recommend others do the same unless Winston provides something you REALLY need.
Until a fix is published I use the following workaround:
So this is still not fixed after 1year? Do I have to hack winston logger code to make it work?
This is my take on how to log errors using Winston. It’s not unique, a lot of peeps above have working solutions based around the same concepts too.
Background I’m using
@jsdevtools/onoto wrap arbitrary object types into custom errors, but regardless, this solution still seems to work fine on native node errors (e.g. fs eperm errors), and custom error classes.Explanation Basically, I rely on
format.errors({stack:true})andformat.metadata(). As mentioned by https://github.com/winstonjs/winston/issues/1338#issuecomment-532327143, this has to be in the parent formatter.Metadata helps to shift all the error object’s custom properties to
info.metadata.I wanted to print 3 types of information: the error message, the stacktrace, and the error object’s properties. The error message was already plaintext. I pretty-printed the stack
info.metadata.stackusing thepretty-errormodule. For the error object’s properties, I didn’t want the stacktrace to appear again, so I cloned the object, and deleted the stacktrace property. I then pretty-printed the error object usingfast-safe-stringify, which is the same stringify module that winston relies on.PS: I also find the solution mentioned in https://github.com/winstonjs/winston/issues/1338#issuecomment-506354691 to be a good alternative. I.e. using
logger.warn("Oh no", { error: new Error() }), then referencinginfo.errorin your custom formatter.I found two solutions, the first one is to use
format.errors, mentioned on logform in the parent logger, then create a messageFormatter usingformat.printfand conditionally add astackfield extracted frominfo(format.errors({ stack: true})will add that).The other solution, that I preferred was hack into winston level loggers:
It seems this way I can get error logging similar to
console.logWe definitely used this form in winston2.x with no problems.
winston.err('some message', err);along withwinston.error(err)the aboveenumerateErrorFormatfixeswinston.error(err)but not the use case with err as second parameter.I use winston with a 3rd party transport (
@google-cloud/logging-winston) so I have a little less control over the syntax. Plus I find this just more intuitive:When logging to the console I concatenate the stack onto the message. But the result is something like this:
Since winston concatenates
meta.messageonto the original message, there is the weirdandsomethingand the duplicate message that is also printed in the stack. This is described in #1660 .It looks like #1664 is trying to fix this. In the meantime, I wrote a formatter that “undos” that concatenation: https://github.com/winstonjs/winston/issues/1660#issuecomment-512226578
Guys, really? this is frustrating…
Update on this folks:
errorsformat inlogformwith winston: https://github.com/winstonjs/winston/compare/gh-1338Hoping to get this sewn up and shipped in the next couple of days. It is the next to last issue in our
winston@3.2.0trackerOkay, I discovered something. My comment from September 3rd is wrong. This isn’t a problem with the default logger. This is a problem with where you define
leveland/orformat. @DABH here is your old code:If you remove this:
And replace it with this:
Then the
info.message === undefinedproblem shows up. I believe it should be okay to specify level and format for each transport; and I’m almost certain this was allowed in Winston 2.0.Here is your code sample with my code change so you can easily run and test:
Hopefully this helps get to the root of the problem.
I also still running into the issue. When i call the error like
logger.error(error);I get onlyundefined. Only if I call it likelogger.error('Something went wrong', error)I get the complete error and can parse it.Still having this issue, lost bunch of time to figure this out, solution from @the-vampiire works well.
Why is this ticket closed?
@sandrocsimas I noticed that you need to give the enumerateErrorFormat function to the default formatter of the logger in order to make it work.
Formatter
I still don’t understand why tho
^^ That is the expected usage. Since mere mortals will never figure this out, it should be documented.
Doesn’t work
My 2¢
Example for
logger.error("Error during schema stitching", e);@tiagonapoli your solution about using
format.errorson the parent logger worked for me:It is fairly painful configuring this logger… Could it not just behave like
console.logout the box?I can verify that @tiagonapoli 's comment about how
format.errorshas to be on the parent logger is accurate. When I do something like this:Handling of
Errorobject is does as if they were a string. However, if I do something like this:Handling of
Errorobject works correctly.It seems to me that the bug here is that there should be no difference in behavior.
same problem here…
after looking the source code, i can see what params it accepts:
interface LeveledLogMethod { (message: string, callback: LogCallback): Logger; (message: string, meta: any, callback: LogCallback): Logger; (message: string, …meta: any[]): Logger; (infoObject: object): Logger; }
so if you pass the error object as first param, it will only take the message of the error because it only understands strings, and if you pass the error in the second param then you can access the stack trace in info.stack
btw i couldn’t find this anywhere in the docs
For anyone that is still dealing with this, this is the workaround formatter I’ve managed to come up with (snippet of my logger.js module):
For some reason
logger.error(new Error("hello"))only works if you define the formatter globally inwinston.createLogger🤔 and then you get the Error object ininfoin the formatter.If you define a formatter per transport then you have to use
logger.log({level: "error", message: new Error("FAILED")})and handle the Error object viainfo.messageinstead ofinfoto access the Error object.I’d guess there’s a bug when setting the format field in the transport options?
Those are just my 2 cents and what worked for me, I’m new to Winston and not seasoned in JavaScript so don’t quote me on anything.
@nvtuan305, did you try @indexzero’s solution exactly or did you edit it a bit? If so could you provide sample code? His code should work if you’re using
logger.log({level: ____, message: err});It won’t work if you’re doinglogger.info,logger.error, or any other flavor oflogger.<level>. I’m almost certain that is a bug as I specified above and it should be fixed in a later release.where does the trace come from?
The issue is caused by this bug : https://github.com/winstonjs/winston-transport/issues/31
@SamuelMaddox17 @indexzero Thank you! I tried using
logger.log({level: 'error', message: err});and it worksI think I’m experiencing the same bug as @sandrocsimas.
Here is my logger config:
logger.js
If I test it with this block of code:
Test A
where
new Error()doesn’t contain a message value, I get this output:Output A
Where
error: [object Object]is exactly what I expectedHowever, if I test it with this block of code:
Test B
Where
new Error()does contain a message value, I get this output:Output B
As you can see I get the same
error: undefinedthat @sandrocsimas gets. I expected to geterror: [object Object]Note, if I try this block of code:
Test C
Where I use
logger.loginstead oflogger.errorI get the same output as Output A above@indexzero, I tried to follow your workaround, but it’s not working. Do you know why?
Formatter:
Code:
Output:
Info object:
Where is the message attribute in the error log?
This is still an issue? I’m still having problems with this:
Very good explanation, I just want to add that with
logger.error(Caught error: ${e});you lose the stack because of the way that string literal works in javascript,`${e}`is exactly the same ase.toString(), so printing only the error message is the expected behavior.format.errors is not a function… well that’s a surprise.I am on the latest winston (3.2.1) and am still getting
undefinedwhen passing an error tologger.error@ezze had a great solution but the stack trace is forced onto a new line. here is a slightly modified version that puts it in one line (so its caught by simple grep of log files)
with output
<Error message> - trace: <stack trace>if there is an easier way to do this with the latest winston please let me know @indexzero. i am new to the library and was following the docs
This might be caused by
wintson-transport, see https://github.com/winstonjs/winston-transport/issues/31 for the issue and https://github.com/winstonjs/winston-transport/pull/34 for a PR.I created https://github.com/winstonjs/winston/pull/1527
This covers all options. However some tests fail so I have closed it for now. The failures are expected given the fix, but I don’t believe I am in a position to make the call to amend/delete the tests.
Failing build is here https://travis-ci.org/winstonjs/winston/jobs/453012141 and its obvious why the tests now fail when you read the test code: https://github.com/winstonjs/winston/blob/c42ab7fdc51b88db180a7dd90c52ce04ddd4e054/test/logger.test.js#L668
Thoughts?
FYI this is where I’ve gotten to after playing with this a bit:
Upon further investigation it seems that the
logger.errorproblem I explained above is only a problem when using the default logger. @DABH, I tried out your code and it does work for me, but when I switch it to the default logger it does not work:Second, I agree that
enumerateErrorFormatshould be added to the json format; and you’re probably right aboutmetaas well.Finally, I’d like to note the code example given by @DABH causes the stack to not “print pretty” if you will; at least on my machine running macOS High Sierra. This is what it looks like for me:
As you can see, when outputting the error with a to JSON function the newline characters
\ndo not create actual new lines. This is expected behavior when taking an object and converting it to JSON, but is probably not the behavior we’d actually want from a logger, at least when logging to console.Thank you for looking more into this @DABH