8

我最近刚刚切换到 Winston 进行日志记录,并在执行 exec 之后记录 mongoose 文档时发现了一个问题。

例子:

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

所以基本上我如何让winston日志记录以与从console.log相同的方式打印?我猜它必须在通过调用 toJSON() 记录之前如何被序列化。

我每次都必须手动调用 .toJSON() 还是让人们做其他事情来自动完成这项工作?

4

3 回答 3

4

警告

我认为 winston 的预期用途是首先记录字符串消息和(如果需要)附加元信息。此外,我不太明白您为什么要记录从 mongo 返回的整个集合,而不是 - 比如说 - 只是_ids (假设docs可能很大)。

介绍

我查看了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);

  ...

}

基本上,类型的单个参数object被解释为元。控制台传输层(默认)主要定义在winston/common.js中,下面是 meta 的处理方式:

 ... if (Object.keys(meta).length > 0) {
      output += ' ' + (
        options.prettyPrint
          ? ('\n' + util.inspect(meta, false, null, options.colorize))
          : exports.serialize(meta)
      );
    }

serialize方法迭代(递归)对象的所有键以形成最终字符串(而不是调用.toString或类似方法)。

建议的解决方案

两种解决方案都强制winston 将单个对象参数解释为元数据,而不是消息字符串。

如果您想支持不同的传输层,则必须对其进行修改。

更改winston源代码

只需 fork 存储库并对源代码进行相关更改。有很多方法可以实现它。一个丑陋的可能是:

 meta     = args.length === 1 ? {} :
          (typeof args[args.length - 1] === 'object' ? args.pop() : {}),

但是.serialize如果对象是芒果模型,最好在方法中添加特殊情况进行特殊处理,非常幼稚和不正确:

 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 += '}';
    }

(不幸的是,这种方法存在问题,因为 winston 复制了元数据,并且原型链中定义更高的所有方法都丢失了——否则它会像调用一样简单,obj.toJSON并且肯定会是最优雅和最强大的解决方案)

覆盖winston默认行为

var original = winston.log;
winston.log = function(){
    if(arguments.length === 2){
        original.call(winston, arguments[0], arguments[1], {});
    }
    else {
        original.apply(winston, arguments);
    }
}

说明:arguments[0]定义级别,因此arguments[1]是要记录的实际对象。

于 2014-10-17T22:22:14.080 回答
3

我结合了先前答案中的想法,提供了一种相当健壮的方法来注销元对象,我已经在生产中运行了好几个月,没有任何问题。

一般的想法是覆盖transport.log元对象并将其转换为 JSON 字符串,然后再返回。这确保了元对象作为对象保留,因此可以利用元对象的温斯顿好东西,例如prettyPrint.

下面是使用 prettyPrint 选项创建新记录器的代码:

var transport = new (winston.transports.Console)({
  prettyPrint: function(meta) {
    return util.format('%j', meta);
  }
});

var originalLog = transport.log;
transport.log = function(level, msg, meta, callback) {
  if(meta) {
    if(meta instanceof Error) {
      // Errors cannot be stringified.
      meta = meta.toString();

    } else {
      // Keep meta as an object so that winston formats it
      // nicely, but ensure we don't have any weird ObjectIds
      meta = JSON.parse(JSON.stringify(meta));
    }
  }
  return originalLog.call(transport, level, msg, meta, callback);
};

var logger = new (winston.Logger)({
  transports: [transport]
});

您现在可以像这样使用记录器:

logger.debug('My Mongoose document:', doc);

这将输出如下内容:

debug: My Mongoose document: {"_id":"56f0598b130b3cfb16d76b3d","name":"Bob"}

于 2016-03-25T11:45:30.760 回答
0

简单的解决方案是将 Mongoose 模型对象转换为 JSON 字符串并将其传递给 winston 函数。对于 Array,您可能必须在循环中调用函数。

winston.info(JSON.stringify(doc));
于 2016-01-28T15:55:50.340 回答