Winston: [3.0.0]未解析或打印错误对象

创建于 2018-05-29  ·  68评论  ·  资料来源: winstonjs/winston

请告诉我们您的环境:

  • _ winston版本?_
  • _ node -v输出:_ v8.11.1
  • _操作系统?_(Windows,macOS或Linux)macOS
  • _Language?_(全部| TypeScript XX | ES6 / 7 | ES5 | Dart)全部

问题是什么?

记录节点Error对象会产生一条空消息:

例子:

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

结果输出:

% node test.js
{"level":"error","message":{}}

还:

logger.error(new Error('hello'))

结果是:

{"level":"error"}

您期望发生什么呢?

我希望消息密钥至少包含错误消息。 如果我尝试使用自定义格式化程序, info也没有错误对象,那么必须在某个地方将其删除?

其他资讯

让我知道我能为您提供的帮助-很高兴完成PR,但是我对[email protected]想法还不了解

bug important

最有用的评论

确实不行,这对于日志记录库是不可接受的。
维护人员应该在文档上简单地突出显示一个示例,该示例显示了如何记录错误,甚至定义了自定义printf格式和非json格式,并且可以在其中记录诸如logger.error(“ something”,err)和logger之类的错误。 。错误(错误)
温斯顿似乎很棒,但是这个问题令人难以接受

所有68条评论

我们对此进行了一些测试,但是显然我们需要更多。 幕后情况:

  1. 您的Error实例沿着objectMode流管道链传递
  2. 为默认格式Loggerjson (参见: json格式码logform
  3. messagestack上性能Error是不可枚举其导致JSON.stringify输出的东西,一个不期望的。
console.log(JSON.stringify(new Error('lol nothing here')));
// '{}'

从设计的角度来看, winston@3引入了formats来解决此类问题,以提高性能。 说到性能,有趣的是pino在这里做了一些有趣的事情。 可能的解决方案是以默认json格式实现类似于asJson的操作。

如果有人在寻找快速解决方法,您可以暂时以记录器的格式包含enumerateErrorFormat 。 我们希望下周在3.0.0之前(或在3.0.1之后不久)有针对此问题的修复程序

const winston = require('../');
const { createLogger, format, transports } = winston;

const enumerateErrorFormat = format(info => {
  if (info.message instanceof Error) {
    info.message = Object.assign({
      message: info.message.message,
      stack: info.message.stack
    }, info.message);
  }

  if (info instanceof Error) {
    return Object.assign({
      message: info.message,
      stack: info.stack
    }, info);
  }

  return info;
});

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

// Error as message
console.log('Run FIRST test...');
logger.log({ level: 'error', message: new Error('FIRST test error') });

// Error as info (one argument)
console.log('\nRun SECOND test...');
const err = new Error('SECOND test error');
err.level = 'info';
logger.info(err);

// Error as info (two arguments);
console.log('\nRun THIRD test...');
logger.log('info', new Error('THIRD test error'));

@indexzero ,我尝试按照您的解决方法进行操作,但是它不起作用。 你知道为什么吗?

格式化程序:
``` javascript const level = settings.debug ? 'debug' : 'info'; const printFormat = winston.format.printf(info => $ {info.timestamp}-$ {info.level}:$ {info.message}`);
const enumerateErrorFormat = winston.format(info => {
if(info.message instanceof Error){
info.message = Object.assign({
讯息:info.message.message,
堆栈:info.message.stack,
},info.message);
}
if(info instanceof Error){
返回Object.assign({
讯息:info.message,
堆栈:info.stack,
},信息);
}
返回信息;
});

const consoleLogger = winston.createLogger({
等级,
格式:winston.format.timestamp(),
运输:[
新的winston.transports.Console({
格式:winston.format.combine(
winston.format.colorize(),
enumerateErrorFormat(),
printFormat,
),
}),
],
});
Code: javascript
尝试 {
//一些代码抛出错误
} catch(err){
logger.error(err);
}
Output:
2018-06-28T21:17:25.140Z-错误:未定义
Info object: javascript
{level:'\ u001b [31merror \ u001b [39m',timestamp:'2018-06-28T21:17:25.140Z',[Symbol(level)]:'error'}
``
错误日志中的message属性在哪里?

@sandrocsimas我注意到您需要将enumerateErrorFormat函数赋予记录器的默认格式化程序,以使其正常工作。

格式化程序

const consoleLogger = winston.createLogger({
  level,
  format: winston.format.combine(
    winston.format.timestamp(),
    enumerateErrorFormat()
  ),
  transports: [
    new winston.transports.Console({
      format: winston.format.combine(
        winston.format.colorize(),
        printFormat,
      ),
    }),
  ],
});

我还是不明白为什么

我认为我遇到了与@sandrocsimas相同的错误。

这是我的记录器配置:

logger.js

const winston = require('winston');
const {configure, format} = winston;
const {combine, colorize, timestamp, printf} = format;

const enumerateErrorFormat = format(info => {
  if (info.message instanceof Error) {
    info.message = Object.assign({
      message: info.message.message,
      stack: info.message.stack
    }, info.message);
  }

  if (info instanceof Error) {
    return Object.assign({
      message: info.message,
      stack: info.stack
    }, info);
  }

  return info;
});

const myConsoleFormat = printf(info => {
  console.log('** Info Object: **');
  console.log(info);
  console.log('** Winston Output: **');
  return `${info.level}: ${info.message}`;
});

winston.configure({
  transports: [
    new winston.transports.Console({
      format: combine(
        colorize(),
        enumerateErrorFormat(),
        myConsoleFormat
      ),
    })
  ]
});

如果我使用以下代码块进行测试:

测试A

const logger = require('winston');
try {
  throw(new Error());
} catch (err) {
  logger.error(err);
}

其中new Error()不包含消息值,我得到以下输出:

输出A

