locustjs-logging

2.2.0 • Public • Published

locustjs-logging

This is a logging library that aims to provide logging in a more flexible way.

It uses an abstract class named LoggerBase as the base class for all of its loggers.

This is done with loose coupling in mind, so that applications do not couple or depend on a specific logging implementation.

There are a few number of loggers that the library provides. All of them can be employed in any type of javascript application.

Log Object Structure

Using locustjs-logging, log messages are not limited to only strings. They are instances of a class named Log that has the following structure.

property type description
date date log date.
host string custom string specifies in what host the log was created (explained later).
scope string defines in what scope the log was created (explained later).
data data One or more items that are logged.
exception Exception optional error object that was logged together with data.
batch bool whether there were more than one item when logging (true) or not (false).

Log Types

There are 11 log types or log levels in locustjs-logging:

{
    info: 0,
    log: 1,
    debug: 2,
    trace: 3,
    warn: 4,
    danger: 5,
    success: 6,
    fail: 7,
    abort: 8,
    suggest: 9,
    cancel: 10,
}

There is an enum defined for the above log types named LogType that is also exported by the library.

Abstraction

LoggerBase

This is the base logger class that defines structure of all loggers. It is an abstract class from which no instance should be created (doing so, generates an error). It should be used just as a base class when implementing a logger.

Public Methods

method description
constructor(options: object) options could be an object with the shape { host: string, scope: string } at bare minimum level. child loggers could define other properties in options object based on their business.
getScope():string returns log scope
enterScope(string | function):void pushes current scope to logger's internal scopes stack and sets scope to the given value
exitScope():void pops one scope from logger's internal scopes stack and sets current scope to the poped value
log(...items):void Logs items as a Log instance with LogType.log type
debug(...items):void Logs items as a Log instance with LogType.debug type
warn(...items):void Logs items as a Log instance with LogType.warn type
danger(...items):void Logs items as a Log instance with LogType.danger type
info(...items):void Logs items as a Log instance with LogType.info type
success(...items):void Logs items as a Log instance with LogType.success type
fail(...items):void Logs items as a Log instance with LogType.fail type
abort(...items):void Logs items as a Log instance with LogType.abort type
suggest(...items):void Logs items as a Log instance with LogType.suggest type
trace(...items):void Logs items as a Log instance with LogType.trace type
cancel(...items):void Logs items as a Log instance with LogType.cancel type

Public Properties

method type description
host string host info
scope string scope info
options object logger's options object given to its constructor upon instantiation.

Public Abstract Methods

The following methods should be implemented by a child class derived from LoggerBase. All of these methods by default throw a NotImplementedException error.

method description implementation
_logInternal(log: Log): void this is the method that should perform the actual logging. log argument is an instance of Log class. The method is automatically invoked by LoggerBase when any of public logging methods (log(), debug(), danger(), etc.) are called. mandatory
clear():void this method is expected to clear logs. optional
getAll():Log[] this method is expected to return back all logs that the logger collected. optional

Host & Scope

As logs can play a helpful role in troubleshooting and debugging, two properties are provided for a log object (in Log class) in addition to logged data themselves. This way, a log object reflects better where it is created at. These two properties are host and scope, both of them are of string type.

  • host: host, is expected to reflect name of the environment or platform where log object was created.
  • scope: scope plays a different role than host. It is meant to reflect in what method or function the log is created.

Usually, host is just needed to be set once for a logger class for the whole app life-cycle. This is usually done through the logger options passed to a logger's constructor.

On the other hand, scope plays a more active role and should be set whenever we step in a function or method and should be cleared when we are stepping out. The helper enterScope() and exitScope() methods are provided for the same very job.

Example:

const logger = new ConsoleLogger({ host: 'Win10-x64' });

function foo(...args) {
    logger.enterScope(foo);

    try {
        logger.debug('input args', ...args);

        bar();

        logger.success('done');
    } catch (e) {
        logger.danger(e);
    }

    logger.exitScope();
}

function bar() {
    logger.enterScope(bar);

    // do something

    logger.exitScope();
}

Implementations

locustjs-logging provides a few implementations (sub-classes) for LoggerBase that can be handy in different scenarios. The hierarchical structure is as follows:

  • LoggerBase
    • ChainLogger
      • ArrayLogger
        • StorageLogger
          • LocalStorageLogger
          • SessionStorageLogger
      • ConsoleLogger
      • DOMLogger
    • NullLogger
    • DynamicLogger

The loggers are described in the following table.

