Using Winston, a versatile logging library for Node.js

Winston

Today, we will explore Winston, a versatile logging library for Node.js.  Winston can be used in a number of contexts including in Node web frameworks such as Express, and Node CLI apps.  We will also dive into features that make Winston a good fit for IoT applications such as logging timestamped entries to files.

Getting started with Winston

Let’s first create a new project folder so we can take Winston for a test drive.  I recommend that you choose a directory name such as winston-test rather than winston to ensure that npm does not yield an error and refuse to install a package as a dependency of itself.

The directory I created was → p:\node\winston-test

Next, create a blank package.json file that automatically accepts all of the defaults without prompting you.  (We are, after all, just creating a quick test project.)

P:\node\winston-test> npm init -y

We are now positioned to install Winston and save it as a dependency in our package.json file:

npm install --save winston

Create a file called index.js and add the following contents:

'use strict';
const winston = require('winston');

winston.level = 'debug';

winston.info('Hello world');
winston.debug('Debugging info');

Winston usage is quite simple in this context.  We use require to load the Winston module and we can then start logging messages to the console.

Next, run the program you just created from the console:

P:\node\winston-test> node index.js

You should see the following output:

info: Hello world
debug: Debugging info

Success – you are logging messages to the console!

Winston logging levels

As described in greater detail in the documentation, Winston provides different logging levels with associated integer values.  In our example above, we utilized the “info” and “debug” logging levels.  By default, Winston uses logging levels utilized by npm:

{ error: 0, warn: 1, info: 2, verbose: 3, debug: 4, silly: 5 }

Logging levels benefit us since we can choose logging level thresholds to determine what logging messages will be displayed.  For example, you might use a different logging level threshold for logging to the console versus logging to a file, or you might choose to temporarily increase the threshold level of logging messages to aid in troubleshooting.

Winston provides other types of logging levels such as syslog levels, and you can even create your own custom levels.  We will use the default npm levels in this tutorial, but, rest assured, other options are available if you need them.

Colorize Winston console log output

Why not colorize our console log output to add an additional dimension of fun to our projects? 🙂  Here’s how it’s done:

'use strict';
const winston = require('winston');

const logger = new (winston.Logger)({
  transports: [
    // colorize the output to the console
    new (winston.transports.Console)({ colorize: true })
  ]
});

logger.level = 'debug';

logger.info('Hello world');
logger.debug('Debugging info');

In this example, we specify a Winston “transport” for the console in order to give us access to an options object that enables us to colorize the output.  Run this example, and you will see colorized output in your console that varies by the logging level of the message.

Add timestamps to the log entries

Adding a timestamp to each log entry will prove to be very useful for IoT applications—or any application for that matter.  Here’s the code needed to bring timestamps to life:

'use strict';
const winston = require('winston');

const tsFormat = () => (new Date()).toLocaleTimeString();

const logger = new (winston.Logger)({
	transports: [
		// colorize the output to the console
		new (winston.transports.Console)({
			timestamp: tsFormat,
			colorize: true,
		})
	]
});

logger.level = 'debug';

logger.info('Hello world');
logger.debug('Debugging info');

In this example, we utilize the Winston timestamp property to ensure a timestamp appears with each log entry. We also take it up a notch by specifying a timestamp format (tsFormat) to gain more control over the appearance of the timestamp.

Log to a file in addition to the console

We now begin to see the power of Winston transports in action as we add a second transport to log to a file in addition to logging to the console:

'use strict';
const winston = require('winston');
const fs = require('fs');
const env = process.env.NODE_ENV || 'development';
const logDir = 'log';

// Create the log directory if it does not exist
if (!fs.existsSync(logDir)) {
  fs.mkdirSync(logDir);
}

const tsFormat = () => (new Date()).toLocaleTimeString();

const logger = new (winston.Logger)({
  transports: [
    // colorize the output to the console
    new (winston.transports.Console)({
      timestamp: tsFormat,
      colorize: true,
      level: 'info'
    }),
    new (winston.transports.File)({
      filename: `${logDir}/results.log`,
      timestamp: tsFormat,
      level: env === 'development' ? 'debug' : 'info'
    })
  ]
});