** Info Object: **
{ message: 
   { message: '',
     stack: 'Error\n    at Object.<anonymous> (app.js:21:9)\n    at Module._compile (module.js:652:30)\n    at Object.Module._extensions..js (module.js:663:10)\n    at Module.load (module.js:565:32)\n    at tryModuleLoad (module.js:505:12)\n    at Function.Module._load (module.js:497:3)\n    at Module.require (module.js:596:17)\n    at require (internal/module.js:11:18)\n    at Object.<anonymous> (server.js:11:13)\n    at Module._compile (module.js:652:30)' },
  level: '\u001b[31merror\u001b[39m',
  [Symbol(level)]: 'error',
  [Symbol(message)]: '{"message":{},"level":"error"}' }
** Winston Output: **
error: [object Object]

error: [object Object]正是我所期望的

但是,如果我使用以下代码块进行测试:

测试B

const logger = require('winston');
try {
  throw(new Error('This causes error: undefined'));
} catch (err) {
  logger.error(err);
}

其中new Error()确实包含消息值,我得到以下输出:

输出B

** Info Object: **
{ level: '\u001b[31merror\u001b[39m',
  [Symbol(level)]: 'error',
  [Symbol(message)]: '{"level":"error"}' }
** Winston Output: **
error: undefined

如您所见,我得到了与@sandrocsimas相同的error: undefined 。 我期望得到error: [object Object]

注意,如果我尝试以下代码块:

测试C

const logger = require('winston');
try {
  throw(new Error('This should work'));
} catch (err) {
  logger.log({level: 'error', message: err});
}

在我使用logger.log而不是logger.error我得到的输出与上面的输出A相同

我也有同样的问题。 我是温斯顿的新手。 我尝试了@indexzero解决方案,但无法正常工作。 你有什么解决办法吗?

@ nvtuan305 ,您是否尝试了@indexzero的解决方案,还是做了一点编辑? 如果可以,您可以提供示例代码吗? 如果您使用logger.log({level: ____, message: err}); ,那么他的代码应该可以工作;如果您使用logger.infologger.error或其他任何类型的logger.<level>则他的代码将无法工作。 我几乎可以肯定这是我上面指定的错误,应该在以后的版本中修复。

我是否错过了某些东西,还是要从console.log / error / warn轻松获得相同的输出是完全头痛(甚至是不可能的?)?

try {
   // ...
  throw new Error('foo');
} catch (e) {
  console.error('Caught error:', e);  // convenient, informative
  logger.error('Caught error:', e);  // nope, the second parameter is something else (couldn't find docs)
  logger.error(`Caught error: ${e}`);  // stack lost
  logger.error(`Caught error: ${JSON.stringify(e)}`);  // Caught error: {}
}

获得与输出相同的等价Winston代码是什么
console.error('Caught error:', error);

记录器对象上的便捷方法所采用的参数的文档在哪里?

@dandv

logger.error('Caught error:', e);

这是行不通的,因为与console.log() ,winston的logger.<level>(message)只接受一个称为message的参数。 该消息参数可以是对象,也可以是字符串(如果我记错了,请纠正我,但这是我的理解)。

请注意,您还可以使用logger.log({level: <level>, message: <message>}) 。 要了解有关这两个功能的更多信息,建议您阅读文档的这一部分: Log Levels上的Winston Docs 。 请务必通读使用日志记录级别

logger.error(`Caught error: ${e}`);

我无法确切地说出为什么它不输出堆栈,但是我知道这对Winston来说不​​是问题。 如果您尝试console.log(`Caught error: ${e}`)则它也不包括堆栈。 我没有太多使用模板文字,因此模板文字不能很好地与对象一起使用,或者javascript的console.log将该对象识别为错误对象,因此仅输出message属性。 那是我最好的猜测。

logger.error(`Caught error: ${JSON.stringify(e)}`)

这是此错误线程所要解决的问题的核心。 首先,您必须了解有关javascript的一些技术细节。 请注意,如果您尝试console.log(`Caught error: ${JSON.stringify(e)}`) ,也会获得相同的输出Caught error: {} 。 正如@indexzero解释的那样:

messagestack上的属性Error是不可枚举导致JSON.stringify输出的东西,一个不期待。

基本上,因为messagestack属性是不可枚举的,所以JSON.stringify跳过这些属性,这就是您最终得到一个空对象{} 。 为了更好地理解可枚举性,我建议阅读此可枚举性和属性所有权

幸运的是,由于Winston 3.0的设计方式(对Winston团队的支持),我们有了@indexzero所提供的解决方法。 我会帮你解释的。 首先,您创建以下功能:

const enumerateErrorFormat = format(info => {
  if (info.message instanceof Error) {
    info.message = Object.assign({
      message: info.message.message,
      stack: info.message.stack
    }, info.message);
  }

  if (info instanceof Error) {
    return Object.assign({
      message: info.message,
      stack: info.stack
    }, info);
  }

  return info;
});

从docs Streams,objectMode和info对象开始,info对象具有两个属性info.levelinfo.message 。 这info.message属性错误的对象,如果这是你在通过了所有,所以我们创建了一个新的对象,其中message.stackmessage.message (把它看成Error.stackError.message )现在可以枚举,并且我们还包括可能附加到该错误对象的其他所有属性。

接下来,您将创建此记录器,该记录器使用上面的enumerateErrorFormat()函数:

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

这将采用您传入的message ,并检查它是否是错误对象。 如果是这样,它将解决枚举问题。 然后它将消息传递给format.json ,它将对任何对象(错误与否)进行字符串化。 如果不是对象, format.json字符串,

不过,如果我们不必创建enumerateErrorFormat会很好,因为通常会记录错误对象。 据我了解,温斯顿团队正在研究将在更高版本中发布的修复程序。

一些最后的笔记。 仅当您使用logger.log({level: <level>, message: <message>})时才有效,其中message是错误对象。 例子:

try {
  throw(new Error('This should work'));
} catch (err) {
  logger.log({level: 'error', message: err});
}

正如我在上面的另一篇文章中所解释的那样,winston中还有另一个错误,该错误代码无法正常工作:

try {
  throw(new Error('This will not work'));
} catch (err) {
  logger.error(err);
}

由于某些原因,当我们使用logger.error(err)时, info.message属性是不确定的。 希望@indexzero可以解决这一问题。

@ SamuelMaddox17 @indexzero谢谢! 我尝试使用logger.log({level: 'error', message: err}); ,它有效

