loading...
Cover image for How to Create a Singleton Logger in Your Server Applications with Webhooks

How to Create a Singleton Logger in Your Server Applications with Webhooks

jrdev_ profile image Jr. Dev πŸ‘¨πŸΎβ€πŸ’» ・5 min read

Contents

  1. Introduction
  2. Setup
  3. Creating a logger factory
  4. Creating a singleton
  5. Using our logger
  6. Configurations
  7. Overview

Introduction

Motivation
Logging is a fundamental part of applications. Logs can inform us of many useful things such as; errors, warnings, stack traces, order of operations, events, and more.

With this information handy, we are able to find, and therefore resolve bugs more quickly, and ensure our applications are running correctly. They also allow us to develop more easily (console.log anyone?).

Logs can be stored to many storage devices, such as slack channels and server logs, making them more accessible to us.

Why a singleton?
A singleton is a creational design pattern whose purpose is to ensure that only one instance of a class exists. We want our logger to be a singleton as we only want one logger instance to be running and logging information at any one time.

What is a transport?
A transport is a location where your logs get stored. For example; they can be stored in a file on your server, sent to a slack or Microsoft teams channel, or just logged to the console, etc.


Setup

We will be using winston as our logging library, which supports multiple transport hooks.

In this article, we will be creating 3 transport hooks:

  1. Console
  2. Daily rotate file
  3. Slack channel

As you could probably guess, the console transport hook will log to the console. The daily rotate file transport hook will log to files on our server which we can configure, for example, we could configure log files to be deleted after a specific number of days. The slack channel transport will send logs to a specified channel.

The console transport is built-in to the winston library. The other 2 transports will need installing in to our project.

Install libraries

npm install --save winston winston-daily-rotate-file winston-slack-webhook-transport

Creating a logger Factory

A factory is another creational design pattern which is responsible for creating objects. Our logger factory will be responsible for creating our logger transports.

// require in our libraries we have just installed
const winston = require('winston');
const WinstonSlackTransport = require('winston-slack-webhook-transport');
const WinstonRotate = require('winston-daily-rotate-file');

function createConsoleTransport(options) {
  return new (winston.transports.Console)(options);
}

function createFileRotateTransport(options) {
  return new (WinstonRotate)(options);
}

function createSlackTransport(options) {
  return new (WinstonSlackTransport)(options);
}

// we pass this function an array of transport objects
// each transport object has 2 properties: type & options
function getLoggerTransports(transports) {
  return transports.map((transport) => {
    const {type, options} = transport;

    switch (type) {
      case 'console':
        return createConsoleTransport(options);
      case 'file-rotate':
        return createFileRotateTransport(options);
      case 'slack':
        return createSlackTransport(options);
    }
  });
}

// our export function which will be invoked by our singleton
module.exports = function create(transports) {
  return winston.createLogger({
    transports: getLoggerTransports(transports)
  });
}

Notice how our create function takes a transports argument. This will be an array of objects, specifying the type of transport we want, along with their configurations.


Creating a singleton

Our singleton will invoke our factory with the logger transport options, and return the one instance we will use throughout our application.

const create = require('../factories/loggerFactory');

// our config which we will populate shortly
const loggerTransports = [];

module.exports = create(loggerTransports);

This is essentially all our singleton needs to do. It invokes the create method from the logger factory, and we export an instance. Now whenever we want to use our logger, we import this file and we will be using the same instance every time.


Using our logger

Now, to use our logger we just need to import it in to the files we want, and use it like so...

const logger = require('./src/utilities/logger');

logger.info('hello world');
logger.warn('warning');
logger.error('arghhh!!');

However, as we have not setup any transports yet, we won't be logging anywhere! An error will present itself in the console to alert you of this.

Alt Text

Let's resolve this next.


Configurations

Create a console configuration
Push the console configuration in to our loggerTransports array in our singleton file.

...

const loggerTransports = [
  {
    type: 'console',
    // specify options here
    options: {}
  }
]

...

When we run our application, you will now notice the application will begin logging errors to the console.

Create a file rotate configuration
Push the file-rotate configuration in to our loggerTransports array in our singleton so it now looks like this...

...

const loggerTransports = [
  {
    type: 'console',
    options: {}
  },
  {
    type: 'file-rotate',
    options: {
      filename: 'filename.log',
      dirname: './logs'
    }
  }
]

...

When we run our application now, you will notice that we have a logs directory with a file in it, where our logs are been written to. Pretty neat, eh?

Checkout more configurations for this transport hook here.

Create a slack configuration
There is a bit more to do with our slack channel. First we need to create a new channel in slack and allow incoming webhooks in to it. You can do this by installing the Incoming webhooks plugin from Slacks app directory and adding the new channel we just made to the configurations. You will receive a webhook url to use in our application, make note of it.

Push the slack configuration in to our loggerTransports array in our singleton so it now looks like this...

...

const loggerTransports = [
  {
    type: 'console',
    options: {}
  },
  {
    type: 'file-rotate',
    options: {
      filename: 'filename.log',
      dirname: './logs'
    }
  },
  {
    type: 'slack',
    options: {
      webhookUrl: 'https://hooks.slack.com/services/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',
      username: 'MY APP'
    }
  }
]

...

Notice the options we specified for our slack transport. We used the webhookUrl property and assigned the url we received from slack. You can optionally specify a username, this will show in the slack channel as the user posting the log messages.

More configurations for this transport can be found here.

Alt Text


Overview

Logging is an essential aspect to creating applications. This article has shown how to make them more accessible and convenient to us, by creating 3 transports to log to.

You can even specify different transports for each environment your application will run in; by placing your logger transport configurations in to your application configurations. Check out this article to understand how to setup different configurations for different environments.


Header photo by Aaron Burden on Unsplash

Posted on by:

jrdev_ profile

Jr. Dev πŸ‘¨πŸΎβ€πŸ’»

@jrdev_

Software developer working in the aviation industry

Discussion

markdown guide