Logging

BackendTooling

Lightweight level based logging system.

Installation#

yarn add @boost/log

Environment variables#

  • BOOSTJS_LOG_DEFAULT_LEVEL (LogLevel) - The default log level to use when calling the logger function stand alone (the usage examples below). Defaults to the lowest level, log.
  • BOOSTJS_LOG_MAX_LEVEL (LogLevel) - The maximum level, based on priority, to write to a stream. All levels higher than the maximum will be ignored. Defaults to allowing all levels.

Logging#

Logging is based around the concept of a "logger", which provides a set of functions of severity levels to log with. Logs are written to one or many provided transports -- or console if not defined. To begin, instantiate a logger with createLogger, which returns a function that can be used for standard level logging.

import { createLogger } from '@boost/log';
const log = createLogger({ name: 'boost' });
log('Something has happened…');

Each logging function requires a message string as the 1st argument, and an optional rest of arguments to interpolate into the message using util.format().

log('Name: %s %s', user.first_name, user.last_name);
log('Object: %O', data);

If you would prefer to interact with a class instance, you may use the Logger class. The major difference between the class and the function, is that the class only has 1 logging method, log().

import { Logger } from '@boost/log';
const logger = new Logger({ name: 'boost' });
logger.log({
level: 'info',
message: 'Something else has happened…',
});

Options#

The following options can be defined when creating a logger. They cannot be customized after the fact.

  • labels (object) - A mapping of localized log level names. Can be used with chalk.
  • name (string) - Unique name of this logger instance, for debugging purposes. (Required)
  • transports (Transportable[]) - List of transports in which to write formatted log messages to.
import chalk from 'chalk';
import { createLogger, StreamTransport } from '@boost/log';
const log = createLogger({
name: 'boost',
labels: {
error: chalk.bgRed.black.bold(' FAIL '),
},
transports: [new StreamTransport({ levels: ['error'], stream: process.stderr })],
});

Log levels#

There are 5 distinct logging levels outside the standard level, each represented as a unique function on the logger instance. The levels in order of priority are trace, debug, info, warn, and error. Each function requires a message as the 1st argument, and an optional rest of arguments to interpolate into the message.

log.trace('Code path hit?');
log.debug('What is going on here?');
log.info('Systems are stable');
log.warn('Something is definitely going on…');
log.error('Systems are down! %s', error.message);

Silencing output#

By default, all logged messages are immediately written to the configured transports. To silence output and disable writes, call the logger.disable() function, and to re-enable, call logger.enable().

log.disable();
// Will not write!
log.debug('Something is broken!');

Messages that are logged while silenced are lost and are not buffered.

Formats#

All logs are represented as an object, known as a log item. These items contain the following metadata about the environment, the logger, and the current log message.

  • host (string) - The host name of the machine.
  • label (string) - The log level as a localized or customized label.
  • level (LogLevel) - The log level as a string.
  • message (string) - The log message with interpolated arguments applied.
  • metadata (object) - Additional data to include with a log item.
  • name (string) - Name of the logger.
  • pid (number) - Current process ID.
  • time (string) - Timestamp of the log, native to the host machine.

Before an item is written to a transport, it must be formatted from an object into a string. This can be done on a per transport basis using the format option, like so.

import { ConsoleTransport, formats } from '@boost/log';
const transport = new ConsoleTransport({
format: (item) => `${item.level} ${item.message}`,
// Or a pre-built format
format: formats.json,
});

Boost provides formats by default, all of which are pre-configured into each built-in transport. Feel free to use the following built-in formats, or customize your own!

  • formats.console - Formats the item as if it's being logged to console. Only inclues the label and message.
  • formats.debug - Formats the item into a human-readable message with all item fields included. This is the default format for most transports.
  • formats.json - Formats the entire item into JSON.
  • formats.message - Formats the item using only the message.

Metadata#

Sometimes additional metadata may be required that is not found within the pre-defined log item fields. Metadata can be defined on the logger using an object, which is then passed to all log items.

const log = createLogger({
name: 'boost',
metadata: {
locale: 'en',
region: 'eu',
},
});

It can also be defined per log by passing an object as the 1st argument. Metadata defined at this level will override the logger metadata.

log({ locale: 'de' }, "What's going on?");

Fields name, host, and pid are reserved names and cannot be used.

Transport types#

There are multiple types of transports that can be used within a logger, all of which support the following shared options.

  • eol (string) - End of line character to append to a message. Defaults to os.EOL.
  • format (Formatter) - Function to format a log item into a message string. Default is transport dependent.
  • levels (LogLevel[]) - List of log levels to only write messages for. (Required)

ConsoleTransport#

Logs messages to the native console and its corresponding level. This is the default transport when no transports are defined.

import { ConsoleTransport } from '@boost/log';
const transport = new ConsoleTransport();

StreamTransport#

Logs messages to any writeable stream or an object that defines a write(message: string) method.

import { StreamTransport } from '@boost/log';
const transport = new StreamTransport({
levels: ['error', 'warn'],
stream: process.stderr,
});

FileTransport#

Appends and logs messages to a file at the defined path. Will automatically rotate files when a max file size is met.

import { FileTransport } from '@boost/log';
const transport = new FileTransport({
levels: ['error'],
path: '/var/log/error.log',
});
  • gzip (boolean) - Apply gzip compression to the write stream.
  • maxSize (number) - Maximum file size before rotating file. Will create a backup and truncate the current file. Defaults to 10mb.
  • path (string | Path) - Absolute file system path for the intended log file. (Required)

RotatingFileTransport#

Like FileTransport, but also rotates files based on timestamps and a chosen periodic interval.

import { RotatingFileTransport } from '@boost/log';
const transport = new RotatingFileTransport({
levels: ['error'],
path: '/var/log/error.log',
rotation: 'daily',
});
  • rotation (hourly | daily | weekly | monthly) - Period in which to rotate files. Will append a timestamp to the rotated log file.

Test utilities#

The following Jest utilities are available in the @boost/log/test module.

mockLogger#

mockLogger(): Logger

Returns a Jest spy that matches the return value shape of createLogger.

import { mockLogger } from '@boost/log/test';
it('calls the logger', () => {
const log = mockLogger();
log('Something has happened');
expect(log).toHaveBeenCalled();
});