能否解决logger.error等问题?

使用logger.log既麻烦又冗长,尤其是因为有了logger.error您可以轻松添加多个参数。

嘿,我正在调查这个。 @indexzero :仍然认为最好的主意是在默认情况下将enumerateErrorFormat功能添加到json格式化程序中? 我们是否需要分别担心meta是否是Error而不只是object (我猜如果我们不处理这种情况的话,人们会抱怨吗?) ? 另外,我正在使用master ,但似乎logger.error可以通过上述@indexzero / @ SamuelMaddox17的解决方案为我工作:

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

const enumerateErrorFormat = format(info => {
  if (info.message instanceof Error) {
    info.message = Object.assign({
      message: info.message.message,
      stack: info.message.stack
    }, info.message);
  }

  if (info instanceof Error) {
    return Object.assign({
      message: info.message,
      stack: info.stack
    }, info);
  }

  return info;
});

const logger = winston.createLogger({
  level: 'debug',
  format: format.combine(
    enumerateErrorFormat(),
    format.json()
  ),
  transports: [
    new winston.transports.Console(),
  ],
});

logger.error(new Error('whatever'));

经过进一步的调查,看来我上面解释的logger.error问题仅是使用默认记录器时的问题。 @DABH ,我尝试了您的代码,它确实对我

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

const enumerateErrorFormat = format(info => {
  if (info.message instanceof Error) {
    info.message = Object.assign({
      message: info.message.message,
      stack: info.message.stack
    }, info.message);
  }

  if (info instanceof Error) {
    return Object.assign({
      message: info.message,
      stack: info.stack
    }, info);
  }

  return info;
});

winston.configure({
  transports: [
    new winston.transports.Console({
      level: 'debug',
      format: format.combine(
        enumerateErrorFormat(),
        format.json()
      ),
    })
  ]
});

winston.error(new Error('whatever'));

其次,我同意将enumerateErrorFormat添加到json格式; 而且您可能对meta也很正确。

最后,我想指出@DABH给出的代码示例,如果您愿意的话,该示例将导致堆栈不“打印漂亮”。 至少在运行macOS High Sierra的计算机上。 这对我来说是这样的:

{"message":"whatever","stack":"Error: whatever\n    at Object.<anonymous> (/Users/samuelmaddox/Desktop/winston-test/index.js:33:14)\n    at Module._compile (internal/modules/cjs/loader.js:689:30)\n    at Object.Module._extensions..js (internal/modules/cjs/loader.js:700:10)\n    at Module.load (internal/modules/cjs/loader.js:599:32)\n    at tryModuleLoad (internal/modules/cjs/loader.js:538:12)\n    at Function.Module._load (internal/modules/cjs/loader.js:530:3)\n    at Function.Module.runMain (internal/modules/cjs/loader.js:742:12)\n    at startup (internal/bootstrap/node.js:266:19)\n    at bootstrapNodeJSCore (internal/bootstrap/node.js:596:3)","level":"error"}

如您所见,当使用JSON函数输出错误时,换行符\n不会创建实际的新行。 在获取对象并将其转换为JSON时,这是预期的行为,但至少在登录控制台时,这可能不是我们实际上希望从记录器获得的行为。

谢谢您对这个@DABH的更多

仅供参考,这是我玩了一点之后必须去的地方:

import winston from 'winston';
const format = winston.format;

const printNice = format.printf(info => {
    const {level, message} = info;
    return `Logging Level: ${level} - Logging Message: ${message}`;
});

const enumerateErrorFormat = format(info => {
    if (info.message instanceof Error) {
        info.message = Object.assign({
            message: `${info.message.message}\n============\n${info.message.stack}`
        }, info.message);
    }

    if (info instanceof Error) {
        return Object.assign({
            message: `${info.message}\n============\n${info.stack}`
        }, info);
    }

    return info;
});

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

export default logger;

该问题是由以下错误引起的: https :

我们绝对可以在winston2.x中毫无问题地使用此表格。 winston.err('some message', err);winston.error(err)使用上述enumerateErrorFormat修复winston.error(err)但不能修复用err作为第二参数的用例。

@ SamuelMaddox17

logger.log({level:____,message:err});

它有效

好吧,我发现了一些东西。 我在9月3日发表的评论是错误的。 默认记录器不是问题。 这是定义level和/或format@DABH这是您的旧代码:

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

const enumerateErrorFormat = format(info => {
  if (info.message instanceof Error) {
    info.message = Object.assign({
      message: info.message.message,
      stack: info.message.stack
    }, info.message);
  }

  if (info instanceof Error) {
    return Object.assign({
      message: info.message,
      stack: info.stack
    }, info);
  }

  return info;
});

const logger = winston.createLogger({
  level: 'debug',
  format: format.combine(
    enumerateErrorFormat(),
    format.json()
  ),
  transports: [
    new winston.transports.Console(),
  ],
});

logger.error(new Error('whatever'));

如果删除此:

const logger = winston.createLogger({
  level: 'debug',
  format: format.combine(
    enumerateErrorFormat(),
    format.json()
  ),
  transports: [
    new winston.transports.Console(),
  ],
});

并替换为:

const logger = winston.createLogger({
  transports: [
    new winston.transports.Console({
      level: 'debug',
      format: format.combine(
        enumerateErrorFormat(),
        format.json()
      ),
    }),
  ],
});

然后出现info.message === undefined问题。 我认为可以为每种运输指定级别和格式; 我几乎可以肯定Winston 2.0允许这样做。

这是您的代码示例,其中包含我的代码更改,因此您可以轻松地运行和测试:

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

const enumerateErrorFormat = format(info => {
  if (info.message instanceof Error) {
    info.message = Object.assign({
      message: info.message.message,
      stack: info.message.stack
    }, info.message);
  }

  if (info instanceof Error) {
    return Object.assign({
      message: info.message,
      stack: info.stack
    }, info);
  }

  return info;
});

const logger = winston.createLogger({
  transports: [
    new winston.transports.Console({
      level: 'debug',
      format: format.combine(
        enumerateErrorFormat(),
        format.json()
      ),
    }),
  ],
});

