1

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
      )
    }),
  ]
});
halfer
  • 19,824
  • 17
  • 99
  • 186
Mawg says reinstate Monica
  • 38,334
  • 103
  • 306
  • 551
  • 1
    Mawg - pings to people who have not interacted with a question do not notify them. (Seven copy-pasta notes about Winston flagged as "no longer needed"). – halfer Apr 29 '22 at 21:42
  • 1
    Thank you I was not aware of that. It was slightly naughty of me anyway, but I was desperate and this was my second bounty offer. You can be sure that I will not do so again. – Mawg says reinstate Monica Apr 30 '22 at 08:33
  • 1
    No worries. It it is considered OK to ping one helpful person under, say, one of their answers elsewhere, with a link to a question they would likely be able to help with. However, I tend to advise against overdoing it to one helper, and it is excellent if comments of this kind can be self-deleted when they are no longer needed (it keeps Stack Overflow tidy). – halfer Apr 30 '22 at 11:35

1 Answers1

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.

Jaro
  • 1,587
  • 5
  • 20
  • 39
  • Thanks for that. Actually, the `env` stuff was just copied from an example. I had thought that it might come on useful in future, but will probably just remove it to make life simpler :-) – Mawg says reinstate Monica Apr 26 '22 at 05:45
  • 1
    @MawgsaysreinstateMonica That was the only thing blocking the third transport for me. Could you give a more details snippet of a working example so that I can help you debug? – Jaro Apr 26 '22 at 08:03
  • Sorry, but I have been really busy these last few days. I have awarded you the bounty. I will try w/o the `env` and let you know how it goes. – Mawg says reinstate Monica Apr 28 '22 at 15:10