I recently just switched over to Winston for logging and noticed an issue when logging mongoose docs after an exec.
Example:
Model.find().exec(function (err, docs) {
console.log(docs) // Prints the collection fine
winston.info(docs) // Prints a ton on mongoose stuff, related to the query
});
So basically how do I get winston logging to print the same way as you get from console.log? I'm guessing it must how it is being serialised before being logged by calling toJSON().
Do I have to manually call .toJSON() each time or have people done something else to make this work automatically?
I think the intended use of winston is to log string messages in first place and (if needed) additional meta information. Moreover, I do not quite get why you would like to log the whole collection returned from mongo and not - let say - just _id
s (assuming docs
may be pretty big).
I looked into winston
source and here are relevant parts:
winston/logger.js
Logger.prototype.log = function (level) {
var self = this,
args = Array.prototype.slice.call(arguments, 1);
...
var callback = typeof args[args.length - 1] === 'function' ? args.pop() : null,
meta = typeof args[args.length - 1] === 'object' ? args.pop() : {},
msg = util.format.apply(null, args);
...
}
Basically, single argument of type object
is interpreted as the meta.
Console transport layer (default) is mainly defined in winston/common.js and here is how meta is handled:
... if (Object.keys(meta).length > 0) {
output += ' ' + (
options.prettyPrint
? ('\n' + util.inspect(meta, false, null, options.colorize))
: exports.serialize(meta)
);
}
serialize method iterates (recursively) over all keys of an object to form the final string (instead of calling .toString
or similar).
Both solutions force winston to interpret a single object argument not as meta but as the message string.
If you want to support different transport layers than they have to be modified.
Just fork the repo and make relevant changes to the source code. There is plenty of ways to accomplish it. One ugly could be:
meta = args.length === 1 ? {} :
(typeof args[args.length - 1] === 'object' ? args.pop() : {}),
But much better would be to add special case in the .serialize
method make special treatment if the object is mangoose model, very naive and incorrect:
else if ('_doc' in obj && 'save' in obj){
var result = [];
msg += '{'
for(var key in obj['_doc']){
result.push (key + ':' + obj['_doc'][key]);
}
msg += result.join(', ');
msg += '}';
}
(Unfortunately there is a problem with this approach, as winston makes copy of the meta and all methods defined higher in prototypical chain are lost -- otherwise it would as easy as calling obj.toJSON
and for sure it would be the most elegant and robust solution)
var original = winston.log;
winston.log = function(){
if(arguments.length === 2){
original.call(winston, arguments[0], arguments[1], {});
}
else {
original.apply(winston, arguments);
}
}
Explanation: arguments[0]
defines level so arguments[1]
is the actual object to be logged.