logger.error(new Error('whatever'));

希望这有助于找到问题的根源。

我创建了https://github.com/winstonjs/winston/pull/1527

这涵盖了所有选项。 但是,有些测试失败了,所以我现在将其关闭。 修复后,预计会出现故障,但是我不认为我可以打电话要求修改/删除测试。

失败的构建在这里https://travis-ci.org/winstonjs/winston/jobs/453012141 ,显而易见,为什么当您读取测试代码时测试现在失败:
https://github.com/winstonjs/winston/blob/c42ab7fdc51b88db180a7dd90c52ce04ddd4e054/test/logger.test.js#L668

有什么想法吗?

我认为问题出在这条线
const info =(msg && !(msg instanceof Error) && msg.message && msg)|| {
讯息:味精
};
@crowleym指出,添加对instanceof Error的检查似乎可以解决问题

对于仍在处理此问题的任何人,这是我设法提出的解决方法格式化程序(我的logger.js模块的片段):

const { format, transports, createLogger }     = require("winston");
const { combine, timestamp, colorize, printf } = format;

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

    return combine(timestamp(), printf(format));
}

由于某些原因,只有在winston.createLogger globally中全局定义了格式化程序,然后在格式化程序中的info中获得Error对象时, logger.error(new Error("hello"))才起作用。

如果为每个传输定义一个格式化程序,则必须使用logger.log({level: "error", message: new Error("FAILED")})并通过info.message而不是info处理Error对象,以访问Error对象。

我猜在传输选项中设置格式字段时会出现错误?

那只是我的2美分,对我有用。我是Winston的新手,没有JavaScript经验,因此请勿在任何事情上引用我的名字。

我的方法是尝试解决根本原因。 但是它从回购所有者那里获得的吸引力不大...

是的,我明白。 我确实花了很多时间来解决这个问题,因为我是Winston的新手,并且认为这可能是我不正确地使用它,或者还没有足够正确地理解它背后的概念。

但是幸运的是,我偶然发现了一些线程(包括其中的一个),这些线程向我展示了其他情况。 希望他们会解决此问题,所以我不必继续使用替代方法。

这可能是由wintson-transport引起的,有关问题,请参见https://github.com/winstonjs/winston-transport/issues/31https://github.com/winstonjs/winston-transport/pull/ PR为

由于错误对象的不可枚举属性,直接记录错误对象始终是一团糟。 我个人认为这是一个不好的做法,但是社区中足够的人对此表示坚信,这是我们必须支持它的要求。

考虑采用https://github.com/winstonjs/logform/pull/59作为支持此类行为的格式。 从好的方面来说,它封装了所有在将错误视为日志消息时都很常见的边缘情况。 不利的一面是人们需要选择另一种格式(类似于.splat()

@indexzero我倾向于同意,但是如果需要以不同方式显示多个Error类型,并且与自定义日志记录格式/ printf结合使用时,直接错误日志记录可能会很有用,而且我不记得Winston 2.x试图打击这种做法,因为它是开箱即用的。

因此,建议的enumerateErrorFormat解决方案可以使用,但不支持格式logger.error('some message', err) 。 因为info.messageinfo都不是instanceof Error 。 我也想指出这个解决方案的另一个问题。 我当前正在记录从superagent返回的错误

    try {
      const response = await request
        .get('https://some/endpoint')
        .set('Authorization', bearerToken);
      logger.info('successfully received response');
      return response.body;
    } catch (e) {
      logger.error('An error was caught while getting programs');
      logger.error(e); // <<< THE ERROR LOG  
    }

如果我们仅使用Object.assign,则冷却堆栈并设置消息! 但是,作为错误一部分的任何其他信息也将被记录。 如果错误具有敏感数据,例如Authorization Headers (在这种情况下,该错误数据将作为错误对象的一部分包含在内),这可能会非常危险。

但是你可能会说。 这不是Winston的错误,不是Superagent将此数据添加到错误的Winston的错误。 我同意! 但是,由于所有内容都平放在info对象上,因此保留旧信息而不覆盖其他信息变得非常困难。

如果使用logger.error,那将几乎是一件好事。 它假定第二个参数是and错误,并将其作为info.error放在info对象上,然后如果以其他方式记录,则接口将为{ level: "error", error: YOUR ERROR OBJECT}

我真的只是在这里吐痰,但新界面肯定让人有些沮丧(所有有关信息的信息)。

为了阐述我的观点,假设您logger.error( e )其中e是类型错误。

然后在您的代码中将winston配置如下:

winston.configure({
    format: combine(
      timestamp(),
      enumerateErrorFormat(),
      ...
    ),
  });

时间戳被推到错误对象😱上。 这真的有意义吗? 考虑一下..您发送的错误对象正在动态获取新的道具..时间戳。

我认为解决此问题的最佳方法是支持以下语法

logger.error('An error occurred when doing something important', { error: e } );

然后在内部,您可以创建一个错误格式化程序,以查找错误字段!

此人的最新消息:

希望能在接下来的几天内将其缝起来并发货。 这是我们[email protected]追踪器中的倒数第二个问题

大家好–请查看https://github.com/winstonjs/winston/pull/1562。 这是我们3.2.0发布检查清单上的最后一个项目,因此一旦PR被缝制,我们就可以发布。

在发布修补程序之前,我使用以下解决方法:

logger.error = item => {
  logger.log({ level: 'error', message: item instanceof Error ? item.stack : item });
};

我正在使用最新的Winston(3.2.1),并且在将错误传递给logger.error时仍然得到undefined logger.error

@ezze有一个很好的解决方案,但是堆栈跟踪被强制到了新的一行。 这是一个经过稍微修改的版本,将其放在一行中(因此它被日志文件的简单grep捕获)

logger.error = item => {
  const message = item instanceof Error
    ? item.stack.replace('\n', '').replace('    ', ' - trace: ')
    : item;
  logger.log({ level: 'error', message });
};

输出<Error message> - trace: <stack trace>

如果有一种更简便的方法可以使用最新的Winston做到这一点,请告诉我@indexzero。 我是图书馆新手,正在关注文档

