esm-logging/docs/logging-cookbook.md

5.5 KiB

Logging cookbook

Recipes and patterns for common logging tasks with @administratrix/esm-logging.

Using logging across multiple modules

getLogger returns the same logger instance for a given scope name across your entire application. Configure handlers on a parent logger and create child loggers in each module:

// main.js
import * as logging from '@administratrix/esm-logging';
import { ConsoleHandler } from '@administratrix/esm-logging/src/handler';
import { Formatter } from '@administratrix/esm-logging/src/formatter';
import { doWork } from './worker.js';

const logger = logging.manager.MANAGER.getLogger('myapp');
logger.setLevel(logging.log_level.DEBUG);

const handler = new ConsoleHandler();
handler.level = logging.log_level.DEBUG;
handler.formatter = new Formatter({
    fmt: '%(asctime)s - %(name)s - %(levelname)s - %(message)s',
});
logger.addHandler(handler);

logger.info('Application starting');
doWork();
logger.info('Application finished');
// worker.js
import * as logging from '@administratrix/esm-logging';

const logger = logging.manager.MANAGER.getLogger('myapp.worker');

export function doWork() {
    logger.debug('Starting work');
    // ... do something ...
    logger.info('Work completed');
}

Because myapp.worker is a child of myapp, its records propagate up to the handler configured on myapp. No handler configuration is needed in worker.js.

Output:

2026-03-14 10:00:00.000 - myapp - INFO - Application starting
2026-03-14 10:00:00.001 - myapp.worker - DEBUG - Starting work
2026-03-14 10:00:00.002 - myapp.worker - INFO - Work completed
2026-03-14 10:00:00.003 - myapp - INFO - Application finished

Logging to multiple destinations

Attach multiple handlers to a single logger, each with its own level and formatter:

import * as logging from '@administratrix/esm-logging';
import { ConsoleHandler, StderrHandler } from '@administratrix/esm-logging/src/handler';
import { Formatter } from '@administratrix/esm-logging/src/formatter';

const logger = logging.manager.MANAGER.getLogger('myapp');
logger.setLevel(logging.log_level.DEBUG);

// console handler: shows everything with detailed format
const consoleHandler = new ConsoleHandler();
consoleHandler.level = logging.log_level.DEBUG;
consoleHandler.formatter = new Formatter({
    fmt: '%(asctime)s [%(levelname)s] %(name)s: %(message)s',
});
logger.addHandler(consoleHandler);

// stderr handler: only errors, compact format
const errorHandler = new StderrHandler(logging.log_level.ERROR);
errorHandler.formatter = new Formatter({
    fmt: 'ERROR %(asctime)s %(name)s: %(message)s',
    datefmt: '%Y-%m-%d %H:%M:%S',
});
logger.addHandler(errorHandler);

Using basicConfig for simple scripts

For quick scripts, basicConfig sets up a handler on the root logger:

import * as logging from '@administratrix/esm-logging';

logging.config.basicConfig({
    level: logging.log_level.INFO,
    format: '%(levelname)s: %(message)s',
});

const logger = logging.manager.MANAGER.getLogger('script');
logger.info('Running');
logger.warning('Check this');

Output:

INFO: Running
WARNING: Check this

Filtering records

By scope

Only allow records from a specific part of the hierarchy:

import { Filter } from '@administratrix/esm-logging/src/filter';

const dbFilter = new Filter('myapp.db');
handler.addFilter(dbFilter);
// handler now only processes records from 'myapp.db' and its children

By custom criteria

Use any object with a filter(record) method:

handler.addFilter({
    filter(record) {
        // only pass records that contain 'important' in the message
        return record.msg.includes('important');
    }
});

Custom formatters

Create formatters with different format strings for different contexts:

import { Formatter } from '@administratrix/esm-logging/src/formatter';

// detailed format for development
const devFormatter = new Formatter({
    fmt: '%(asctime)s %(levelname)s %(name)s: %(message)s',
    datefmt: '%H:%M:%S',
});

// compact format for production
const prodFormatter = new Formatter({
    fmt: '%(levelname)s:%(name)s:%(message)s',
});

Custom handlers

Subclass Handler and implement emit(record):

import { Handler } from '@administratrix/esm-logging/src/handler';

class BufferHandler extends Handler {
    constructor(level) {
        super(level);
        this.buffer = [];
    }

    emit(record) {
        try {
            this.buffer.push(this.format(record));
            if (this.buffer.length >= 100) {
                this.flush();
            }
        } catch (e) {
            this.handleError(record);
        }
    }

    flush() {
        // send buffered records somewhere
        const batch = this.buffer.splice(0);
        // ... process batch ...
    }
}

Disabling logging below a threshold

The manager's disable property suppresses all logging at or below a given level across all loggers:

import * as logging from '@administratrix/esm-logging';

// suppress DEBUG and INFO globally
logging.manager.MANAGER.disable = logging.log_level.INFO;

Reconfiguring with force

basicConfig only takes effect once. To reconfigure, use force: true:

logging.config.basicConfig({
    level: logging.log_level.DEBUG,
    format: '%(asctime)s %(message)s',
    force: true,
});

This removes and closes all existing handlers on the root logger before applying the new configuration.