Merge branch 'feature/10' into dev
This commit is contained in:
commit
768ff9263d
4 changed files with 622 additions and 55 deletions
104
README.md
104
README.md
|
|
@ -1,33 +1,89 @@
|
||||||
# esm-logging
|
# esm-logging
|
||||||
|
|
||||||
> This README is a stub. Working on it. Currently stabilizing the build
|
A quasi-port of the Python standard library
|
||||||
environment after that I'll make it nice around here.
|
[logging](https://docs.python.org/3/library/logging.html) module to
|
||||||
|
ECMAScript. Browser-compatible, zero dependencies.
|
||||||
|
|
||||||
A quasi-port of the Python standard library logging module to ECMAScript.
|
## Why?
|
||||||
|
|
||||||
# Why?
|
Logging is important. It is important for debugging purposes, leading to
|
||||||
|
faster and more resilient development, for traceability leading to better
|
||||||
First of, because logging is important. It is important for debugging purposes,
|
security. Most logging libraries I've discovered didn't satisfy me, introduced
|
||||||
leading to faster and more resilient development, for traceability leading to
|
weird concepts and all in all just weren't great. Other programming language
|
||||||
better security. Most logging libraries I've discovered didn't satisfy me,
|
ecosystems offer way nicer logging facilities. Take Rust for example, or...
|
||||||
introduced weird concepts and all in all just weren't great. Other programming
|
Python! Python has PEP, giving it a very structured approach towards
|
||||||
language ecosystems offer way nicer logging facilities. Take Rust for example,
|
|
||||||
or... Python! Python has PEP, giving it a very structured approach towards
|
|
||||||
implementing new features and that's also how its logging facilities came to be
|
implementing new features and that's also how its logging facilities came to be
|
||||||
([PEP 282](https://peps.python.org/pep-0282/)). Python's logging facilities are
|
([PEP 282](https://peps.python.org/pep-0282/)). Python's logging facilities
|
||||||
implemented by the [logging]() module, which is part of the standard library and
|
are implemented by the [logging](https://docs.python.org/3/library/logging.html)
|
||||||
has been since 2002. It was originally authored by Vinay Sajip
|
module, which is part of the standard library and has been since 2002. It was
|
||||||
|
originally authored by Vinay Sajip.
|
||||||
|
|
||||||
# Roadmap
|
## Installation
|
||||||
|
|
||||||
- do a quasi-port of the logging module with minimal amount of adaption
|
```bash
|
||||||
- add documentation
|
npm install @administratrix/esm-logging
|
||||||
- add support for asynchronous calls
|
```
|
||||||
- implement Open Cybersecurity Framework (OCSF) formatter
|
|
||||||
- implement (Browser) local storage handler as a replacement for file handler
|
|
||||||
|
|
||||||
# Usage
|
## Quick start
|
||||||
|
|
||||||
For the time being, please check out my [CI
|
```javascript
|
||||||
service](https://bitbucket.org/byteb4rb1e/esm-logging/pipelines), for an idea on
|
import * as logging from '@administratrix/esm-logging';
|
||||||
how to build this.
|
|
||||||
|
// one-shot configuration: sets up a console handler on the root logger
|
||||||
|
logging.config.basicConfig({ level: logging.log_level.INFO });
|
||||||
|
|
||||||
|
// create a logger for this module
|
||||||
|
const logger = logging.manager.MANAGER.getLogger('myapp');
|
||||||
|
|
||||||
|
logger.info('Application started');
|
||||||
|
logger.warning('Something looks off');
|
||||||
|
logger.error('Something went wrong');
|
||||||
|
```
|
||||||
|
|
||||||
|
## Concepts
|
||||||
|
|
||||||
|
The logging system is built around four core components:
|
||||||
|
|
||||||
|
- **Loggers** expose the interface that application code uses directly.
|
||||||
|
- **Handlers** send log records to the appropriate destination (console,
|
||||||
|
stderr, custom writable streams).
|
||||||
|
- **Formatters** control the layout of log records in the final output.
|
||||||
|
- **Filters** provide fine-grained control over which records to output.
|
||||||
|
|
||||||
|
Loggers are organized in a dot-separated hierarchy. A logger named `app.db`
|
||||||
|
is a child of the logger named `app`. Log records propagate up the hierarchy,
|
||||||
|
so a handler attached to `app` will also receive records from `app.db`.
|
||||||
|
|
||||||
|
## Log levels
|
||||||
|
|
||||||
|
| Constant | Value | Purpose |
|
||||||
|
|------------|-------|------------------------------------------|
|
||||||
|
| `CRITICAL` | 50 | A serious error, the program may not continue |
|
||||||
|
| `ERROR` | 40 | An error that prevented some operation |
|
||||||
|
| `WARNING` | 30 | Something unexpected, but the software still works |
|
||||||
|
| `INFO` | 20 | Confirmation that things work as expected |
|
||||||
|
| `DEBUG` | 10 | Detailed diagnostic information |
|
||||||
|
| `NOTSET` | 0 | All messages are processed |
|
||||||
|
|
||||||
|
## Documentation
|
||||||
|
|
||||||
|
See [docs/](docs/README.md) for the full user guide and
|
||||||
|
[docs/logging-cookbook.md](docs/logging-cookbook.md) for recipes and patterns.
|
||||||
|
|
||||||
|
API reference can be generated with TypeDoc:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm run build/doc
|
||||||
|
```
|
||||||
|
|
||||||
|
## Roadmap
|
||||||
|
|
||||||
|
- [x] quasi-port of the logging module with minimal adaptation
|
||||||
|
- [x] add documentation
|
||||||
|
- [ ] add support for asynchronous calls
|
||||||
|
- [ ] implement Open Cybersecurity Schema Framework (OCSF) formatter
|
||||||
|
- [ ] implement browser local storage handler as a replacement for file handler
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
UNLICENSED
|
||||||
|
|
|
||||||
2
TODO
2
TODO
|
|
@ -156,7 +156,7 @@ Content-Type: application/issue
|
||||||
ID: 10
|
ID: 10
|
||||||
Type: feature
|
Type: feature
|
||||||
Title: add documentation
|
Title: add documentation
|
||||||
Status: open
|
Status: done
|
||||||
Priority: medium
|
Priority: medium
|
||||||
Created: 2026-03-13
|
Created: 2026-03-13
|
||||||
Relationships:
|
Relationships:
|
||||||
|
|
|
||||||
334
docs/README.md
334
docs/README.md
|
|
@ -1 +1,333 @@
|
||||||
The doc/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](#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` |
|
||||||
|
|
|
||||||
|
|
@ -1,37 +1,216 @@
|
||||||
# Using logging in multiple modules
|
# Logging cookbook
|
||||||
|
|
||||||
Multiple calls to `logging.getLogger('someLogger')` return a reference to the
|
Recipes and patterns for common logging tasks with `@administratrix/esm-logging`.
|
||||||
same logger object. This is true not only within the same module, but also
|
|
||||||
across modules as long as it is in the same Python interpreter process. It is
|
|
||||||
true for references to the same object; additionally, application code can
|
|
||||||
define and configure a parent logger in one module and create (but not
|
|
||||||
configure) a child logger in a separate module, and all logger calls to the
|
|
||||||
child will pass up to the parent. Here is a main module:
|
|
||||||
|
|
||||||
``javascript
|
## Using logging across multiple modules
|
||||||
import * as logging from 'eslib/logging';
|
|
||||||
import * as my_module from './my_module';
|
|
||||||
|
|
||||||
// create logger with 'spam_application'
|
`getLogger` returns the same logger instance for a given scope name across
|
||||||
var logger = logging.getLogger('spam_application');
|
your entire application. Configure handlers on a parent logger and create
|
||||||
logger.setLevel(logging.DEBUG);
|
child loggers in each module:
|
||||||
|
|
||||||
// create file handler which logs even debug messages
|
```javascript
|
||||||
var fh = logging.FileHandler('spam.log')
|
// main.js
|
||||||
fh.setLevel(logging.DEBUG);
|
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';
|
||||||
|
|
||||||
// create console handler with a higher log level
|
const logger = logging.manager.MANAGER.getLogger('myapp');
|
||||||
var ch = logging.StreamHandler();
|
logger.setLevel(logging.log_level.DEBUG);
|
||||||
ch.setLevel(logging.ERROR);
|
|
||||||
|
|
||||||
// create formatter and add it to the handlers
|
const handler = new ConsoleHandler();
|
||||||
var formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s');
|
handler.level = logging.log_level.DEBUG;
|
||||||
fh.setFormatter(formatter);
|
handler.formatter = new Formatter({
|
||||||
ch.setFormatter(formatter);
|
fmt: '%(asctime)s - %(name)s - %(levelname)s - %(message)s',
|
||||||
|
});
|
||||||
|
logger.addHandler(handler);
|
||||||
|
|
||||||
// add the handlers to the logger
|
logger.info('Application starting');
|
||||||
logger.addHandler(fh);
|
doWork();
|
||||||
logger.addHandler(ch);
|
logger.info('Application finished');
|
||||||
|
|
||||||
logger.info('creating an instance of auxiliary_module.Auxiliary')
|
|
||||||
```
|
```
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
// 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:
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
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:
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
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:
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
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:
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
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:
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
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)`:
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
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:
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
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`:
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
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.
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue