@clevercloud/scribe
v0.0.2
Published
Structured logging library built on Pino with stdout/stderr splitting, file rotation, and namespaced loggers
Maintainers
Keywords
Readme
@clevercloud/scribe
Structured logging library built on Pino with stdout/stderr splitting, file rotation, and namespaced loggers.
Install
pnpm add @clevercloud/scribeQuick start
import { setup, getLogger, shutdown } from '@clevercloud/scribe';
setup({ stdio: { level: 'info' } });
const log = getLogger('my-service');
log.info('Server started');
log.error({ err }, 'Something went wrong');
// Flush before exit
await shutdown();API
setup(config)
Initializes the logging system. Must be called once before getLogger().
setup({
stdio: { level: 'info', pretty: true },
});| Option | Type | Default | Description |
| ----------------- | ----------------------- | ------- | ---------------------------------------- |
| namespaceLevels | Record<string, Level> | — | Per-namespace level overrides |
| resolveModule | boolean | false | Attach caller file path to each logger |
| stdio | StdioTransportConfig | — | Stdio transport config |
| file | FileTransportConfig | — | File transport config |
| redact | string[] | — | Dot-notation paths to redact from output |
When neither stdio nor file is provided, the logger is fully silent.
Throws if called twice without calling shutdown() first.
StdioTransportConfig
| Option | Type | Default | Description |
| -------- | --------- | ------- | ------------------------------------------------ |
| level | Level | — | Minimum severity level for stdout/stderr |
| pretty | boolean | false | Enable human-readable formatting via pino-pretty |
FileTransportConfig
| Option | Type | Default | Description |
| ---------- | ------------------------------- | ------- | ------------------------------------------------------------------------------------------ |
| level | Level | — | Minimum severity level for file output |
| path | string | — | File path to write logs to (required) |
| rotation | boolean \| FileRotationConfig | false | true enables rotation with defaults; an object customizes it; false disables rotation. |
getLogger(namespace?)
Returns a Pino Logger. The namespace argument is optional.
const log = getLogger('http');
log.info('Listening on port 8080');
// → {"level":30,"severity":"info","namespace":"http","module":"/src/server.ts","msg":"Listening on port 8080",...}
const rootLog = getLogger();
rootLog.info('App started');
// → {"level":30,"severity":"info","module":"/src/server.ts","msg":"App started",...}Each logger includes:
namespace— the name passed togetLogger(), omitted when called without an argumentmodule— the caller's file path (whenresolveModuleis enabled)- All fields injected by registered mixins
If a per-namespace level is configured via namespaceLevels, it controls what the logger emits. When multiple transports are configured, each transport still applies its own level filter independently. A logger created without a namespace cannot be targeted by namespaceLevels and inherits the root level.
addMixin(name, fn)
Registers a function that injects dynamic fields into every log entry.
import { addMixin } from '@clevercloud/scribe';
addMixin('requestId', () => asyncLocalStorage.getStore()?.requestId);- Return
nullorundefinedto omit the field from a given entry. - Re-registering with the same
namereplaces the previous function. - If the function throws, the error propagates through the
log.*()call.
shutdown()
Flushes all buffered log entries and resets internal state. Safe to call multiple times or before setup().
process.on('SIGTERM', async () => {
log.info('Shutting down…');
await shutdown();
process.exit(0);
});Transport
Scribe routes logs based on severity:
| Destination | Levels | | ----------- | ------------------------ | | stdout | trace, debug, info, warn | | stderr | error, fatal |
When file is configured, all levels are additionally written to file.
Per-transport log levels
Each transport specifies its own log level:
setup({
stdio: { level: 'warn' },
file: {
level: 'debug',
path: '/var/log/app.log',
},
});In this example, only warn and above appear in the console, while debug and above are written to file.
File rotation
File rotation is powered by pino-roll and controlled by the rotation field of the file transport:
false(or omitted) — rotation is disabled; logs are written to a plain file.true— rotation is enabled with all default settings.- A
FileRotationConfigobject — rotation is enabled with the provided overrides.
// Enable rotation with all defaults
setup({
file: {
level: 'info',
path: '/var/log/app.log',
rotation: true,
},
});
// Customize rotation
setup({
file: {
level: 'info',
path: '/var/log/app.log',
rotation: {
size: '50m',
frequency: 'daily',
maxFiles: 14,
},
},
});| Option | Type | Default | Description |
| ------------ | ------------------------------- | -------------- | -------------------------------------------------------- |
| size | string \| number | '50m' | Max file size ('1k', '50m', '1g', or number in MB) |
| frequency | 'daily' \| 'hourly' \| number | 'daily' | Rotation frequency (or milliseconds) |
| dateFormat | string | 'yyyy-MM-dd' | date-fns format for rotated file suffix |
| maxFiles | number | 7 | Maximum rotated files to keep |
| symlink | boolean | false | Create a current.log symlink |
Per-namespace log levels
Control what individual loggers emit, regardless of the transport level:
setup({
stdio: { level: 'info' },
namespaceLevels: {
database: 'debug',
http: 'warn',
},
});
getLogger('database').debug('Query executed'); // visible
getLogger('http').info('Request received'); // hidden (below warn)Namespace levels set the logger's emission threshold. When multiple transports are configured, each transport still applies its own level filter independently.
Single transport — namespace level controls what appears:
setup({
stdio: { level: 'warn' },
namespaceLevels: { verbose: 'debug' },
});
const log = getLogger('verbose');
log.debug('…'); // ✓ appears on stdout (namespace level wins)
log.info('…'); // ✓ appears on stdoutMultiple transports — each transport filters independently:
setup({
stdio: { level: 'warn' },
file: { level: 'debug', path: '/var/log/app.log' },
namespaceLevels: { verbose: 'debug' },
});
const log = getLogger('verbose');
log.debug('…'); // ✗ not on stdout (filtered by stdio level) ✓ in file
log.info('…'); // ✗ not on stdout (filtered by stdio level) ✓ in file
log.warn('…'); // ✓ on stdout ✓ in fileRedaction
Sensitive fields can be redacted from log output using Pino's built-in redaction:
setup({
stdio: { level: 'info' },
redact: ['password', 'req.headers.authorization'],
});
const log = getLogger('auth');
log.info({ password: 's3cret', user: 'alice' }, 'login attempt');
// → {"password":"[Redacted]","user":"alice","msg":"login attempt",...}Paths use dot notation for nested properties. See the Pino redaction docs for wildcard and advanced path syntax.
License
See LICENSE.
