'Winston log level filter

I am trying to use multiple transports. I also want to be able to log only one or more specific level(s) on a transport, where Winston logs from the given level and all which are more sever, so that I cannot by default be as selective as I would like to be.

I currently have 3 transports. One logs everything to console, one logs everything to file, and the third I want to log only levels HTTP_SEND and HTTP_RECV to a different file.

It is the third that I am having problems with. When I breakpoint the function levelFilter, I never see info.level != 'info'.

What am I doing wrongly? NB my code is based heavily on the answer to this question.

const winston = require('winston');

// Custom logging levels. We use all of those from https://datatracker.ietf.org/doc/html/rfc5424, although we change the values.
// We also add some custom levels of our own, which are commented with "".
const logLevels = {
    none: 0,                    // no error logging - don't log with this, set it in process.env.LOG_LEVEL to turn off logging
                                // Could also be achieved by silent=true in winston.createLogger, from process.env, but this is simplest
    emergency : 1,              // system is unusable
    alert: 2,                   // action must be taken immediately
    critical: 3,                // critical conditions
    unhandledException: 4,      // unhandled exception
    error: 5,                   // error conditions
    coding_bug: 6,              // hard bug. E.g switch stemante hits default, etc
    warning: 7,                 // warning conditions
    notice: 8,                  // normal but significant condition
    info: 9,                    // informational messages
    debug: 10,                  // debug-level messages
    HTTP_SEND: 11,              // HTTP request sent
    HTTP_RECV: 12,              // HTTP request sent
    called: 13,                 // function called
    returns: 14,                // function returns
    log_everything: 15,         // always the lowest level, so that we can log everything
  };

  const options = {
    everythingToDailyLogFile: {
      level: process.env.LOG_LEVEL || 'log_everything',
      filename: `./logs/everything.log`,
      handleExceptions: true,
      json: true,
      maxsize: 5242880, // 5MB
      maxFiles: 5,
      colorize: false,
    },
    httpActivityToLogFile: {
      level: process.env.LOG_LEVEL || 'log_everything',
      filename: `./logs/http_activity.log`,
      handleExceptions: true,
      json: true,
      maxsize: 5242880, // 5MB
      maxFiles: 5,
      colorize: false,
    },
    everythingToConsole: {
      level: process.env.LOG_LEVEL || 'log_everything',
      handleExceptions: true,
      json: false,
      colorize: true,
    },
  };  

  const myFormat = winston.format.printf( ({ level, message, timestamp , ...metadata}) => {
    let formattedMessage = `${timestamp} [${level}] : ${message} `  
    if(metadata) {
    formattedMessage += JSON.stringify(metadata)
    }
    return formattedMessage
  });  

  const levelFilter = (levelToFilterOn) =>
  winston.format((info, _options) => {
      // If no info object is returned from the formatter chain, nothing gets logged
      if (toString.call(info.level) === "[object Array]")
      {
        if (info.level.includes(levelToFilterOn)) { return info; }
        return false;
      }
      
      if (info.level != levelToFilterOn) { return false; }
        return info;
})(); 

module.exports = winston.createLogger({
  levels: logLevels,
  transports: [
      new winston.transports.Console({
          format: winston.format.combine(
            levelFilter(process.env.LOG_LEVEL || 'log_everything'),
            winston.format.timestamp({ format: 'YYYY-MM-DD HH:mm:ss.SSS' }), 
            myFormat
          )
      }),
      new winston.transports.File({
        filename: "./logs/bet_squad.log",
        format: winston.format.combine(
          levelFilter(process.env.LOG_LEVEL || 'log_everything'),
          winston.format.timestamp({ format: 'YYYY-MM-DD HH:mm:ss.SSS' }), 
          myFormat
      )
    }),
    new winston.transports.File({
      filename: "./logs/http_activity.log",
      format: winston.format.combine(
        levelFilter(process.env.LOG_LEVEL || ['HTTP_SEND', 'HTTP_RECV']),
        winston.format.timestamp({ format: 'YYYY-MM-DD HH:mm:ss.SSS' }), 
        myFormat
      )
    }),
  ]
});


Solution 1:[1]

I tried your code and managed to make the third transport work. Your question is unclear, what do you mean by:

I never see info.level != 'info'.

In any case, if you want the third transport to work you have to configure a environment variable that matches the info level you define in your code

If you want your information level to be info, you must define an environment variable because of how you defined the parameter you pass to your levelFilter function.
levelFilter(process.env.LOG_LEVEL || ['HTTP_SEND', 'HTTP_RECV']),
In order to use that process.env.LOG_LEVEL, you need to use the dotenv package that allows you to load env variable from a .env file.

Go Ahead and install that package:

npm install dotenv

Create a .env file at the root of your project and fill it with this code:

LOG_LEVEL=info

Add this line after your winston import as such:

const winston = require('winston');
const dotenv= require('dotenv').config();

Thanks to the dotenv package, the env variable you defined in the .env file is populated in the process.env object.
Now if you try logger.log('info', 'test message %s', 'my string'); you should be able to see the log appearing as well as the http_activity.log file populated.

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 Jaro