2.0.3 • Published 2 years ago

@intermattory/logging v2.0.3

Weekly downloads
-
License
MIT
Repository
bitbucket
Last release
2 years ago

Intermattory Logging

JavaScript message logging system.

Features

  • Numbering logs
  • Custom log numbers
  • Predefined log levels: DEBUG, INFO, WARN, ERROR, FATAL
  • Custom log levels support
  • Multiple log targets
  • Logging errors
  • Log configuration per target
  • Filtering log entries

To use the logger you need to first set it up. The best way to do that is to create a separate JavaScript module. The simplest logger setup would look like this:

Simplest setup

logging/online-shop-logger.js

import { Log } from '@intermattory/logging';

const log = Log()
log.start()
export const Logger = log.Logger

you can than use it like so:

import { Logger } from 'logging/online-shop-logger.js'

function ShoppingCart(items) {
    const logger = Logger(ShoppingCart)
    logger.info('Initializing shopping cart with items', items)
}

By default, the Logger logs all messages to the console.

Typical setup

Usually you need more than this. Intermattory logging system lets you configure multiple log targets, where you can log messages of different levels, with custom formatting, custom log number pattern, filtered with given filter function.

logging/online-shop-logger.js

import { Log, ArrayTarget, ConsoleTarget } from '@intermattory/logging';
import { Config } from './config.js';
import { LogServiceTarget } from './logging/log-service.js'

const consoleTarget = ConsoleTarget()
const arrayTarget = ArrayTarget()

const configuration = {
  DEV: {
    logTargets: [
      {
          target: consoleTarget,
          level: LogLevel.ALL
      },
      {
          target: arrayTarget,
          level: LogLevel.ALL
      }
    ]},
  STAGE: {
    logTargets: [
      {
          target: consoleTarget,
          level: LogLevel.WARN,
          filter: logEntry => logEntry.module !== 'authorization'
      },
      {
          target: arrayTarget,
          level: LogLevel.ALL
      }
  ]},
  PROD: {
    logTargets: [
      {
          target: consoleTarget,
          level: LogLevel.ERROR,
          filter: logEntry => logEntry.module !== 'authorization'
      },
      {
          target: arrayTarget,
          level: LogLevel.ALL
      },
      {
          target: LogServiceTarget(),
          level: LogLevel.WARN
      }
  ]}
}

const log = Log(configuration[Config.environment])
log.start()

export const Logger = (source, module) => log.Logger(source, { module, application: 'Web application' })
export const getLogs = () => arrayTarget.array

Usage example:

import { Logger } from 'logging/online-shop-logger.js'

function ShoppingCart(items) {
    const logger = Logger(ShoppingCart, 'eCommerce')
    logger.info('Initializing shopping cart with items', items)
    
    const getDiscount = items => service.getDiscount(items)
        .then(discount => {
            logger.debug('Discount:', discount)
            if (discount > 50)
                logger.warn('Discount calculation formula is probably wrong')
        })
        .catch(error => {
            const logEntry = logger.error.withError(error, 'Service failed to calculate the discount')
            showAlert(`Your discount could not be calculated. Please try again later. See log #${logEntry.logNumber} for details`)
        })
    // ...
}

Intermattory logging doesn't provide the ServiceTarget. If you need one, check Creating custom log target.

Configuration

By default, the logger logs all messages to the console

const DEFAULT_LOGGER_CONFIGURATION = {
  logNumberGenerator: DEFAULT_LOG_NUMBER_GENERATOR,
  logTargets: [
    {
      target: ConsoleTarget(),
      level: LogLevel.ALL
    }
  ]
}

to change that, pass your configuration to Log's "constructor":

const consoleTarget = ConsoleTarget()
const arrayTarget = ArrayTarget()
const configuration = {
  logTargets: [
    {
        target: consoleTarget,
        level: LogLevel.ALL,
        filter: logEntry => logEntry.source !== 'IgnoredSource'
    },
    {
        target: arrayTarget,
        level: LogLevel.INFO
    }
]}
const log = Log(configuration)
log.start()

Log targets

ArrayTarget is useful to list the logs in GUI. Check out the demo to see it in action. Get the array from arrayTarget.array.

ConsoleTarget can be initialized with an optional messageFormatter argument. It lets you set the template of a message and customize information it contains. ConsoleTarget uses TemplateMessageFormatter as default. You can also create your own formatter - check out Creating custom message formatter.

You can create your own log targets. Check out Creating custom log target to find out how.

Use filter field to exclude the messages you don't want to log. It expects a function which returns true or false. Filtering doesn't affect the log number incrementation.

Log number

By default, the log number is an integer incremented by 1 with every new log entry. Counting starts at the moment you call log.start().

You can customize it by setting the logNumberGenerator field to a function which generates unique IDs. The function takes the default integer log number and a date as arguments. It can return a number or string. Logger does not validate the result of the function, so make sure you return unique values.

All log targets use the same log number generator.

Logger arguments

Logger function takes 2 arguments: source and optional parameters.

The parameters are sort of tags which can help you filter the log entries. In different projects or even modules of the same project, you may need to log different information. Sometimes you may want to log only the source of the message, sometimes the module name or message category. You can define your Logger arguments by wrapping the original log.Logger in a Higher Order Function. You can also create multiple loggers with different signature:

