Logging with Winston in your Node.js application

Logging is an essential part of application.  According to Twelve-Factor App methodology it’s the XI factor of application development. A proper logging gives you a valuable insite of your application. In Node.js application, it is always suggested to use logging libraryrather than just using console.log  in productione environment. Log4js, Bunyan , and Winston are the best logging libraries among other libraries. I am not going to discuss here which is the best logging library among them.

In this tutorials I will cover the Winston logging library with Morgan logger middleware. Winston is most popular, simple and versatile logging library for NodeJs application. You can easily customize it, format the log messages and send them any internal or cloud log management tools.

Logging with Winston

Let’s create a custom nodejs app with Winston logging library. Create a blank package.json file that automatically accepts all the defaults without prompting.

mkdir ~/myapp 
cd ~/myapp
npm init -y

Install Winston

npm install  winston --save

Now, create a config folder file with logger.js file which contains the winston configuraiton.

mkdir ~/myapp/config
touch  ~/myapp/config/logger.js

Let’s talk about a little bit about Winston logging library. It supports

  • Pre-defined levels as well as custom error levels
  • Multiple transports as well as custom transport
  • Handle exceptions
  • Different log formats as well as custom formats

Transport

Transport is a concept that stores or output the logs into file, console, stream or cloud platform over http. It’s loaded as module while creating an instance of a winston logger. You can use multiple transports at different log level at the same time. What it means actually you can output to console and send to http at the same time. Built-in available transports are Console, File, Http and Stream. There are also some aditional community transports available.

Logging level

Logging level is part of transport which indicate the minimum logging priority that transport will listen out for.  Logong level is denoted the by integer number and it follows npm logging levels.

  • 0: error
  • 1: warn
  • 2: info
  • 3: verbose
  • 4: debug
  • 5: silly

For example, you configure transport with setting logging level to debug: 4.This means, it will log levels

  • 4 (debug)
  • 3 (verbose)
  • 2 (info)
  • 1 (warn)
  • 0 (error)

If you set the logging level to info then the logs will show only info messages  as well as less than or equal to this level, You wont see the debug or verbose messages. This helps to avoid clutter in the logs and prevents too much info being shown in the logs in a production environment.

Next, define a logger with a very basic winston configurations.

//~/myapp/config/logging.js
// Import modules
const winston = require('winston');
 
// Logger configuration
const logConfiguration = {
    exitOnError: false,
    transports: [
        new winston.transports.Console({
            level: 'info'
        }),
        new winston.transports.File({
            level: 'error',
            filename: `./logs/app.log`
        })
    ]
};
 
// Create the logger
const logger = winston.createLogger(logConfiguration);
 
// Export the logger
module.exports = logger;

Let me explain what properties I configured here. In logConfiguration object, I initiated two transports: console with level: info  and file with level: error. File tranporter will log only errors into log/app.log file at application root. Console transporter will log upto info or any logs less than info level. I also set exitOnError to false. Means it will not exit after logging an uncaughtException.

Now, I am going to use the logger in my app.js.
//~/myapp/app.js
const logger = require('./config/logger');
logger.info("hello world!");

It print hello world! in console as below. It doesn’t add any log into app.log file because of log level.

{"message":"hello world!","level":"info"}

Now, we print another log with error level.

logger.error("error occured");

This time, you see following message in console as well into app.log file.

{"message":"hello world!","level":"error"}

Custom log formats

Another important advantage of Winston is customizing log message format as well as creating your own custom format. By default it supports JSON formatting, coloring log output, and the ability to change with formats. You can add different message formats to diferent transports.  Let’s start with an example that adds a timestamp, label, colorizes the level and message, lowercase message content and prints using the template [level] [timestamp-label]: message.

// Import modules
const { createLogger, format, transports } = require('winston');
const { combine, timestamp, label, colorize, printf } = format;
 
const customFormat = printf(({ level, message, label, timestamp }) => {
  return `$[${level}] [${timestamp}-${label}] ${message.toLowerCase()}`;
});
 
// Logger configuration
const logConfiguration = {
  format: combine(
    label({ label: 'Custom APP log!' }),
    timestamp(),
    colorize({ all: true }),  
    customFormat
  ),
  transports: [
    new transports.Console({ level: 'info' })
  ],
  exitOnError: false,
};
 
// Create the logger
const logger = createLogger(logConfiguration);

Now if you try again logger.info("HELLO WORLD"), you will see following message into console.

 

More details on format: See Winston Format

Send log message to Cloud platform

There are multiple cloud platforms with paid and limited free subscribtions where you can send log messages. I used couple of them, but for this tutorials I chose Papertrail.

// import modules
const winston = require('winston');
 
// Install papertail: npm install winston-papertrail --save
// Requiring `winston-papertrail` will expose  
require('winston-papertrail').Papertrail;
 
const winstonPapertrail = new winston.transports.Papertrail({
        level: 'info',
        colorize: true, 
        host: 'logs.papertrailapp.com', 
        port: 12345 });
 
// Logger configuration 
const logConfiguration = { 
   exitOnError: false, 
   transports: [winstonPapertrail] 
};
 
 
// Create the logger 
const logger = winston.createLogger(logConfiguration); 
 
// Export the logger 
module.exports = logger;

 

I almost covered everything about Winston as a proper logger. One more thing I want to add using winston with morgan.

 

Why Morgan?

Morgan is HTTP request middleware logger for Node.js. It basically simplifies the process of request logging. Most of the time it’s used with Winston to consolidate HTTP request data logs.  Morgan supports a handful of pre-defined logged formats with well-know names/structures: combined, common, dev, short, tiny as well as custom format. Beside the format, it also accepts following properties as into its optiont object.

  • immediate: set it to true in order to log request even server crashes.
  • skip: skip log outputs based on some custom logic
  • stream: Output stream for writing log lines. Defaults to process.stdout

Let’s look at an example of Morgan with Winston logger into an express based Node.js application. By default,  it outputs to the console, so let’s define a stream function into Winston so that we can transfer morgan’s output into the Winston log files.

//~/myapp/config/logging.js
...
 
logger.stream = {
  write: function(message, encoding) {
    logger.info(message);
  },
};
 
 
// Export the logger 
module.exports = logger;

In app.js (Example based on express framework)

const express = require('express');
const bodyParser = require('body-parser');
const app = express();
 
 
const morgan = require('morgan');
const logger = require('./config/logger');
 
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({
    extended: true
}));
 
app.use(morgan('combined', {
    immediate: true,
    stream: logger.stream
}));
 
...

Morgan supports token. To know more details token and logged format:  Morgan NPM Package documentation

In this tutotial, I mostly focused on Winston logger with Morgan HTTP middleware for simplicity. I mentioned before that there are more better logger libraries availabe based on different features. Choice one that best fits into your application. Happy Hacking.

 

Eftakhairul Islam

Hi, I'm Eftakhairul Islam, a passionate Software Engineer, Hacker and Open Source Enthusiast. I enjoy writing about technical things, work in a couple of startup as a technical advisor and in my spare time, I contribute a lot of open source projects.

 

Leave a Reply

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

 

This site uses Akismet to reduce spam. Learn how your comment data is processed.

Read previous post:
SSH to AWS EC2 instance without key pairs

This is a common issue for the developers to access ec2 instances by username and password authentication.  It requires a...

Close