Most logging frameworks are destructive. The act of converting logs to strings partially destroys the data it contains. Instead, Holz encourages pipelines of structured data:
logger.info('Sending new user email', { userId: user.id });
{
message: 'Sending new user email',
level: LogLevel.Info,
origin: ['UserService'],
context: { userId: '465ebaec-2b53-4b81-95e9-9f35771c0af2' },
}
Each log is sent through a chain of plugins that choose how to filter, transform, serialize, or upload them.
Holz is built on a chain of plugins, but if you want something that Just Works, use the preconfigured bundle:
import logger from '@holz/logger';
logger.info('Hello, world!');
That's it! You can use Holz in Node or in the browser.
By default, logs are hidden. To enable them, set the DEBUG
environment variable to the namespace(s) you want to see logs for:
DEBUG='your-app*' node script.js
Alternatively, you can enable logs by setting the localStorage.debug
property:
localStorage.debug = 'your-app*';
For more details, check the documentation.
To keep logs consistent and useful, the API is designed to follow these two rules:
- Don't Interpolate: Never interpolate data into your log messages. Instead, pass variables as structured data. This makes it easier to search, analyze, and visualize your logs.
- Keep Context Shallow: While the
log.context
property provides additional context for your log messages, we don't allow nested objects in it. This is to prevent the accidental inclusion of unsuitable log context, like sensitive user data or redux state.
By following these rules, we make sure our logs are well-organized and useful, without compromising on the privacy and security of our users.
Almost everything in Holz is a plugin. Plugins are functions that take a log and do something with it:
import { createLogger } from '@holz/core';
const plugin = (log: Log) => {
// Print it, save it to a file, pass it to another plugin...
// This is up to you.
};
const logger = createLogger(plugin);
Holz has a number of plugins already available. See each package for documentation:
Plugin | Description |
---|---|
@holz/core |
Core framework. Includes tools and types. |
@holz/ansi-terminal-backend |
Pretty-print logs to the terminal. |
@holz/console-backend |
Pretty-print logs to the browser console. |
@holz/json-backend |
Write logs as NDJSON to a writable stream. |
@holz/stream-backend |
Send plaintext logs to a writable stream. |
@holz/pattern-filter |
Filter logs against a pattern. |
@holz/env-filter |
Pull filters from env.DEBUG or localStorage . |
Holz supports forking to different log destinations by using the combine(...)
operator:
import { createLogger, combine } from '@holz/core';
const logger = createLogger(
combine([
createConsoleBackend(),
createFileBackend('./my-app.log'),
createUploadBackend({ apiKey: config.apiKey }),
])
);
import { createLogger, filter, LogLevel } from '@holz/core';
const logger = createLogger(
filter(
(log) => log.level !== LogLevel.Debug,
createStreamBackend({ stream: process.stderr })
)
);
import { createLogger, combine, filter } from '@holz/core';
const logger = createLogger(
combine([
createStreamBackend({ stream: process.stderr }),
filter(
(log) => log.origin[0] === 'my-app',
createUploadBackend({ key: config.uploadKey })
),
])
);
Holz is inspired by other loggers:
It's a play on the word "logger":
Holz (German, noun): a piece of wood, usually small.
There is another library named holz
(without the org scope). It is not related.