logging/online-shop-logger.js

const log = Log()
log.start()

export const Logger = (source) => log.Logger(source, { application: 'Web application', workstationId: getWorkstaionId() })
export const ModuleLogger = (source, module) => log.Logger(source, { module, application: 'Web application' })
export const CategoryLogger = (source, category) => log.Logger(source, { category })

LogLevel

Log level is a number.

LogLevel object provides predefined log levels:

  • LogLevel.ALL = 0
  • LogLevel.DEBUG = 100
  • LogLevel.INFO = 200
  • LogLevel.WARN = 300
  • LogLevel.ERROR = 400
  • LogLevel.FATAL = 1000

Logger

Once you instantiated the Log you can use the Logger:

const log = Log()
const logger = log.Logger('LogSource')

Logger function takes 2 arguments:

  • mandatory source (function or string)
  • optional parameters (object)

source

If you pass a function as source, the logger will log its name. Be aware that if you minify your code, function names may be minified too and a string may be more reliable.

parameters

This can be anything. If you would like to log more information and don't want to pass them every time you log a message, you can pass them as parameters to the Logger:

const logger = Logger(ShoppingCart, { module: 'eCommerce'})

NOTE OK, it can't be anything... You can give your parameters any valid JavaScript name except date, logNumber, level, error, message, source. These are standard fields which describe every log entry.


Logger methods

Here are all available methods:

const logEntry = logger.log(customLevel, 'Message')
const logEntry = logger.fatal('Message')
const logEntry = logger.error('Message')
const logEntry = logger.warn('Message')
const logEntry = logger.info('Message')
const logEntry = logger.debug('Message')

With error:

const logEntry = logger.log.withError(error, LogLevel.ERROR, 'Message')
const logEntry = logger.fatal.withError(error, 'Message')
const logEntry = logger.error.withError(error, 'Message')
const logEntry = logger.warn.withError(error, 'Message')
const logEntry = logger.info.withError(error, 'Message')
const logEntry = logger.debug.withError(error, 'Message')

LogEntry

Every method returns a log entry, which of the following structure:

{
    logNumber: number | string,
    level: string,
    message: [],
    error?: {*},
    date: Date,
    source: function | string
}

This is very useful, if after an action, you need to give the user a feedback with some technical details, like the log number or error:

const logEntry = logger.error.withError(error, 'Parsing error')
showAlert(`An error occurred while opening the document (${logEntry.errorMessage}). For technical details go to the log No.${logEntry.logNumber}`)

TemplateMessageFormatter

import { TemplateMessageFormatter, ConsoleTarget } from '@inttermattory/logging'

const consoleMessageFormatFunction = (entry, formattedEntry) => `#${formattedLogEntry.logNumber} [${formattedLogEntry.level}][${formattedLogEntry.time}]${formattedLogEntry.parameters ? `[${formattedLogEntry.parameters}]` : ''} ${formattedLogEntry.source}: ${formattedLogEntry.message}${logEntry.error ? ` ${formattedLogEntry.errorName}
    stack trace:
    ${formattedEntry.stackTrace}
` : ''}`
const consoleMessageTemplate = TemplateMessageFormatter(consoleMessageFormatFunction)
const consoleTarget = ConsoleTarget(consoleMessageTemplate)

The default formatter is stored in TemplateMessageFormatter.DEFAULT_MESSAGE_TEMPLATE and here is how it looks like:

(logEntry, formattedLogEntry) => `#${formattedLogEntry.logNumber} [${formattedLogEntry.level}][${formattedLogEntry.time}]${formattedLogEntry.parameters ? `[${formattedLogEntry.parameters}]` : ''} ${formattedLogEntry.source}: ${formattedLogEntry.message}${logEntry.error ? ' ' + formattedLogEntry.error: ''}`

Creating custom message formatter

A message formatter has a very simple interface: It must implement a format method and return an array. format takes one argument: a log entry Example log entry:

{
    date: [object Date],
    logNumber: 11,
    error: [object Error],
    level: 300,
    message: [object Array],
    source: 'Demo',
    ...parameters
}

Your formatter may look like this:

const MyMessageFormatter = {
    format(entry) {
        let message = [];

        // your implementation

        return message;
    }
}

It is important that the format method returns an array, even if the formatted message is a string.

Creating custom log target

Every log target must have log method, which takes 1 argument: log entry object. Example log entry:

{
    date: [object Date],
    logNumber: 11,
    error: [object Error],
    level: 300,
    message: [object Array],
    source: 'Demo',
    ...parameters
}

Your log target may have the following shape:

const MyLogTarget = {
    log(entry) {
        // your implementation
    }
}

You may want to use a message formatter. If so, please refer to ConsoleTarget

Demo

https://intermattory.bitbucket.io/logging/

What's included?

What's not included (yet)?

  • Colors
  • FileTarget

Extending

You can easily extend package features:

Tests

Run tests with npm test

License

MIT

2.0.3

2 years ago

2.0.2

2 years ago

2.0.1

2 years ago

2.0.0

2 years ago

1.0.9

6 years ago

1.0.8

6 years ago

1.0.7

6 years ago

1.0.6

6 years ago

1.0.5

6 years ago

1.0.4

6 years ago