logger.info('Hello world');
logger.warn('Warning message');
logger.debug('Debugging info');

As shown above, we create a log directory if it does not exist.  We also add the second transport for a file.  Notice also that we can specify different levels (thresholds) for our transports.  In this context, if we are running in a development environment, we use a level of debug and thus send more messages to the log file than we send to the console which is configured with a level of info.

When you run this code, you should see a log file get created before your eyes.  Feel free to experiment with the levels and see how the log output varies between the console and the log file.

Log to a file that rotates daily

As a final example, we will add an npm module to automatically create a new log file every day.  This same functionality can be accomplished other ways including through the use of the logrotate command in the Linux world; however, we will demonstrate a way to make this happen here in the context of Winston.

We’re going to leverage the winston-daily-rotate-file npm module to make this happen which is also available on npm.

Install winston-daily-rotate-file npm module directly from Github

Update: this section is no longer needed, but left here for progeny to provide a tutorial on installing npm modules from GitHub.

At the original time of this writing, there was a useful “prepend” feature that was not available in the version of the module on npm, but was available in the version on GitHub. This provides a good learning experience too!  Let’s do an npm install from GitHub instead of the npm repository.  Here we go!  (The following command should be entered on one line even if it appears as two or more lines in your Web browser.)

npm install --save https://github.com/winstonjs/winston-daily-rotate-file/tarball/master

There are other ways to accomplish the same objective, but many of the other options require that you have git installed on your local machine. This awesome code snippet that someone else created out there on the Internet works great! You can replace master with a different git branch of the code if needed, but master is typically what you would want.

Install winston-daily-rotate-file module from npm

We will first install the winston-daily-rotate-file module from npm using the following command:

npm install --save winston-daily-rotate-file

Implement the code for the daily log file

After the npm install is complete, we are ready to implement the code for the daily log file:

'use strict';
const winston = require('winston');

const fs = require('fs');
const env = process.env.NODE_ENV || 'development';
const logDir = 'log';

// Create the log directory if it does not exist
if (!fs.existsSync(logDir)) {
  fs.mkdirSync(logDir);
}

const tsFormat = () => (new Date()).toLocaleTimeString();

const logger = new (winston.Logger)({
  transports: [
    // colorize the output to the console
    new (winston.transports.Console)({
      timestamp: tsFormat,
      colorize: true,
      level: 'info'
    }),
    new (require('winston-daily-rotate-file'))({
      filename: `${logDir}/-results.log`,
      timestamp: tsFormat,
      datePattern: 'yyyy-MM-dd',
      prepend: true,
      level: env === 'development' ? 'verbose' : 'info'
    })
  ]
});

logger.debug('Debugging info');
logger.verbose('Verbose info');
logger.info('Hello world');
logger.warn('Warning message');
logger.error('Error info');

In this code example, we change our file transport to use the winston-daily-rotate-file transport that we installed above.   We also use the prepend option so the date appears before the file name and datePattern to define how the date appears in our file name. Notice also that we place a “-” before “results.log”.  All of these configuration settings in unison result in a log file for a given day that appears in a format like: 2016-06-09-results.log rather than the default format of results.log.2016-06-09.  The former format, as I’m sure you would agree, is far superior since we are not creating a unique file extension for every future day of mankind. 🙂

You will also notice in this example that I added some additional log messages at various log levels and changed the file transport to use a logging level of verbose if the machine is in a development environment.  You can experiment with these to solidify your understanding of Winston logging levels and observe how the logging messages appear (or don’t appear) on the console and in the log file.

Conclusion

We’ve only scratched the surface of the many features in Winston.  I hope this guide has made you a little smarter and equipped you to use Winston for your Node.js file logging endeavors!

Follow @thisDaveJ on Twitter to stay up to date on the latest tutorials and tech articles.

Additional Articles

Beginner’s Guide to Installing Node.js on a Raspberry Pi
Using Visual Studio Code with a Raspberry Pi (Raspbian)
Visual Studio Code Jumpstart for Node.js Developers
Node.js Learning through Making – Build a CPU Sensor

7 thoughts on “Using Winston, a versatile logging library for Node.js

Leave a Reply

Your email address will not be published. Required fields are marked *