docs: add user guide, cookbook, and rewrite README
This commit is contained in:
parent
741b959820
commit
1a74f2afa4
3 changed files with 621 additions and 54 deletions
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` |
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue