51

Can Winston logging be selectively disabled when executing unit tests of a node module?

Ideally, I'd like to have logging for informational and debug purposes when the application is running, but be suppressed as to not clutter the presentation unit test results when I run my tests.

My use of winston is internal to my module, something like this:

// MyModule.js
var logger = require('winston');
module.exports = function() {
  // does some stuff
  // and logs some stuff like so:
  logger.log('an informational message');
}

// MyModuleTest.js
describe('MyModule', fucntion() {
  it('should do some stuff', function() {
     var myModuleUnderTest = require('MyModule');
     // some tests
  }
}   
Sk606
  • 2,182
  • 1
  • 21
  • 14
  • 1
    http://stackoverflow.com/questions/22709882/how-to-suppress-application-logging-messages-from-a-node-js-application-when-run – Vladimir G. Jul 14 '16 at 05:24

9 Answers9

42

Winston transports have a silent property that you can set, which is probably a little nicer than removing the entire transport.

I add a name to the transports to make is a little easier like this:

var logger = new winston.Logger();

logger.add(winston.transports.Console, {
    name: 'console.info',
    colorize: true,
    showLevel: true,
    formatter: consoleFormatter,
})

Then in the test or set-up I can selectively turn logging on and off with:

logger.transports['console.info'].silent = true  // turns off
logger.transports['console.info'].silent = false // logging back on
Mark
  • 90,562
  • 7
  • 108
  • 148
  • 3
    You can add `silent` to `createLogger` instead of to each transporter: `createLogger({ silent: true, ... })` . It's useful if you want to silent all logs. – Rafael Tavares Oct 07 '21 at 12:27
25

What I do is a bit ugly, but allows me to keep using Jest's --silent option normally. I just set Winston's silent to process.argv.indexOf("--silent") >= 0. For example:

const logger = new winston.Logger({
  …,
  transports: [
    new winston.transports.Console({
      …,
      silent: process.argv.indexOf("--silent") >= 0,
    }),
  ],
});
Rafael Tavares
  • 5,678
  • 4
  • 32
  • 48
  • 23
    `silent: process.env.NODE_ENV === 'testing'` is also an option – Palisand Nov 15 '17 at 21:41
  • True. But then you can't just enable it or disable it using Jest's `--silent` option directly, which I found quite annoying. – Rubén Illodo Brea Nov 17 '17 at 02:51
  • 2
    Ah, I should have said it would be an option only if you **always** want to silence winston when running tests. – Palisand Nov 17 '17 at 16:43
  • 1
    This does not work when you are testing a suite of test files. Looks like it does not make the argv available to check. – Andy N Dec 08 '21 at 03:24
  • `'test'` is what Jest sets NODE_ENV to so I think it would be `silent: process.env.NODE_ENV === 'test'` – finrod Aug 25 '23 at 18:55
19

Create a logger:

const logger = createLogger({
    level: "info",
    format: format.json(),
    transports: []
});

Silence all logging:

logger.transports.forEach((t) => (t.silent = true));
shusson
  • 5,542
  • 34
  • 38
11

If you are using Jest, you can disable it like so:

  1. Set set up files to be run before jest runs the test. In package.json:

    {
        "jest": {
            "setupFiles": ["<rootDir>/jest-set-up/index.js"]
        }
    }
    
  2. In jest-set-up/index.js:

    import winston from 'winston'
    winston.remove(winston.transports.Console)
    
Meyer
  • 1,662
  • 7
  • 21
  • 20
Quan Vuong
  • 1,919
  • 3
  • 14
  • 24
11

we used in the tests the silent property:

import logger from './my-defined-winston-logger'


//...
beforeAll(() => {
  logger.silent = true;
})
afterAll(() => {
  logger.silent = false;
})
ya_dimon
  • 3,483
  • 3
  • 31
  • 42
7

Here's my setup:

const { createLogger, format, transports, config } = require("winston");

let level, silent;
switch (process.env.NODE_ENV) {
  case "production":
    level = "warning";
    silent = false;
    break;
  case "test":
    level = "emerg";
    silent = true;
    break;
  default:
    level = "debug";
    silent = false;
    break;
}

const options = {
  console: {
    level,
    silent,
    handleExceptions: true,
    format: format.combine(
      format.colorize(),
      format.splat(),
      format.printf(
        info => `${new Date().toISOString()} ${info.level}: ${info.message}`,
      ),
    ),
  },
};

const logger = createLogger({
  levels: config.syslog.levels,
  transports: [new transports.Console(options.console)],
  exitOnError: false,
});

module.exports = logger;
Joel
  • 2,285
  • 2
  • 21
  • 22
2

I realise that this is quite late but I just wanted to share my solution with using jest, since I wasn't entirely satisfied with the solutions found here. I can't say my solution is very elegant and may just be hiding some code smell as I'm still learning TDD, but it works.

In my work I often want to log to a file specified via a winston.transports.File(filename: "<filename>") transport. Let's say my log file is info.log

Of course, during testing, I don't want

  1. logs to be written to this info.log
  2. info.log to be created if it doesn't exist.

This is so to avoid side-effects. The answers above along with mocking were enough for avoiding 1. but for some reason did not avoid 2. (explained why below) .

The way I set up my projects is usually as such

   src
    ├── app.js
    ├── services
    │   ├── logging
    │   │   ├── logger.js
    │   │   └── logger_utils.js
    │   ├── foo.js
    │   ├── bar.js
    │   └── etc.js
    ├── tests
    │   ├── foo.test.js
    │   ├── bar.test.js
    │   └── etc.test.js
    └── logs
        └── info.log

Focus mostly on the log-related files. logger.js is where I instantiate and subsequently export the winston Logger object. I then write helper functions in logger_utils.js for modularity and easier testing.

When my issue was appearing, logger.js consisted in

problematic_logger.js
const winston = require("winston");
const path = require("path");
// define the log file directory
const log_dir = path.resolve(__dirname, "./../../logs/info.log");
// create logger
const logger = winston.createLogger({
    transports: [
      new winston.transports.File({
        filename: log_dir
      })
    ]
  });
// export it
module.exports = logger;

I then required it in logger_utils.js which would in turn be required in any other modules scripts. So, in testing (apart from testing logger_utils.js), I only need to mock functions contained in logger_utils.js, with no need to worry about logger.js, since it is only called by logger_utils.js.

Now, I'm not entirely sure about this, but I think 2. defined above still failed despite the mocks and the silencing because winston.createLogger() was still being called, and I believe this will create a file even when a --silent flag is set. I don't know if this is true, but nevertheless the solutions above weren't working.

So, (inspired by this answer) what I decided to do is to simply not create any winston object when testing. I did this by changing my logger.js file to

fixed_logger.js
const winston = require("winston");
const path = require("path");
// define the log file directory
const log_dir = path.resolve(__dirname, "../../logs/info.log");
// if we are testing, don't create any winston object
if (process.env.NODE_ENV === "test") {
  // export
  module.exports = {};
} else {
  // behave normally otherwise
  // create winston logger
  const logger = winston.createLogger({
    transports: [
      new winston.transports.File({
        filename: log_dir
      })
    ]
  });
  // export it
  module.exports = logger;
}

(NODE_ENV is automatically set to "test" when running npm test or npm run test:watch etc.)

We still need to export something for logger_utils.js to not break when testing it, so we export an empty object. This is fine since it will be mocked.

Anyway, that's my first answer on stackoverflow out of the way. I hope it wasn't too disastrous, let me know if anyone wants further details.

thesofakillers
  • 290
  • 3
  • 13
1

The set up stuff did not work for me, I am using winston v3.1.0, there is a new way to create loggers.

From the winston site: https://github.com/winstonjs/winston

The recommended way to use winston is to create your own logger. The simplest way to do this is using winston.createLogger:

const logger = winston.createLogger({
    level: 'info',
    format: winston.format.json(),
    transports: [
      //
      // - Write to all logs with level `info` and below to `combined.log` 
      // - Write all logs error (and below) to `error.log`.
      //
      new winston.transports.File({ filename: 'error.log', level: 'error' }),
      new winston.transports.File({ filename: 'combined.log' })
    ]
  });

  //
  // If we're not in production then log to the `console` with the format:
  // `${info.level}: ${info.message} JSON.stringify({ ...rest }) `
  // 
  if (process.env.NODE_ENV !== 'production') {
    logger.add(new winston.transports.Console({
    format: winston.format.simple()
  }));
}

So I do this in my logger.js

if (process.env.NODE_ENV === 'test') {
    return winston.createLogger({
      transports: [ new winston.transports.Console({ level: 'error'}) ]
    });
}

This stops all log messages unless you have an error, which I would like to see to help with debugging any issues.

Hope this helps.

englishPete
  • 809
  • 10
  • 15
0

If you want to selectively enable or disable logging from the command line, you have to use Jest's --silent option and force Winston to use console.log for logging. Normally you could just run jest --silent and all logging would be suppressed, but that alone doesn't work here. This is because by default Winston writes logs using process.stdout.write instead of console.log, and apparently Jest only silences console.* calls.

A simple fix is to log the message using console.log and then filter it out so Winston doesn't do anything else with it. Here is a custom format that I used:

const MESSAGE = Symbol.for("message");

const redirectLoggingToConsole = format((info, opts) => {
  console.log(info[MESSAGE]);
  return false; // filter out the log message
})

const logger = createLogger({
  level: "debug",
  format: format.combine(
      format.simple(),
      redirectLoggingToConsole() // note: order matters
  ),
  transports: [new transports.Console()]
});

It is important that the custom format goes last so that is prints the formatted message set by "finalizing formats" in Winston. You can also add a check so that this format only redirects logging in unit tests, but in my experience forcing usage of console.log can actually solve a lot of problems with Winston logging in addition to this one.

Now you can silence output from tests on the command line:

jest --silent
Corey
  • 351
  • 3
  • 4