Tiny logging combinator library for node and the browser
;const logger = woody ; logger; // => [WARN][2015-06-02 ...][woody] foo bar
Installation
$ npm install --save woody
Why another logging library?
I wanted a logging library that focuses on simplicity and expressiveness over configuration, that made making module-local loggers as simple as possible.
Project goals
- Expressive, unobstrusive logging library
- Simple to contextualize a logger, in other words: easy to put a line of output into context.
- Compatibility with
console.log
in terms of all logging functions (same semantics) - As small as possible developer "buy-in" - it should be easy to pack your bags and leave woody for something else
- Consistency with existing logging projects - e.g. same log-level names and weighting order.
Usage
The idea of woody is to make it as easy as possible to contextualize logging. The application or library using woody could have a root logger and then pass "contextualized" sub-loggers into different areas of the codebase.
Logger#log | trace | debug | info | warn | error | fatal
The .log(...)
and friends are semantically identical to the console.log
function and have a straight forward mapping provided by woody.console
.
Logger#fork | module
The .fork(...)
function takes either a string or a function and creates a
new logger with the new context pushed onto it's context stack. The old
logger remains in tact and operationally independent; It can be used as before.
Logger#if
The .if(...)
function takes either a log level to "set the bar" and cull any
levels lower than the given level, or a function that is evaluated on each
log application.
Logger#to
The .to(...)
function takes one or more committers as input, all of which
will be invoked upon logging. A comitter is nothing but a function of shape
(level, message [, callback]) => { ... }
, where level
indicates the
log-level, the message is the rendered message string and the callback allows
for asynchronous processing of the message. The caller of e.g. Logger#log
will then recieve a promise that is either rejected or resolved based on the
callback. The callback is of the shape function([err]) => { ... }
.
Note, however, that since log
and friends are conceptually unaware of what
a committer is doing, and a logger can have multiple committers, the promise
is the output of a Promise.all
on all commit calls.
Also note that .to(...)
actually creates a new logger that effectfully calls
it's base's commit functions upon logging.
⚠️ Note that since functions can capture state at site of definition, threading down a function may not be a great idea. It's best used for internal loggers or where the function does not reference any outer state, such as e.g. a timestamped logger.
Levels
Levels are directly taken from Log4j
for consistency:
const Level = FATAL: 50000 // => Logger#fatal(...) ERROR: 40000 // => Logger#error(...) WARN: 30000 // => Logger#warn(...) INFO: 20000 // => Logger#info(...) DEBUG: 10000 // => Logger#debug(...) TRACE: 5000 // => Logger#trace(...);
Application domains aka modules
The .fork(...)
function lends itself very well to creating application or
library domain specific loggers:
; { logger; } { const logger = woody ; logger; const foo = logger; }
Now:
const app = ;
Will print the following to the console:
> [app] created
> [app][foo] created
Culling
Woody allows to conditionally cull logs from a logger. It takes either a function or a log level to determine when to cull a log request.
; const logger = woody ; logger // => [WARN] foologger // => [INFO] foologger // =>logger // =>
Culling works the same way &&
would work, consider:
; let shouldlog = true;const logger = woody ; logger // => [WARN] foologger // => [INFO] foologger // =>logger // => shouldlog = false; logger // =>logger // =>logger // =>logger // =>
This could, for example, make it easy to restrict logging at the top level
and add more fine grained control later, but since it's essentially a binary
&&
operation, any consecutive if
can only further restrict.
noop
Fallback to Woody ships with a noop
logger, that literally does nothing but satisfies the
logger interface, such that application code does not have to null-check:
// ES5 { logger = logger || woodynoop; logger;} // ES6+ { logger;}
⚠️
noop
only means it does not render or commit anything. Sequencing it with another logger using.sequence
will not return anoop
logger, but a logger that applies both thenoop
and the second logger.
Where's the stock logger X?
For now I have decided to not include "simple" resource-based loggers such as logging to file. Resource management is not the responsibility nor the intended purpose of woody. Writing a file logger is trivial, however:
;; // open the fileconst fd = fs;const logger = woody ;
Since a logger instance should not have to care about the resources it happens to consume, the management of these is out-sourced. Should a committer defect, it has to handle that error itself.
debug
Integration with There is out-of-the-box integration with the debug package on npm:
; const debug = woody debugFoo = debug; debug;debugFoo;debug;debugFoo;
yields
woody foo +0ms
woody:foo qux +1ms
woody bar +2ms
woody:foo biz +3ms