Memoir
Memoir is a type-checked asynchronous logging facility with a simple and familiar interface.
Memoir provides a framework for building custom Logger
, Handler
, and Formatter
implementations. Memoir also provides prebuilt implementations that can be used for everyday logging, such as Memoir's performance oriented LevelLogger
.
Table of Contents
Install
npm install memoir
Concepts
Memoir implements concepts that are familiar in the logging domain i.e., Levels
, Loggers
, Handlers
, and Formatters
. In addition to these familiar concepts, Memoir features a Metadata
object that contains relevant information about the logged message (e.g., the log Level, originating function name, etc.). It may be passed to Formatters
that support it.
The following concepts are a subset of Memoir classes that are of practical use. These concepts are used in the provided Examples
.
memoir.Level[level]
- level
<string>
-
BASE
<string>
-
DEBUG
<string>
-
INFO
<string>
-
WARN
<string>
-
ERROR
<string>
-
memoir.Metadata
-
name
<string>
The name of theLogger
. -
level
<string>
The level of the log message represented as a string. -
func
<string>
The name of the function where theLogger
method was called. -
url
<string>
The file URL for the module where theLogger
method was called. -
line
<string>
The line number where theLogger
method was called. -
col
<string>
The column number where theLogger
method was called.
The Metadata
object may be passed to a Formatter
that supports it.
memoir.LevelLogger<MessageT, FormatT>(options)
- Extends:
<memoir.MetadataLogger>
- options
<LoggerOptions & LevelLoggerOptions>
-
name
<string>
Optional string that names the Logger. -
level
<memoir.Level>
OptionalLevel
that indicates which methods theLogger
will implement for logging. Default:Level.BASE
-
levelLogger.setLevel(level)
-
level
<memoir.Level>
ALevel
that indicates which methods theLogger
will implement for logging.
levelLogger.addHandler(handler)
-
handler
<memoir.MetadataHandler>
A MemoirHandler
that supports aMetadata
argument.
The LevelLogger implementation is unique in that it may be assigned a
Level
in its constructor or aLevel
may be set using itssetLevel
method. TheLevelLogger
will configure its interface according to the logLevel
. JavaScript's optional chaining operator can be used in order to only log messages that meet the specifiedLevel
constraint. This strategy is aimed at improving performance.
memoir.MetadataHandler<MessageT, FormatT>()
- Extends:
<memoir.Handler>
handler.setFormatter(formatter: Formatter)
-
formatter
<memoir.formatter>
A memoirFormatter
.
memoir.MetadataFormatter<MessageT, FormatT>(formatter)
- Extends:
<memoir.Formatter>
-
formatter
<(message: MessageT, meta: Metadata) => FormatT>
A function that will return the formatted message of typeFormatT
.
memoir.RotatingFileHandler(options)
- options
<FileHandlerOptions>
-
path
<string>
-
rotations
<0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10>
Optional number of rotations. -
bytes
<number>
The size of the log file in MB. Default:1e6
-
encoding
<BufferEncoding>
Default:utf8
-
mode
<number>
Default:0o666
-
Performant Logging
Memoir provides a performant Logger
class named LevelLogger
that implements a dynamic logging interface. LevelLogger
may implement any of the common logging methods: base
, debug
, info
, warn
, and error
. The LevelLogger
dynamically configures its interface to only implement the methods that are relevant for the specified logging Level
that is passed to its constructor or set using its setLevel
method. This approach allows the programmer to take advantage of JavaScript's optional chaining operator in order to eliminate unnecessary calls to the Logger
and its Handlers
.
Practically, this strategy ensures that frequent calls to LevelLogger.debug
are not evaluated unless the LevelLogger
has been expressly configured to the DEBUG
Level
.
TypeScript will enforce the usage of the optional chaining operator when calling LevelLogger
's methods. Please see the examples for how to use the LevelLogger
interface.
Examples
ConsoleHandler Logger
In this simple example you will create a LevelLogger
. The LevelLogger
's Handler
will be set to log at the DEBUG
Level; however, the LevelLogger
level will be set to INFO
, which will log INFO
, WARN
, and ERROR
messages. This ensures that frequent calls throughout the codebase to log.debug
will never be evaluated. This is achieved by using JavaScript's optional chaining operator.
Code
const formatter = (message: string, { name, level, func, url, line, col }: Metadata): string =>
`${name}:${level}:${new Date().toISOString()}:${func}:${line}:${col}:${message}`;
const log = new LevelLogger<string, string>({ name: 'Console Handler Example 1', level: Level.INFO }); // Create an instance of a Logger.
const consoleHandler = new ConsoleHandler<string, string>(); // Create an instance of a Handler.
const metadataFormatter = new MetadataFormatter<string, string>({ formatter }); // Create an instance of a Formatter.
consoleHandler.setLevel(Level.DEBUG); // Set the Level of the Handler.
consoleHandler.setFormatter(metadataFormatter); // Set the Formatter on the Handler.
log.addHandler(consoleHandler); // Add the Handler to the Logger.
log.debug?.("Because the LevelLogger's `level` property is set to Level.INFO, this method is never called.");
log.info?.('Hello World.'); // Log a Hello World to the console.
(function test() { log.info?.('Hello World.'); }());
log.setLevel(Level.DEBUG);
log.debug?.("The LevelLogger's `level` property has been set to Level.DEBUG; hence, the method is called.");
Output
Console Handler Example 1:INFO:2023-09-15T20:05:10.621Z:undefined:11:11:Hello World.
Console Handler Example 1:INFO:2023-09-15T20:05:10.631Z:test:12:30:Hello World.
Console Handler Example 1:DEBUG:2023-09-15T20:05:10.632Z:undefined:14:12:The LevelLogger's `level` property has been set to Level.DEBUG; hence, the method is called.
RotatingFileHandler Logger
Code
const formatter = (message: string, { name, level, func, url, line, col }: Metadata): string =>
`${name}:${level}:${new Date().toISOString()}:${func}:${line}:${col}:${message}`;
const log = new LevelLogger<string, string>({ name: 'Rotating File Handler Example' }); // Create an instance of a Logger.
const fileHandler = new RotatingFileHandler({ path: './test.log', rotations: 5 }); // Create an instance of a Handler.
const metadataFormatter = new MetadataFormatter<string, string>({ formatter }); // Create an instance of a Formatter.
fileHandler.setLevel(Level.DEBUG); // Set the Level of the Handler.
fileHandler.setFormatter(metadataFormatter); // Set the Formatter on the Handler.
log.addHandler(fileHandler); // Add the Handler to the Logger.
log.info?.('Hello World.'); // Log a Hello World to the console.
(function test() { log.info?.('Hello World.'); }());
Output
Rotating File Handler Example:INFO:2023-09-15T20:03:45.657Z:undefined:10:11:Hello World.
Rotating File Handler Example:INFO:2023-09-15T20:03:45.657Z:test:11:30:Hello World.
How To
How to build a type-checked custom Memoir Handler.
Please see the examples in ./src/console_handler.ts
and ./src/rotating_file_handler.ts
for practical examples that demonstrate how to subclass the base classes in order to build a custom Memoir Handler.