| .. | ||
| logging-cookbook.md | ||
| README.md | ||
esm-logging user guide
This guide covers the @administratrix/esm-logging library, a quasi-port of
Python's logging module for ECMAScript. It targets browser environments and
provides hierarchical, configurable logging with formatters, handlers, and
filters.
Table of contents
Basic usage
The simplest way to start logging is with basicConfig:
import * as logging from '@administratrix/esm-logging';
logging.config.basicConfig({
level: logging.log_level.DEBUG,
format: '%(levelname)s:%(name)s:%(message)s',
});
const logger = logging.manager.MANAGER.getLogger('myapp');
logger.info('Hello, world');
// output: INFO:myapp:Hello, world
basicConfig creates a StreamHandler writing to stderr (via
console.error) and attaches it to the root logger. It is a one-shot
function: calling it again has no effect unless you pass force: true.
Loggers
Loggers are the entry point for emitting log records. They are organized in a
dot-separated hierarchy managed by a singleton Manager.
Creating loggers
const logger = logging.manager.MANAGER.getLogger('myapp');
const childLogger = logging.manager.MANAGER.getLogger('myapp.db');
Calling getLogger with the same scope always returns the same logger
instance. The hierarchy is established automatically: myapp.db is a child of
myapp.
Setting levels
logger.setLevel(logging.log_level.WARNING);
A logger only processes messages at or above its effective level. The
effective level is determined by walking up the parent chain until a logger
with a non-zero level is found. The root logger defaults to WARNING.
Logging methods
Each level has a corresponding method:
logger.debug('Detailed diagnostic info');
logger.info('Things are working');
logger.warning('Something unexpected');
logger.error('An operation failed');
logger.critical('System is in trouble');
Propagation
By default, log records propagate up the hierarchy. A record emitted by
myapp.db will be handled by handlers on myapp.db, then myapp, then the
root logger. This means you typically only need to configure handlers on the
root logger or on high-level loggers.
Checking if a level is enabled
if (logger.isEnabledFor(logging.log_level.DEBUG)) {
logger.debug('Expensive computation: ' + computeDebugInfo());
}
This avoids the cost of building the message string when the level would be filtered out anyway.
Handlers
Handlers determine where log records go. A logger can have multiple handlers, and each handler can have its own level and formatter.
Available handlers
StreamHandler
Writes formatted output to a Writable stream. Defaults to stderr (via
console.error).
import { StreamHandler } from '@administratrix/esm-logging/src/handler';
import { ConsoleWritable } from '@administratrix/esm-logging/src/helper/stream';
const handler = new StreamHandler(new ConsoleWritable());
logger.addHandler(handler);
ConsoleHandler
Maps log levels to the appropriate browser console method:
ERRORandCRITICALuseconsole.errorWARNINGusesconsole.warnDEBUGandINFOuseconsole.log
import { ConsoleHandler } from '@administratrix/esm-logging/src/handler';
const handler = new ConsoleHandler();
handler.level = logging.log_level.DEBUG;
logger.addHandler(handler);
StderrHandler
Always writes to console.error, regardless of level. Useful when you want
all output on stderr.
import { StderrHandler } from '@administratrix/esm-logging/src/handler';
const handler = new StderrHandler(logging.log_level.WARNING);
logger.addHandler(handler);
FileHandler
Not available in browser environments. Throws NotImplementedError on
construction. Use ConsoleHandler or a storage-backed handler instead.
Handler methods
handler.level = logging.log_level.INFO; // only handle INFO and above
handler.formatter = myFormatter; // set a custom formatter
handler.close(); // release resources
Custom handlers
Subclass Handler and implement emit(record):
import { Handler } from '@administratrix/esm-logging/src/handler';
class ArrayHandler extends Handler {
constructor() {
super();
this.records = [];
}
emit(record) {
this.records.push(this.format(record));
}
}
Formatters
Formatters control how log records are rendered as strings. The default format
is %(message)s (just the message). The basic format used by basicConfig is
%(levelname)s:%(name)s:%(message)s.
Creating a formatter
import { Formatter } from '@administratrix/esm-logging/src/formatter';
const formatter = new Formatter({
fmt: '%(asctime)s - %(levelname)s - %(name)s - %(message)s',
datefmt: '%Y-%m-%d %H:%M:%S',
});
handler.formatter = formatter;
Format string placeholders
Formatters use %-style substitution. Available placeholders correspond to
log record attributes:
%(name)s Logger scope name
%(levelno)d Numeric log level
%(levelname)s Text log level (DEBUG, INFO, etc.)
%(message)s The formatted message
%(asctime)s Human-readable timestamp
%(created)f Milliseconds since epoch (Date.now())
Date formatting
The datefmt option accepts strftime-style tokens:
| Token | Meaning | Example |
|---|---|---|
%Y |
Four-digit year | 2026 |
%m |
Zero-padded month | 03 |
%d |
Zero-padded day | 14 |
%H |
Hour (24h) | 09 |
%M |
Minute | 05 |
%S |
Second | 30 |
If datefmt is omitted, an ISO 8601-like format is used:
2026-03-14 09:05:30.123.
Filters
Filters provide fine-grained control over which records get processed. They can be attached to loggers or handlers.
Scope-based filtering
A Filter initialized with a scope name only allows records from that scope
and its children:
import { Filter } from '@administratrix/esm-logging/src/filter';
const filter = new Filter('myapp.db');
handler.addFilter(filter);
// only records from 'myapp.db' and 'myapp.db.*' will pass
A filter initialized with an empty string allows all records.
Custom filters
Any object with a filter(record) method can be used:
handler.addFilter({
filter(record) {
return record.levelno >= logging.log_level.WARNING;
}
});
Configuration
basicConfig
basicConfig is a convenience function for simple, one-shot configuration of
the root logger:
logging.config.basicConfig({
level: logging.log_level.DEBUG,
format: '%(asctime)s %(levelname)s %(message)s',
datefmt: '%Y-%m-%d %H:%M:%S',
});
Options
| Option | Type | Description |
|---|---|---|
level |
number |
Root logger level |
format |
string |
Format string for the handler's formatter |
datefmt |
string |
Date format string |
style |
string |
Format style ('%' only, currently) |
handlers |
Handler[] |
Pre-built handlers to attach |
stream |
Writable |
Stream for the default StreamHandler |
force |
boolean |
Remove existing handlers first |
stream and handlers are mutually exclusive.
Manual configuration
For more control, configure loggers and handlers directly:
const root = logging.manager.MANAGER.root;
const handler = new ConsoleHandler();
handler.level = logging.log_level.DEBUG;
handler.formatter = new Formatter({
fmt: '%(asctime)s [%(levelname)s] %(name)s: %(message)s',
});
root.addHandler(handler);
root.setLevel(logging.log_level.DEBUG);
Log record attributes
A LogRecord carries the following attributes:
| Attribute | Type | Description |
|---|---|---|
scope |
string |
Logger name that created the record |
name |
string |
Same as scope |
levelno |
number |
Numeric level (10, 20, 30, 40, 50) |
levelname |
string |
Text level (DEBUG, INFO, etc.) |
msg |
string |
The raw message template |
args |
any[] |
Substitution arguments for %s in msg |
message |
string |
The formatted message (set by formatter) |
created |
number |
Milliseconds since Unix epoch |
asctime |
string |
Formatted timestamp (set by formatter) |
The getMessage() method on LogRecord performs %s argument substitution
on msg using args.
Module structure
The library is organized into submodules, all re-exported from the main entry point:
| Import path | Contents |
|---|---|
config |
basicConfig() |
filter |
Filter, Filterer |
formatter |
Formatter, STYLES, DEFAULT_FORMATTER |
handler |
Handler, StreamHandler, ConsoleHandler, StderrHandler, FileHandler |
log_level |
Level constants, getLevelName(), checkLevel() |
log_record |
LogRecord, factory functions |
logger |
Logger, RootLogger, ROOT |
manager |
Manager, MANAGER |