Winston 日志记录对象

Posted

技术标签:

【中文标题】Winston 日志记录对象【英文标题】:Winston logging object 【发布时间】:2019-09-29 03:29:08 【问题描述】:

我使用 Winston 进行后端日志记录我无法在不使用 JSON.stringify 的情况下记录对象,这很烦人

logger.debug(`Register $JSON.stringify(req.body)`)
const logger: Logger = createLogger(
    // change level if in dev environment versus production
    level: env === 'production' ? 'info' : 'debug',
    format: format.combine(
        format.label(label: path.basename(process.mainModule.filename)),
        format.timestamp(format: 'YYYY-MM-DD HH:mm:ss'),
        format.prettyPrint()
    ),
    transports: [
        new transports.Console(
            format: format.combine(format.colorize(), logFormat),
        ),
        new transports.File(
            filename,
            format: format.combine(format.json()),
        ),
    ],
    exitOnError: false,
)

你能告诉我用 Winston 记录对象的方法吗?我使用的是 3.2.1 版

【问题讨论】:

【参考方案1】:

如果您希望将对象记录到控制台和文件中,您可以执行以下操作:

1.初始化2种格式。一个用于文件,另一个用于控制台。注意consoleFormat中使用的JSON.stringify方法

const winston = require("winston");
const  format, transports, createLogger  = winston;
const path = require("path");
const consoleloggerLevel = process.env.WINSTON_LOGGER_LEVEL || "info";

const consoleFormat = format.combine(
  format.colorize(),
  format.timestamp(),
  format.align(),
  format.printf((info) => 
    return `$info.timestamp - $info.level:  [$info.label]: $
      info.message
     $JSON.stringify(info.metadata)`;
  )
);

const fileFormat = format.combine(
  format.timestamp(),
  format.label( label: path.basename(process.mainModule.filename) ),
  format.metadata( fillExcept: ["message", "level", "timestamp", "label"] ),
  format.json()
);

2.现在,创建记录器。

const logger = createLogger(
  level: "info",
  defaultMeta:  service: "some-random-service" ,
  format: fileFormat,
  transports: [
    new transports.File(
      filename: path.join(__dirname, "../logs/error.log"),
      level: "error",
    ),
    new transports.File(
      filename: path.join(__dirname, "../logs/activity.log"),
      maxsize: 5242880, //5MB
      maxFiles: 5 // just in case  
    ),
  ],
);

3.仅在非生产环境中启用控制台日志记录:

if (process.env.NODE_ENV !== "production") 
  logger.add(
    new transports.Console(
      level: consoleloggerLevel,
      format: consoleFormat,
    )
  );

4.将其导出为默认记录器

module.exports = logger;

开启logger.info("Server started listening", port: 9000 );

这将打印出来,

在控制台上:

2021-06-22T07:47:25.988Z - info:  [index.js]:   Server started listening "service":"some-random-service", "port": 9000

在文件中:

"message":"Server started listening","level":"info","timestamp":"2021-06-22T07:47:25.988Z","label":"index.js","metadata":"service":"some-random-service", "port": 9000

【讨论】:

【参考方案2】:

您正在尝试将 JSON 对象直接插入到字符串中,因此它将打印 [Object Object] 而没有 JSON.stringify

这不能通过配置 Winston 来解决,因为这个问题发生在字符串生成时(在logger.debug 函数实际读取它之前),所以console.log 调用会打印相同的内容。

logger.*函数的第一个参数是消息(字符串),然后你可以传递一个元数据对象(JSON)。

要在 logFormat 函数中使用元数据,请按如下方式更新 Logger 实例:

const winston = require('winston')
const  format, transports  = winston
const path = require('path')

const logFormat = format.printf(info => `$info.timestamp $info.level [$info.label]: $info.message`)

const logger = winston.createLogger(
  level: process.env.NODE_ENV === 'production' ? 'info' : 'debug',
  format: format.combine(
    format.label( label: path.basename(process.mainModule.filename) ),
    format.timestamp( format: 'YYYY-MM-DD HH:mm:ss' ),
    // Format the metadata object
    format.metadata( fillExcept: ['message', 'level', 'timestamp', 'label'] )
  ),
  transports: [
    new transports.Console(
      format: format.combine(
        format.colorize(),
        logFormat
      )
    ),
    new transports.File(
      filename: 'logs/combined.log',
      format: format.combine(
        // Render in one line in your log file.
        // If you use prettyPrint() here it will be really
        // difficult to exploit your logs files afterwards.
        format.json()
      )
    )
  ],
  exitOnError: false
)

用法:

const req = 
  body: 
    name: 'Daniel Duuch',
    email: 'daniel.duuch@greatmail.com',
    password: 'myGreatPassword'
  


logger.debug(`Register $req.body.name with email $req.body.email`,  ...req.body, action: 'register' )

控制台输出:

2019-05-11 17:05:45 debug [index.js]: Register Daniel Duuch with email daniel.duuch@greatmail.com