我刚刚看到了您发布到PR的链接。 这是否意味着将错误传递给logger.error需要一个消息字符串,那么该错误呢?

try {
  someThing();
} catch(error) {
  logger.error(error); // what I would like to do
  logger.error('special message', error); // what I believe is required?
}

@ the-vampiire该线程最终讨论了2个问题。 第一个是原始海报提出的问题,第二个是我提出的问题,与您的问题相同。 我认为他们只解决了原始海报问题。 我一直想进一步检查以确保确定,然后再打开一个新的问题。 不幸的是,我还没有时间去深入。 同时,如果您使用logger.log({level: 'error', message: err}); ,其中err是一个错误对象,则它将起作用。

仍然有这个问题,浪费了很多时间来解决这个问题,@ the-vampiire的解决方案效果很好。

为什么这张票关闭了?

到目前为止,重写logger.error是最好的解决方案,因为它不会向作为error()的单个参数传递的Error对象添加时间戳属性。 大多数人可能希望Error对象是不可变的。 如果您还没有覆盖logger.info和所有其他与级别相关的方法,那么当事情无法按预期进行时,您会感到惊讶。 同样,除非您希望修改对象,否则无论其类型如何,都不要将其直接发送到Winston记录器方法。

[email protected]开始支持此功能

用法示例:

const winston = require('winston');
const { transports, format } = winston;

const print = format.printf((info) => {
  const log = `${info.level}: ${info.message}`;

  return info.stack
    ? `${log}\n${info.stack}`
    : log;
});

const logger = winston.createLogger({
  level: 'debug',
  format: format.combine(
    format.errors({ stack: true }),
    print,
  ),
  transports: [new transports.Console()],
});

const error = new Error('Ooops');

logger.error(error);
logger.error('An error occurred:', error);

cc @ HRK44 @ the-vampiire

我也仍然遇到这个问题。
当我调用logger.error(error);类的错误时,我只会得到undefined
只有当我像logger.error('Something went wrong', error)这样称呼它时,我才得到完整的错误并可以解析它。

我也仍然遇到这个问题。
当我调用logger.error(error);类的错误时,我只会得到undefined
只有当我像logger.error('Something went wrong', error)这样称呼它时,我才得到完整的错误并可以解析它。
您添加了吗?

format.errors({ stack: true })

是的,还是同样的问题。 我试图重现它。

@ OBrown92我今天也遇到了同样的问题。 我可以确认,如果将format.errors({ stack: true })应用于记录器,而不是进行传输,则可以正常工作。 在这种情况下,可以同时使用logger.error(error);logger.error('Something went wrong', error) 。 但是,当我尝试将format.errors({ stack: true })应用于所选的传输方式时,出现了一个问题。 在这种情况下,我以logger.error(error);获得undefined logger.error(error); ,但是logger.error('Something went wrong', error)可以正常工作。

我不确定这是预期的行为还是错误,但是我花了很多时间来查找原因,因此请修复它或在您的文档中的某处提及。 这将是真正的帮助。

无论如何,我非常感谢您在这个伟大项目中所做的工作。

我遇到了同样的问题,所以我写了这个软件包utils-deep-clone 。 一探究竟。

format.errors is not a function ...好吧,这很令人惊讶。

@holmberd
您是否从winston包中导入了格式?
用法示例:
const { format } = require('winston')
或者
const winston = require('winston'); const { format } = winston;

@aybhalala yepp,尽管没关系,错误堆栈在没有它的情况下传递给printf

更新:因为仍然存在问题,我已经做了一段时间了,并且效果很好

// Grab the default winston logger
const winston = require('winston');

const { format } = winston;
const { combine, timestamp } = format;

// Custom format function that will look for an error object and log out the stack and if 
// its not production, the error itself
const myFormat = format.printf((info) => {
  const { timestamp: tmsmp, level, message, error, ...rest } = info;
  let log = `${tmsmp} - ${level}:\t${message}`;
  // Only if there is an error
  if ( error ) {
    if ( error.stack) log = `${log}\n${error.stack}`;
    if (process.env.NODE_ENV !== 'production') log = `${log}\n${JSON.stringify(error, null, 2)}`;
  }
  // Check if rest is object
  if ( !( Object.keys(rest).length === 0 && rest.constructor === Object ) ) {
    log = `${log}\n${JSON.stringify(rest, null, 2)}`;
  }
  return log;
});

 winston.configure({
    transports: [
      new winston.transports.Console({
        level: 'debug',
        timestamp: true,
        handleExceptions: true
    }),
  ];
    format: combine(
      trace(),
      timestamp(),
      myFormat
    ),
  });


// Finally you log like this
logger.error('An error occurred!!!', { error } );

^^这是预期的用法。 由于凡人永远不会弄清楚这一点,因此应将其记录在案。

由于凡人永远不会弄清楚这一点,因此应记录在案

😂+1到那个伴侣

我将winston与第三方传输( @google-cloud/logging-winston )一起使用,因此对语法的控制较少。 另外,我发现这更加直观:

const error = new Error('something bad happened');
logger.error('was doing this and', error);

登录到控制台时,我将堆栈连接到消息上。 但是结果是这样的:

ERROR was doing this andsomething bad happened Error: something bad happened <rest of the stack.>

由于winston将meta.message连接到原始消息上,因此有奇怪的andsomething和重复消息也被打印在堆栈中。 这在#1660中进行了描述。

似乎#1664正在尝试解决此问题。 同时,我编写了一个格式化程序,“撤消”了该连接: https :

@dandv

logger.error('Caught error:', e);

这是行不通的,因为与console.log() ,winston的logger.<level>(message)只接受一个称为message的参数。 该消息参数可以是对象,也可以是字符串(如果我记错了,请纠正我,但这是我的理解)。

请注意,您还可以使用logger.log({level: <level>, message: <message>}) 。 要了解有关这两个功能的更多信息,建议您阅读文档的这一部分: Log Levels上的Winston Docs 。 请务必通读使用日志记录级别

logger.error(`Caught error: ${e}`);

