Logging System in Node.js Apps

logging with Winston and Morgan.

ยท

4 min read

Logging System in Node.js Apps

Today will talk about logging systems and the benefits of having a logging system in NodeJs applications

I was digging into some techniques to make my app more performant and secure when I was trying to get better at NodeJs + ExpressJS. Structured Logging is one of these practices that is highly recommended.

And this is one of the few topics that I recently presented to our tech team at Growth School.

So without any further due let's get started!


Why Setup a Logging Library

Logging Library helps achieve structured logging that makes monitoring easier.
It is one of the best things a developer can do, as it allows us to know the latest state of the app and can save hours of debugging.
For example, if there's some bug in your user route, a log will point that out the area of failure, allowing you to identify the cause of the bug.

In this blog, we'll talk about the 2 most popular logging libraries, Winston and Morgan. Both serve a different purpose.


1. Winston:

Winston is an extremely versatile logging library that allows us to log to console and a file as well.
We can configure Winston to log levels, timestamps, messages and more!

Let's take a look at the code:
This is the basic boilerplate code with all the default settings.

const logger = winston.createLogger({
  transports: [
    new winston.transports.Console(),
    new winston.transports.File({ filename: 'app.log' })
  ]
});

Transport is a term that refers to the storage/output mechanisms used for the logs.

  • winston.transports.Console will log information to the console
  • winston.transports.File will log information to a file. (For instance, all the logs will be stored in a file named app.log)

Each transport definition can contain its own configuration settings such as file size, log levels, and log format.
You can customize the logs to a great extent.

The code below demonstrates it:
I have created a file named logger.js with this code.

const winston = require('winston')

const options = {
  file: {
    level: "info",
    filename: "./logs/app.log",
    handleExceptions: true,
    json: true,
    maxsize: 5242880, // 5MB
    maxFiles: 5,
  },
  console: {
    level: "silly",
    format: winston.format.combine(
      winston.format.colorize({ all: true })
      winston.format.timestamp(),
      winston.format.simple(),
      winston.format.printf(
        (info) => `${info.timestamp} : ${info.level}: ${info.message} `
      )
    ),
    json: true,
  },
};

const logger = winston.createLogger({
  transports: [
    new winston.transports.File(options.file),
    new winston.transports.Console(options.console)
  ],
  exitOnError: false
})

module.exports = logger

Now, we have custom configurations such as log level, file size, max files, format, etc
For instance, we have configured our logs to be colourized and to have timestamps, level and the message we pass.

Most of the code above is pretty self-explanatory but let's learn more about levels.
Logging Levels are used to identify the severity of a log.
Here are npm logging levels, prioritized from 0 to 6 (highest to lowest):

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

error has the highest priority and silly has the least.

You can create your own logging levels too! By default, the above npm logging levels will be used.

Only the logs that are less than or equal to the specified level will be logged. For instance, if we have specified level: "info", then only error warn and info level logs will be logged.

Log levels are specified when calling the logger, meaning we can import the logger and use it as follows:

const log = require("./logger");

app.get("/", (req, res) => {
  try {
     log.error("Error Log");
     log.warn("WARN Log");
     log.info("INFO Log");
     log.http("HTTP Log");
     log.verbose("VERBOSE Log");
     log.debug("DEBUG Log");
     log.silly("SILLY Log");
  } catch (err) {
     log.error(err);
  }
});

Here's what the logs look like in the console.

image.png As you can see, we are logging the timestamps, levels and the message.

Yes, that's pretty much about Winston. You can create multiple transport and customize it the way you want!


2. Morgan.

Morgan is an HTTP logger middleware for Express.js. Itโ€™s built to output HTTP logs to the console.
Morgan is quite straight-forward and easy to set up.

const express = require('express');
const morgan = require('morgan');

 app.use(morgan('tiny'));

const app = express();

    app.listen(3000, () => {
        console.debug('App listening on :3000');
    });

tiny is Predefined log formats, that represents: ':method :url :status :res[content-length] - :response-time ms'

Morgan has 5 predefined log formats:

  1. combined
  2. common
  3. dev
  4. short
  5. tiny

We can also define our own custom log format. Learn more here.

That's all for Morgan. It become very handy to monitor incoming requests with Morgan.


Those were the 2 most popular logging libraries that can help us achieve structured logging. Morgan is quite lightweight, with a gzipped size of 4.8kb. Winston is a bit more heavier comparatively with a gzipped sized of 36.5kb.


That's all for today. Let me know your thoughts in the comment section.
If you liked the article give it a thumbs up.

Hope you enjoyed it, and if you did, consider supporting me ๐Ÿ‘‡. It will make my day :)

Also, comment below if you would like to know more about the other topics I presented to our tech team.