Sub-Class Description
ChainLogger This abstract logger makes use of Chain of Responsibility design pattern and enables chaining loggers together. This way, chained loggers can be configured in a way that each one logs a specific types of logs. For example logging danger and fail log types could be assigned to ConsoleLogger and debug and trace log types to DOMLogger. This will be shown a little later.
ArrayLogger This logger is used as a cache and stores logs in an array in memory for later inspection (probably by invoking getAll() method).
StorageLogger This abstract logger, stores logs in a StorageBase store.
LocalStorageLogger This logger is derived from StorageLogger and logs items in localStorage in JSON format
SessionStorageLogger This logger is derived from StorageLogger and logs items in sessionStorage in JSON format
ConsoleLogger This logger logs items in console. It uses a colored logging output so that logs could be more readable. It supports both NodeJs and Web Browsers (its logs can be seen in Developer Tools).
DOMLogger This logger logs item into an HTML table in DOM. It also provides a few options to customize logs.
NullLogger This logger does not log anything and is meant to be used as a safe logger when we do not want our logs be exposed.
DynamicLogger As the name implies, this is a dynamic and flexible logger that provides a type property and could be turned into another logger based on the value specified for type.

Chaining Loggers Together

Based on Chain Of Responsibility design pattern implemented in ChainLogger, we can chain different loggers together and create a more advanced logger based on our need.

Options

A ChainLogger class can receive the following options through its constructor:

{
  next: LoggerBase, // next logger in the chain to which logs should be passed
  filter: array | string,  // list of log types to be logged; default = '*' i.e. all lgo types
  unattended: bool, // whether or not this logger acts in an unattended behavior,
                    // i.e. it actually logs, but pretends it does not, thus, logging is passed 
                    // to the next logger in the chain; default = undefiend = false
}

Example:

const logger = new ArrayLogger({
    unattended: true,
    next: new LocalStorageLogger({
        filter: 'suggest',
        next: new DOMLogger({
            filter: 'debug,danger,fail',
            next: new ConsoleLogger({
                filter: 'info,log',
                next: new NullLogger()
            })
        })
    })
})

The above logger is a chain of loggers that provides the following logging features:

  • An ArrayLogger logs everything sent through the chain (logs everything).
  • suggest logs are logged in localStorage
  • debug, danger and fail logs are logged in DOM (in an HTML table)
  • log and info logs are logged in console.
  • finally a NullLogger logger prevents unhandled errors to leak out of the chain and crash the whole application.

ConsoleLogger

ConsoleLogger receives an options object through its constructor by which we can customize it.

{
    styles: object,  // color customization object
    dateType: 'utc' | 'current' (default)
    env: 'node' | 'web' (default)
}

Color Customization

Web

If our environment is web, we can customize colors with an object as below:

{
    styles: {
        info: {
            label: {},
            scope: {},
            host: {},
            date: {}
        },
        debug: {
            label: {},
            scope: {},
            host: {},
            date: {}
        },
        ...
    }
}

Each key in styles object points to a specific log type. Using a custom object, we can customize each part of a log object, like its date, host, scope. The value provided for each property is an object whose keys are CSS style names.

{
    color: 'red',
    'background-color': 'white',
    'font-weight': 'bold',
    'font-size': '14px'
}

If no value is specified in any part of the styles, the default will be used.

Example: Customizing debug logs label to be displayed with white fore-color and blue back-color in browser web developer tools.

{
    styles: {
        debug: {
            label: {
                color: 'white',
                'background-color': 'blue'
            }
        },
        ...
    }
}

NodeJs

The color customization object in nodejs is similar to web:

{
    styles: {
        info: {
            label: {},
            scope: {},
            host: {},
            date: {}
        },
        debug: {
            label: {},
            scope: {},
            host: {},
            date: {}
        },
        ...
    }
}

The only difference is the color objects specified for each part.

{ fc: 'fore-color', bc: 'back-color' }

Nodejs does not support CSS styles as web developer tools in browsers. Moreover, its color codes conforms to ANSI colors. So, customizing colors in a nodejs environment is different than web. We can use ConsoleColors object exported by locustjs-logging to access a predefined set of console colors.

Example: Customizing debug logs label to be displayed with white fore-color and blue back-color.

import { ConsoleLogger, ConsoleColors } from 'locustjs-logging';

const consoleLogger = new ConsoleLogger({
    styles: {
        debug: {
            label: {
                fc: ConsoleColors.ForeColor.White,
                bc: ConsoleColors.BackColor.Blue,
            }
        },
        ...
    }
});

There is a picture in the DOMLogger section that shows how different generated logs in a Browser Developer Tools look like.

StorageLogger

Storage loggers are best suited in web scenarios when application's URL is changed, user is redirected to another route or page in the application or the page refreshes or posted to another route.

In all of these scenarios, the javascript code of application will be unloaded and loaded again. So, all logs stored in previous loggers described earlier will be lost. StorageLogger comes to rescue in these scenarios.

Since StorageLogger stores the logs in a storage, the logs will be persisted and will not be lost. StorageLogger can receive an options object through its constructor in the following structure:

{
    storeKey: string // storage item key where logs will be stored at. default = 'logs'
    store: 
    bufferSize: number // number of logs to be buffered before flushing into store. default = 5
}