我无法确切地说出为什么它不输出堆栈,但是我知道这对Winston来说不​​是问题。 如果您尝试console.log(`Caught error: ${e}`)则它也不包括堆栈。 我没有太多使用模板文字,因此模板文字不能很好地与对象一起使用,或者javascript的console.log将该对象识别为错误对象,因此仅输出message属性。 那是我最好的猜测。

logger.error(`Caught error: ${JSON.stringify(e)}`)

这是此错误线程所要解决的问题的核心。 首先,您必须了解有关javascript的一些技术细节。 请注意,如果您尝试console.log(`Caught error: ${JSON.stringify(e)}`) ,也会获得相同的输出Caught error: {} 。 正如@indexzero解释的那样:

messagestack上的属性Error是不可枚举导致JSON.stringify输出的东西,一个不期待。

基本上,由于messagestack属性是不可枚举的,因此JSON.stringify跳过这些属性,这就是您最终得到一个空对象{} 。 为了更好地理解可枚举性,我建议阅读此可枚举性和属性所有权

幸运的是,由于Winston 3.0的设计方式(对Winston团队的支持),我们有了@indexzero所提供的解决方法。 我会帮你解释的。 首先,您创建以下功能:

const enumerateErrorFormat = format(info => {
  if (info.message instanceof Error) {
    info.message = Object.assign({
      message: info.message.message,
      stack: info.message.stack
    }, info.message);
  }

  if (info instanceof Error) {
    return Object.assign({
      message: info.message,
      stack: info.stack
    }, info);
  }

  return info;
});

从docs Streams,objectMode和info对象开始,info对象具有两个属性info.levelinfo.message 。 这info.message属性错误的对象,如果这是你在通过了所有,所以我们创建了一个新的对象,其中message.stackmessage.message (把它看成Error.stackError.message )现在可以枚举,并且我们还包括可能附加到该错误对象的其他所有属性。

接下来,您将创建使用上面的enumerateErrorFormat()函数的记录器:

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

这将采用您传递的message ,并检查它是否是错误对象。 如果是这样,它将解决枚举问题。 然后它将消息传递到format.json ,它将对任何对象(错误与否)进行字符串化。 如果不是对象, format.json字符串,

仍然,如果我们不必创建enumerateErrorFormat会很好,因为通常会记录错误对象。 据我了解,温斯顿团队正在研究将在更高版本中发布的修复程序。

一些最后的笔记。 仅当您使用logger.log({level: <level>, message: <message>})其中message是错误对象)时,此方法才有效。 例子:

try {
  throw(new Error('This should work'));
} catch (err) {
  logger.log({level: 'error', message: err});
}

正如我在上面的另一篇文章中所解释的那样,winston中还有另一个错误,该错误代码无法正常工作:

try {
  throw(new Error('This will not work'));
} catch (err) {
  logger.error(err);
}

由于某些原因,当我们使用logger.error(err)时, info.message属性是不确定的。 希望@indexzero可以解决这一问题。

很好的解释,我只想加上logger.error(捕获的错误:$ {e} );您会丢失堆栈,因为字符串文字在javascript中的工作方式是`${e}`e.toString()完全相同,因此仅打印错误消息是预期的行为。

这还是个问题吗? 我仍然有这个问题:

const { createLogger, format, transports } = require('winston')

const { combine } = format

const errorFormatter = format((info) => {
  console.log(info)
  return info
})


const consoleTransport = new transports.Console({
  format: combine(errorFormatter()),
})

const logger = createLogger({
  transports: [
    consoleTransport,
  ],
})

try {
  throw new Error('Error message')
} catch(err) {
  logger.error(err) // info doesnt have the error object
  logger.error('', err) // info have the error object
}

这还是个问题吗? 我仍然有这个问题:

const { createLogger, format, transports } = require('winston')

const { combine } = format

const errorFormatter = format((info) => {
  console.log(info)
  return info
})


const consoleTransport = new transports.Console({
  format: combine(errorFormatter()),
})

const logger = createLogger({
  transports: [
    consoleTransport,
  ],
})

try {
  throw new Error('Error message')
} catch(err) {
  logger.error(err) // info doesnt have the error object
  logger.error('', err) // info have the error object
}

同样的问题在这里...

看完源代码后,我可以看到它接受了哪些参数:

接口LeveledLogMethod {
(消息:字符串,回调:LogCallback):记录器;
(消息:字符串,元:任何,回调:LogCallback):记录器;
(消息:字符串,...元:任意[]):记录器;
(infoObject:对象):Logger;
}

因此,如果您将错误对象作为第一个参数传递,则它将仅接收错误消息,因为它仅理解字符串,如果在第二个参数中传递错误,则可以访问info.stack中的堆栈跟踪

顺便说一句,我在文档中的任何地方都找不到

我发现了两种解决方案,第一种是使用format.errors ,在记录器的logform中提到,然后使用format.printf创建messageFormatter并有条件地添加从中提取的stack字段infoformat.errors({ stack: true})将添加)。

我更喜欢的另一个解决方案是入侵Winston级记录器:

const addArgs = format((info) => {
  const args: any[] = info[Symbol.for('splat')]
  info.args = args ? [...args] : []
  return info
})

const messageFormatter = format.printf(info => {
  const { timestamp: timeString = '', message, args = [] } = info
  const formattedMsgWithArgs = util.formatWithOptions({ colors: true }, message, ...args)
  const msg = `${timeString} - ${info.level}: ${formattedMsgWithArgs}`
  return msg
})

const logger = createLogger({
  format: format.combine(
    addArgs(),
    format.timestamp({ format: 'HH:mm:ss.SSS' })
  ),

  transports: [
    new transports.Console({
      format: format.combine(format.colorize(), messageFormatter),
    }),
  ],
})

const levels = ['debug', 'info', 'error']
levels.forEach((level) => {
  logger[level] = (msg: any, ...remains: any) => {
    if(typeof msg != "string") {
      return logger.log(level, '', msg, ...remains)
    }

    logger.log(level, msg, ...remains)
  }  
})

看来我可以这样获得类似于console.log错误日志记录

我可以验证@tiagonapoli关于format.errors _

