How to use @owlmeans/log — the one logging system of an OwlMeans process. Levels (debug|info|warn|error|silent), scoped loggers, structured data with automatic redaction, per-call routing to the console and/or analytics plugins by option parameters, the plugin contract, the global console override, per-environment configuration through cfg.log (file-resolved on a server, build-time in a browser), debug scopes, throttling and testing with the memory plugin. Auto-invoked when adding or changing...
Scanned 10/6/2026
npx -y skills add owlmeans/common --skill log --agent claude-codeInstalls into .claude/skills of the current project.
Are you the author of Log?
Add the live security badge to your README — it updates automatically with every re-scan.
[](https://www.skillsdirectory.com/skills/owlmeans-log)More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.
---
name: log
description: How to use @owlmeans/log — the one logging system of an OwlMeans process. Levels (debug|info|warn|error|silent), scoped loggers, structured data with automatic redaction, per-call routing to the console and/or analytics plugins by option parameters, the plugin contract, the global console override, per-environment configuration through cfg.log (file-resolved on a server, build-time in a browser), debug scopes, throttling and testing with the memory plugin. Auto-invoked when adding or changing any log line, importing logger, logThrottle, addLogPlugin, configureLog or appendLog, replacing console.*, wiring a level into a chart or .env, or reading a log to diagnose a process.
user-invocable: false
---
# @owlmeans/log
**Layer:** Core (depends on `@owlmeans/context` only; isomorphic — server, browser, worker)
**Install:** `"@owlmeans/log": "^0.1.18-rc.2"` in `dependencies`
Everything a process says about itself goes through this package. It owns four decisions that a bare
`console.*` leaves to chance: **how much** is written (the level), **in what form** (text or JSON on a
server, the original objects in a browser), **what is never written** (secrets), and **who else
hears it** (plugins: analytics, a platform link, a file, a test).
## Use it
```ts
import { logger } from '@owlmeans/log'
const log = logger('billing') // one per module; the scope names where a line comes from
log.debug('Plan read', { entityId }) // chatty: per request, per tick, progress, dumps
log.info('Subscription started', { plan }, { event: 'subscription.started' })
log.warn('Fallback used', { model }) // degraded but continuing
log.error('Save failed', error) // pass the Error itself — its stack and class survive
log.error('Save failed', { error, id }) // an Error under data.error / data.err is lifted out
if (log.enabled('debug')) log.debug('dump', costly()) // guard a costly argument
const worker = log.child('worker', { queue }) // scope `billing:worker`, `queue` on every line
```
`logger(scope)` is cheap — keep one per module. A scope is lowercase and colon-separated
(`agent:slot`, `publisher:build`); it is what `cfg.log.debug` selects by.
### Levels — what belongs where
| Level | Written when | Examples |
|---|---|---|
| `debug` | the process is being diagnosed | per-request lines, progress, state changes, object dumps, model usage |
| `info` | an operator wants it in production | a job/pipeline started or stopped, a subscription event, a minimal auth event, a server listening, a migration applied |
| `warn` | something degraded and the work went on | a fallback, a refused request, an access-forbidden, a swallowed non-fatal error |
| `error` | the work failed | a fault, a failed write, an unhandled rejection |
| `silent` | nothing | — |
A line that can fire every few seconds is `debug`, or guarded: `if (logThrottle('key', 30_000)) …` is
`true` once per window. Give a significant event a stable dotted name in the options (`{ event: 'job.start' }`)
— it makes the line greppable and lets a plugin pick it up.
### Never write
Tokens, keys, passwords, authorization/cookie headers, whole request or response bodies, user prompts,
file contents, environment values. Log ids, names, sizes, counts, statuses. `redact()` runs on every
record's data and replaces the value of a secret-looking key (`token`, `secret`, `password`,
`authorization`, `cookie`, `apiKey`, `credential`, `private…`, `signature`; `token` only as the END of a
key, so `maxTokens` survives) and clips long strings and deep structures — but it cannot recognize a
secret inside a free-text value, so do not rely on it for a dump.
## Console and analytics are call parameters
One call can go to either, chosen by its options — there are not two APIs:
```ts
log.info('Opened', { page }, { analytics: 'page_view' }) // console AND analytics plugins
log.info('Opened', { page }, { analytics: 'page_view', console: false }) // analytics plugins only
log.info('Plain line') // console only
```
`analytics` (a name, or `true` to use `event`/the message) hands the call to every plugin's `track`;
it ignores the level — an analytics call needs none. `console: false` keeps it out of the console
sink and the plugins' `log`. Use `analytics` only for product events that something should count.
## Plugins
```ts
interface LogPlugin {
name: string
log?: (record: LogRecord) => void // every record the level filter admitted
track?: (event: AnalyticsEvent) => void // every call that asked for analytics
install?: () => void; uninstall?: () => void
}
addLogPlugin(plugin) // same name replaces; returns the remover
```
Analytics systems (`@owlmeans/web-log`'s GTM plugin, a server-side measurement sender), a target's
link to the platform (`@owlmeans/viable-log`), a file or a test's memory are all plugins. A plugin that
throws is skipped (and reported at most once a minute) — logging never breaks the work.
## Console override
`appendLog(context)` — which `makeServerContext` and `makeClientContext` already call — applies
`cfg.log` and **replaces the global `console`**: `console.debug/log/trace` become debug, `info`, `warn`
and `error` keep their level, all under scope `console`. A library that still calls `console.*` is
therefore under the same level as everything else. `cfg.log.console: 'native'` leaves the console
alone; CLIs do not install the override. Do not write new `console.*` calls anyway: a scope and a
level are the point.
The sink writes through the console methods captured when the module loaded (shared through
`globalThis`, so duplicate copies of the package cannot capture each other's override). In a browser
the sink passes the original objects, **including an `Error`, to `console.error`** — an error reporter
hooked on `console.error` (the preview's) keeps working.
## Configure it — `cfg.log`
```ts
cfg.log = { level: 'info', debug: 'agent:slot,jobs', format: 'text', console: 'override' }
```
| Field | Values | Meaning |
|---|---|---|
| `level` | `debug` `info` `warn` `error` `silent` | the floor; default `info` |
| `debug` | `'*'` or comma-separated scopes | scopes that log at debug whatever `level` is (a scope matches itself and its children) |
| `format` | `text` `json` | server sink only |
| `console` | `override` `native` | whether `appendLog` replaces the global console |
Every field also accepts the raw string a config file resolves to; a value that does not parse
(including an unresolved `/etc/…` path) is ignored and the previous setting stays — a mistake can
never silence or flood a process.
- **Server:** declare the leaves as file paths (`level: '/etc/app-config/log-level'`); the server
context's file reader resolves them, and `appendLog` applies the result — as a Config middleware
right after the reader and again as a Context middleware after every reader has finished.
- **Browser:** the value must be known at the first render (the i18n instance is created then), so
it is a **build-time constant** (`import.meta.env.VITE_LOG_LEVEL` in a Vite app), not something the
API's advertised config can change later. `log` is deliberately NOT in the advertised config.
- `debug.all` of `cfg.debug` no longer controls logging; it is a feature flag for other things.
## Test with it
```ts
const memory = memoryPlugin()
addLogPlugin(memory)
logger('x').warn('boom', { id: 1 })
expect(memory.records[0]).toMatchObject({ level: 'warn', scope: 'x', data: { id: 1 } })
resetLog() // restores the console, drops plugins, back to the defaults
```
`configureLog({ level: 'silent' })` quiets a noisy test; do not replace `console.*` methods — the sink
writes through captured natives, so it would not see them.
## API
`logger`, `appendLog`, `configureLog`, `logConfig`, `logEnabled`, `logThrottle`, `addLogPlugin`,
`removeLogPlugin`, `logPlugins`, `overrideConsole`, `restoreConsole`, `resetLog`, `memoryPlugin`,
`redact`, `parseLogLevel`, `nativeConsole`; types `LogConfig`, `LogRecord`, `AnalyticsEvent`,
`LogPlugin`, `Logger`, `LogOptions`.
Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.
No comments yet. Be the first to comment!