# 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](#basic-usage) - [Loggers](#loggers) - [Handlers](#handlers) - [Formatters](#formatters) - [Filters](#filters) - [Configuration](#configuration) - [Log record attributes](#log-record-attributes) ## Basic usage The simplest way to start logging is with `basicConfig`: ```javascript 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 ```javascript 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 ```javascript 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: ```javascript 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 ```javascript 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`). ```javascript 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: - `ERROR` and `CRITICAL` use `console.error` - `WARNING` uses `console.warn` - `DEBUG` and `INFO` use `console.log` ```javascript 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. ```javascript 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 ```javascript 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)`: ```javascript 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 ```javascript 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](#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: ```javascript 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: ```javascript 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: ```javascript 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: ```javascript 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` |