【问题标题】:Winston logging objectWinston 日志记录对象
【发布时间】: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 版

【问题讨论】:

    标签: node.js winston


    【解决方案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: "StackOverflow",
          };
          
          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'的导入记录器,我不知道该怎么做stackoverflow.com/questions/53655740/…
            • 很有趣,因为我使用 typescript 并使用 babel7 来转换 ts 文件。我所有的模块和错误堆栈跟踪都指向 dist/*.js 文件
            • @anton-pastukhov @coinhndp 很抱歉,这是一种不好的做法。我建议 OP 使用 metadata 参数是有原因的,即如果您需要在监控/分析系统中使用日志,最好直接在日志中使用 JSON 对象,而不是通过搜索数据字符串。如果日志要被人工操作变为红色,那么最好将该对象解析为可读字符串。这就是为什么我的解决方案提供了针对人类操作可读性优化的控制台输出和针对数据处理优化的日志文件输出。请不要鼓励不良做法。
            • 谢谢。明白了你的想法。我只是在调试级别使用它,并将日志记录到我使用 Json 的文件中。同意你的观点,我应该使用元数据。你有 React 经验吗,有很多问题要问@SherloxFR
            • @coinhndp 我知道你明白了我的意思,我只是担心下一个会提出你的问题并且在生产级工作中做错事的人^^是的我'我对 React 很有经验,我看到你加入了 Discord,我会在那里帮助你
            猜你喜欢
            • 2015-04-15
            • 2018-06-21
            • 1970-01-01
            • 2014-01-22
            • 1970-01-01
            • 1970-01-01
            • 2018-06-20
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多