55

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

9 Answers 9

44

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
1
  • 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. Commented Oct 7, 2021 at 12:27
28

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,
    }),
  ],
});
5
  • 24
    silent: process.env.NODE_ENV === 'testing' is also an option
    – Palisand
    Commented Nov 15, 2017 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. Commented Nov 17, 2017 at 2:51
  • 2
    Ah, I should have said it would be an option only if you always want to silence winston when running tests.
    – Palisand
    Commented Nov 17, 2017 at 16:43
  • 2
    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
    Commented Dec 8, 2021 at 3:24
  • 'test' is what Jest sets NODE_ENV to so I think it would be silent: process.env.NODE_ENV === 'test'
    – finrod
    Commented Aug 25, 2023 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));
13

we used in the tests the silent property:

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


//...
beforeAll(() => {
  logger.silent = true;
})
afterAll(() => {
  logger.silent = false;
})
0
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)
    
4
  • thanks for the edit @Meyer, I tried to use 4 spaces but not sure my text didn't get formatted as code
    – Quan Vuong
    Commented Feb 26, 2017 at 17:17
  • Do you know how to achieve the same goal with ava?
    – aviggiano
    Commented Jun 12, 2017 at 18:54
  • i am using winston daily transports, how can i skip the test cases to be logged
    – Learner
    Commented Jan 31, 2019 at 6:17
  • This is great!!, Can you pls share any test file too?
    – TechTurtle
    Commented Sep 12, 2019 at 21:44
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;
1
  • silent is the param that I was looking for. Commented May 13, 2020 at 11:05
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.

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.

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

Not the answer you're looking for? Browse other questions tagged or ask your own question.