winston.loggers.add("default");
const log = winston.loggers.get("default");
/* get a `transportOptions` object and a `transportType` */
transportOptions.format = logform.format.combine(
  logform.format.errors({ stack: true }),
  logform.format.timestamp(),
  logform.format.printf(myFormatter)
);
log.add(new winston.transports[transportType](transportOptions);

Error对象的处理就像它们是字符串一样。 但是,如果我做这样的事情:

winston.loggers.add("default");
const log = winston.loggers.get("default");
log.format = logform.format.errors({ stack: true });
/* get a `transportOptions` object and a `transportType` */
transportOptions.format = logform.format.combine(
  logform.format.timestamp(),
  logform.format.printf(myFormatter)
);
log.add(new winston.transports[transportType](transportOptions);

Error对象的处理正常。

在我看来,这里的错误是行为上应该没有差异。

因此,在1年后仍不固定吗? 我是否必须破解Winston记录器代码才能使其正常工作?

是的,这让我头疼不已,以至于Winston看上去似乎比我相对简单的用例要麻烦的多。我最终只写了自己的小型logger类,我建议其他人也这样做,除非Winston提供您真正需要的东西。

伙计们,真的吗? 这令人沮丧...

我不是提交者,但我可能会正确地说这不会被“修复”,因为它没有损坏。 温斯顿值得使用。 您只需要配置它-对我来说最好的建议是在上面https://github.com/winstonjs/winston/issues/1338#issuecomment -506354691

还没有 ?

更新:因为仍然存在问题,我已经做了一段时间了,并且效果很好

// Grab the default winston logger
const winston = require('winston');

const { format } = winston;
const { combine, timestamp } = format;

// Custom format function that will look for an error object and log out the stack and if 
// its not production, the error itself
const myFormat = format.printf((info) => {
  const { timestamp: tmsmp, level, message, error, ...rest } = info;
  let log = `${tmsmp} - ${level}:\t${message}`;
  // Only if there is an error
  if ( error ) {
    if ( error.stack) log = `${log}\n${error.stack}`;
    if (process.env.NODE_ENV !== 'production') log = `${log}\n${JSON.stringify(error, null, 2)}`;
  }
  // Check if rest is object
  if ( !( Object.keys(rest).length === 0 && rest.constructor === Object ) ) {
    log = `${log}\n${JSON.stringify(rest, null, 2)}`;
  }
  return log;
});

 winston.configure({
    transports: [
      new winston.transports.Console({
        level: 'debug',
        timestamp: true,
        handleExceptions: true
    }),
  ];
    format: combine(
      trace(),
      timestamp(),
      myFormat
    ),
  });


// Finally you log like this
logger.error('An error occurred!!!', { error } );

痕迹是从哪里来的?

确实不行,这对于日志记录库是不可接受的。
维护人员应该在文档上简单地突出显示一个示例,该示例显示了如何记录错误,甚至定义了自定义printf格式和非json格式,并且可以在其中记录诸如logger.error(“ something”,err)和logger之类的错误。 。错误(错误)
温斯顿似乎很棒,但是这个问题令人难以接受

这是我对如何使用Winston记录错误的看法。 这不是唯一的,上面的许多观点也都基于相同的概念提供了可行的解决方案。

背景
我正在使用@jsdevtools/ono将任意对象类型包装到自定义错误中,但是无论如何,此解决方案对于本机节点错误(例如fs eperm错误)和自定义错误类似乎仍然可以正常工作。

解释
基本上,我依靠format.errors({stack:true})format.metadata() 。 如https://github.com/winstonjs/winston/issues/1338#issuecomment -532327143所述,该地址必须位于格式器中。

元数据有助于将所有错误对象的自定义属性转移到info.metadata

我想打印3种类型的信息:错误消息,stacktrace和错误对象的属性。 错误消息已经是纯文本。 我使用pretty-error模块漂亮地打印了堆栈info.metadata.stack 。 对于错误对象的属性,我不想让stacktrace再次出现,因此我克隆了该对象,并删除了stacktrace属性。 然后,我使用fast-safe-stringify漂亮地打印了错误对象,这是winston依赖的相同string化模块。

    const lodash = require("lodash");
    const path = require("path");
    const winston = require("winston");
    const { default: stringify } = require("fast-safe-stringify");
    const logDir = "D:/temp/logs";

    // pretty formatting
    const PrettyError = require("pretty-error");
    const pe = new PrettyError();
    pe.withoutColors()
        .appendStyle({
            'pretty-error > trace':
            {
                display: 'inline'
            },
            'pretty-error > trace > item':
            {
                marginBottom: 0,
                bullet: '"*"'
            }
        })
        // @ts-ignore
        .alias(/.*[\\\/]CelebrityQuery/i, "<project>")
        .alias(/\[CelebrityQuery\][\\\/]?/i, "")
        .skip(/** <strong i="23">@type</strong> {(_:any) => boolean} */ (traceline => {
            if (traceline && traceline.dir) {
                return traceline.dir.toString().startsWith("internal");
            }
            return false;
        }))
        .skipNodeFiles();

    const consoleFormat = winston.format.combine(
        winston.format.colorize(),
        winston.format.timestamp({
            format: 'DD MMM HH:mm:ss'
        }),
        winston.format.printf(info => {
            if (!lodash.isEmpty(info.metadata) && info.metadata.hasOwnProperty("stack")) {
                let dup = lodash.clone(info.metadata);
                delete dup.stack;
                const errBody = stringify(dup, undefined, 4);
                const stack = pe.render({ stack: info.metadata.stack });
                return `${info.timestamp} ${info.level} ${info.message}${errBody}\n${stack}`;
            } else if (lodash.isString(info.message)) {
                return `${info.timestamp} ${info.level} ${info.message}`;
            } else {
                return `${info.timestamp} ${info.level} ${stringify(info.message, undefined, 4)}`;
            }
        })
    );
    const logFormat = winston.format.combine(winston.format.timestamp(), winston.format.json());
    return winston.createLogger({
        level: 'debug',
        format: winston.format.combine(
            winston.format.errors({ stack: true }),
            winston.format.metadata()
        ),
        transports: [
            new winston.transports.Console({
                format: consoleFormat,
                level: 'info',
            }),
            new winston.transports.File({
                filename: path.join(logDir, "stdout.json"),
                format: logFormat,
                level: 'debug',
                maxsize: 1000000,
                tailable: true
            })
        ]
    });

Log Screenshot

PS:我也发现https://github.com/winstonjs/winston/issues/1338#issuecomment -506354691中提到的解决方案是不错的选择。 即使用logger.warn("Oh no", { error: new Error() }) ,然后在自定义格式化程序中引用info.error

@tiagonapoli,关于在父记录器上使用format.errors的解决方案为我工作:

const logger = createLogger({
  transports: loggerTransports,
});

logger.format = format.errors({ stack: true });

配置此记录器是一件很痛苦的事情……它的表现不只是像console.log那样开箱吗?

@ will093在这里一样。 再次在这个问题上,不明白为什么我的console.log干净整洁,温斯顿格式很烂。

我的2¢

// Enable console logging when not in production
if (process.env.NODE_ENV !== "production") {
    logger.add(new transports.Console({
        format: format.combine(
            format.colorize(),
            format.simple(),
            format.printf(info => {
                const { level, ...rest } = info;
                let rtn = "";
                // rtn += info.timestamp;
                rtn += "[" + info.level + "] ";
                if (rest.stack) {
                    rtn += rest.message.replace(rest.stack.split("\n")[0].substr(7),"");
                    rtn += "\n";
                    rtn += "[" + level + "] ";
                    rtn += rest.stack.replace(/\n/g, `\n[${level}]\t`);
                } else {
                    rtn += rest.message;
                }
                return rtn;
            }),
        ),
    }));
}

logger.error("Error during schema stitching", e);示例

image

使用@tiagonapoli@ will093的解决方案将其添加到父项中似乎是支持直接记录错误并仍然记录消息的最简单方法-这是带有时间戳的最小设置的完整示例:

const createLogger = () => {
  const logFormatter = winston.format.printf(info => {
    let { timestamp, level, code, stack, message } = info;

    // print out http error code w/ a space if we have one
    code = code ? ` ${code}` : '';
    // print the stack if we have it, message otherwise.
    message = stack || message;

    return `${timestamp} ${level}${code}: ${message}`;
  });

  return winston.createLogger({
    level: 'info',
    // put the errors formatter in the parent for some reason, only needed there:
    format: winston.format.errors({ stack: true }),
    transports: new winston.transports.Console({
      format: winston.format.combine(
        winston.format.timestamp(),
        logFormatter
      ),
  });
};

用栈工作时一样的错误叫: logger.error(error) ,与一个叫时喜欢串作品logger.error('a regular message') ,看起来像这样在我的日志:

2020-09-23T20:05:30.30Z info: Feathers application started on http://localhost:3030
2020-09-23T20:05:35.40Z info: job queue - redis ready, registering queues...
2020-09-23T20:05:40.25Z error 401: NotAuthenticated: invalid authorization header
    at new NotAuthenticated (/path/to/server/node_modules/@feathersjs/errors/lib/index.js:94:17)
    at Object.<anonymous> (/path/to/server/src/hooks/authentication.js:123:456)
    at /path/to/server/node_modules/@feathersjs/commons/lib/hooks.js:116:46

并不是试图解决Winston的logger.error('message here', error) -w / console.log兼容问题,而

另外,如果您喜欢json日志,则可以在此处放置logFormatter并在其位置使用winston.format.json() ,该位置仍将包括堆栈-但不是很漂亮。

更新:因为仍然存在问题,我已经做了一段时间了,并且效果很好

// Grab the default winston logger
const winston = require('winston');

const { format } = winston;
const { combine, timestamp } = format;

// Custom format function that will look for an error object and log out the stack and if 
// its not production, the error itself
const myFormat = format.printf((info) => {
  const { timestamp: tmsmp, level, message, error, ...rest } = info;
  let log = `${tmsmp} - ${level}:\t${message}`;
  // Only if there is an error
  if ( error ) {
    if ( error.stack) log = `${log}\n${error.stack}`;
    if (process.env.NODE_ENV !== 'production') log = `${log}\n${JSON.stringify(error, null, 2)}`;
  }
  // Check if rest is object
  if ( !( Object.keys(rest).length === 0 && rest.constructor === Object ) ) {
    log = `${log}\n${JSON.stringify(rest, null, 2)}`;
  }
  return log;
});

 winston.configure({
    transports: [
      new winston.transports.Console({
        level: 'debug',
        timestamp: true,
        handleExceptions: true
    }),
  ];
    format: combine(
      trace(),
      timestamp(),
      myFormat
    ),
  });


// Finally you log like this
logger.error('An error occurred!!!', { error } );

trace()定义在哪里?

更新:因为仍然存在问题,我已经做了一段时间了,并且效果很好

// Grab the default winston logger
const winston = require('winston');

const { format } = winston;
const { combine, timestamp } = format;

// Custom format function that will look for an error object and log out the stack and if 
// its not production, the error itself
const myFormat = format.printf((info) => {
  const { timestamp: tmsmp, level, message, error, ...rest } = info;
  let log = `${tmsmp} - ${level}:\t${message}`;
  // Only if there is an error
  if ( error ) {
    if ( error.stack) log = `${log}\n${error.stack}`;
    if (process.env.NODE_ENV !== 'production') log = `${log}\n${JSON.stringify(error, null, 2)}`;
  }
  // Check if rest is object
  if ( !( Object.keys(rest).length === 0 && rest.constructor === Object ) ) {
    log = `${log}\n${JSON.stringify(rest, null, 2)}`;
  }
  return log;
});

 winston.configure({
    transports: [
      new winston.transports.Console({
        level: 'debug',
        timestamp: true,
        handleExceptions: true
    }),
  ];
    format: combine(
      trace(),
      timestamp(),
      myFormat
    ),
  });


// Finally you log like this
logger.error('An error occurred!!!', { error } );

痕迹是从哪里来的?

有什么答案吗?

此页面是否有帮助?
0 / 5 - 0 等级