node.jswinston

Winston logging object


I use Winston for my backend logging I cannot log the object without using JSON.stringify which is annoying

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

Could you show me the way to log object with Winston. I am using version 3.2.1


Solution

  • You are trying to insert a JSON object directly into the string, so it will print [Object Object] without the JSON.stringify.

    This is not fixable by configuring Winston, as this problem happens while the string is generated (before the logger.debug function actually reads it), so a console.log call would print the same thing.

    The first parameter of the logger.* functions is the message (string), then you can pass a metadata object (JSON).

    To use the metadata in your logFormat function, update your Logger instantiation as follow:

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

    Usage:

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

    Console output:

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

    Logfile output (prettified by hand, see comment in the transport file format):

    {
      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'
      }
    }
    

    Hope this solves your issue.

    Important note: as noted by @Xetera in the comments, "you should make sure you're not actually logging people's passwords anywhere"

    Code for this answer