bufferSize enables the logger to buffer logs and then write them into storage. This is to reduce the number of times storage is hit. A zero value will disable buffering.

DOMLogger

DOMLogger receives an options object in the following structure:

{
    target: string (CSS selector) | object,
    className: string,
    onInit: function(source: DOMLogger): string | HtmlTable,
    onNewLog: function(source: DOMLogger, log: Log, count: int): HtmlTableRow,
    format: function(source: DOMLogger, log: Log, type: string): string,
}
Property Type Description
target string | Node a DOM node or CSS selector of a node where logs table should be created in (default = #dom-logger). it could be a simple <div id="dom-logger"></div> element.
onInit function(source: DOMLogger): string | HtmlTable custom html table creator function. It is called once when logger is constructed and also every time clear() method is called.
onNewLog function(source: DOMLogger, log: Log, row: int): HtmlTableRow custom function to create a new table row in logs table. it should return a &<TR> node.
className string CSS className of logs table (default = "dom-logger").
format function(source: DOMLogger, log: Log, type: string): string custom formatter function to format data and exception properties of a log. by default the two proeprties are serialized in JSON and displayed in this format in logs table.

DOMLogger by default creates an HTML table in the target node specified in its options. The table has the following structure:

    <table class="${className}">
        <thead>
            <tr>
            <th>Row</th>
            <th>Type</th>
            <th>Date</th>
            <th>Scope/Host</th>
            <th>Data/Exception</th>
            </tr>
        </thead>
        <tbody>
        </tbody>
    </table>

Also, each log row is created in the following structure:

<tr>
	<td><div>{row}</div></td>
	<td><div>{log.type}</div></td>
	<td><div>{log.date}</div></td>
	<td>
		<span class="scope">{log.scope}</span>
		<span class="host">{log.scope}</span>
	</td>
	<td>
		<div class="data" title="{data}">{data}</div>
		<div class="exception" title="{exception}">{exception}</div>
	</td>
</tr>

data and exception are html-encoded by default (to counter XSS exploit). If a format function is given to DOMLogger using its constructor's options object argument, DOMLogger calls it once to get formatted data and again to get formatted exception. No html-encoding is performed if custom format function is used.

By default, DOMLogger applies no CSS styling to the generated HTML table. This is assigned to the user to apply any visual decoration to the logs table based on his choice.

There is a sample CSS styles file in tests folder in the repository of this library that decorates logs table with a few coloring and borders as shown in the following picture.

DOMLogger & ConsoleLogger coloring example

DynamicLogger

As it was stated earlier, DynamicLogger is a dynamic logger that changes its behavior based on the value specified for its type proeprty. This way, in a Web app for example, we can vastly use logging without fearing that our logs will be leaked out to console, while we also have the choice to switch our logger to a different one anytime we want upon debugging or troubleshooting our app.

Possible values for DynamicLogger.type property are as follows:

value internal logger
null NullLogger
console ConsoleLogger
dom DOMLogger
array ArrayLogger
localstorage LocalStorageLogger
sessionstorage SessionStorageLogger
console ConsoleLogger
other values customized logger by factory option (explained later)

The values are case-insensitive.

By default DynamicLogger acts as NullLogger.

Options

DynamicLogger can receive an options object in the following strcuture through its constructor:

{
    dom: object, // custom options for DOMLogger
    store: object, // custom options for store logger types
    console: object, // custom options for console logger
    array: object, // custom options for array logger
    factory: function(source: DynamicLogger, type: string): LoggerBase // custom factory to create loggers
    next: LoggerBase // default next logger
}

The dom, store, array and console values are used as options argument when instantiating loggers based on the type specified for DynamicLogger upon chaning its new type value.

Custom Logger

Using factory function, it is possible to override or extend the behavior of DynamicLogger. If factory is a function, it is invoked upon chaning DynamicLogger.type property and the new type is passed to it. It is expected for the factory function to return a LoggerBase instance. If it does not return anything (null or undefined), the default loggers will be used. If no logger could be found for the given type value, an InvalidLoggerTypeException error will be raised.

In the following example, we created a DynamicLogger that supports a redux type which logs items in a Redux store.

import { LoggerBase } from "locustjs-logging";
import store, { clearLogs } from './store'; // => redux store

class ReduxLogger extends LoggerBase {
    _logInternal(log) {
        store.dispatch(logAdded(log));
    }
    getAll() {
        return store.getState().logs;
    }
    clear() {
        store.dispatch(clearLogs());
    }
}

const logger = new DynamicLogger({
    factory: (source, type) => {
        if (type == 'redux') {
            return new ReduxLogger();
        }
    }
});

Package Sidebar

Install

npm i locustjs-logging

Weekly Downloads

1

Version

2.2.0

License

MIT

Unpacked Size

415 kB

Total Files

15

Last publish

Collaborators

  • mansoor-omrani