日志文件输出(手工美化,参见传输文件格式的注释):


  message: 'Register Daniel Duuch with email daniel.duuch@greatmail.com',
  level: 'debug',
  timestamp: '2019-05-11 17:05:45',
  label: 'index.js',
  metadata: 
    name: 'Daniel Duuch',
    email: 'daniel.duuch@greatmail.com',
    password: 'myGreatPassword',
    action: 'register'
  

希望这能解决您的问题。

Code for this answer

【讨论】:

我像这样覆盖日志格式 const logFormat = format.printf( info => $info.timestamp $info.level [$info.label]: $info.message, ) 我知道代码只是一个例子,但如果你这样做了,你应该确保你实际上没有在任何地方记录人们的密码。 @SherloxTV 你能告诉我我怎样才能得到“函数名”,就像你得到文件名一样(path.basename(process.mainModule.filename) action: register 数据会发生什么? 完美解决方案!【参考方案3】:

我的解决方案是使用这种格式化程序:

const  format  = winston
const consoleFormat = format.combine(
  format.prettyPrint(),
  format.splat(),
  format.printf((info) => 
    if (typeof info.message === 'object') 
      info.message = JSON.stringify(info.message, null, 3)
    

    return info.message
  )
)

现在所有这些选项都按预期工作:

logger.info('plain text')
logger.info('plain text with object %o',  a:1, b: 2 )
logger.info( a:1, b: 2 )

【讨论】:

【参考方案4】:

或者你只是使用

打印

函数与 JSON.stringify

结合使用
new winston.transports.Console(
  format: winston.format.combine(
    winston.format.colorize(),
    winston.format.simple(),
    winston.format.printf(context => 
      const msgstr = JSON.stringify(context.message, null, '\t')
      return `[$context.level]$msgstr`
    ),
  ),
)

【讨论】:

【参考方案5】:

我不得不结合@SherloxFR 和@Anton 提供的解决方案。

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

const options = 
    file: 
        ....
        format: format.combine(
            format.splat(), 
            format.json()
        ),
        ...
    ,
    console: 
        ...
        format: format.combine(
            format.splat(),
            format.json()
        ),
        ...
    
;

你可以看到我在上面代码的选项配置中添加了format.splat()format.json()

const logger = new Winston.createLogger(
    transports: [
        new Winston.transports.File(options.file),
        new Winston.transports.Console(options.console)
    ],
    exitOnError: false // do not exit on handled exceptions
);

这就是我使用选项配置对象的方式。您实际上可以在传输数组中编写格式代码,但我不喜欢这样。无论如何,这是你的选择。

在这样的配置之后,这就是我在我的代码中使用它的方式

let myObj = 
   name: "***",
;

logger.info('Content: %o', myObj);

如果你愿意,你也可以这样传播

logger.info('Content: %o', ...myObj);

就是这样。 Winston 应该使用此设置记录您的对象。

【讨论】:

【参考方案6】:

您可以在记录器配置中使用format.splat()

const logger = createLogger(
    format: combine(
        ...
        format.splat(), // <--
        ...
    ),
    ...
);

...并使用字符串插值记录对象:

let myObj =  /* ... */ ;
logger.info('This message will include a complete object: %O', myObj);

【讨论】:

谢谢。顺便说一句,您知道如何将文件名记录为标签。由于我使用'../../utils/logger'的导入记录器,我不知道该怎么做***.com/questions/53655740/… 很有趣,因为我使用 typescript 并使用 babel7 来转换 ts 文件。我所有的模块和错误堆栈跟踪都指向 dist/*.js 文件 @anton-pastukhov @coinhndp 很抱歉,这是一种不好的做法。我建议 OP 使用 metadata 参数是有原因的,即如果您需要在监控/分析系统中使用日志,最好直接在日志中使用 JSON 对象,而不是通过搜索数据字符串。如果日志要被人工操作变为红色,那么最好将该对象解析为可读字符串。这就是为什么我的解决方案提供了针对人类操作可读性优化的控制台输出和针对数据处理优化的日志文件输出。请不要鼓励不良做法。 谢谢。明白了你的想法。我只是在调试级别使用它,并将日志记录到我使用 Json 的文件中。同意你的观点,我应该使用元数据。你有 React 经验吗,有很多问题要问@SherloxFR @coinhndp 我知道你明白了我的意思,我只是担心下一个会提出你的问题并且在生产级工作中做错事的人^^是的我'我对 React 很有经验,我看到你加入了 Discord,我会在那里帮助你

以上是关于Winston 日志记录对象的主要内容,如果未能解决你的问题,请参考以下文章

Winston:多进程日志记录

运行单元测试时禁用winston日志记录?

如何像 console.log 一样在 winston 中记录 JavaScript 对象和数组?

如何在 Winston/Node.js 中设置日志级别

如何刷新winston日志?

温斯顿:尝试在没有传输的情况下写入日志 - 使用默认记录器