Ecosyste.ms: Awesome

An open API service indexing awesome lists of open source software.

Awesome Lists | Featured Topics | Projects

https://github.com/felixschl/woody

Tiny logging combinator library for node and the browser
https://github.com/felixschl/woody

Last synced: 7 days ago
JSON representation

Tiny logging combinator library for node and the browser

Awesome Lists containing this project

README

        

[![npm version](https://badge.fury.io/js/woody.svg)](http://badge.fury.io/js/woody)
[![Build Status](https://travis-ci.org/felixSchl/woody.svg?branch=master)](https://travis-ci.org/felixSchl/woody)

> Tiny logging combinator library for node and the browser

```javascript
import woody from 'woody';
const logger = woody
.as(woody.bracketed())
.to(woody.console)
.fork(woody.level())
.fork(woody.timestamp())
.fork('woody');

logger.warn('foo', 'bar'); // => [WARN][2015-06-02 ...][woody] foo bar
```

## Installation

```bash
$ 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.

> :warning: 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:

```javascript
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:

```javascript
import woody from 'woody';

class Foo {
constructor(logger=woody.noop) {
logger.info('created');
}
}

class Application {
constructor() {
const logger = woody
.as(woody.bracketed())
.to(woody.console)
.fork('app');
logger.info('created');
const foo = new Foo(logger.fork('foo'));
}
}
```

Now:

```javascript
const app = new Application();
```

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.

```javascript
import woody from 'woody';

const logger = woody
.as(woody.bracketed())
.to(woody.console)
.fork(woody.level())
.if(woody.level.INFO);

logger.warn('foo') // => [WARN] foo
logger.info('foo') // => [INFO] foo
logger.debug('foo') // =>
logger.trace('foo') // =>
```

Culling works the same way `&&` would work, consider:

```javascript
import woody from 'woody';

let shouldlog = true;
const logger = woody
.as(woody.bracketed())
.to(woody.console)
.fork(woody.level())
.if(woody.level.INFO)
.if(() => shouldLog);

logger.warn('foo') // => [WARN] foo
logger.info('foo') // => [INFO] foo
logger.debug('foo') // =>
logger.trace('foo') // =>

shouldlog = false;

logger.warn('foo') // =>
logger.info('foo') // =>
logger.debug('foo') // =>
logger.trace('foo') // =>
```

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.

### Fallback to `noop`

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:

```javascript
// ES5
function foo(bar, logger) {
logger = logger || woody.noop;
logger.info('test');
}

// ES6+
function foo(bar, logger=woody.noop) {
logger.info('test');
}
```

> :warning: `noop` only means it does not render or commit anything.
> Sequencing it with another logger using `.sequence` will *not* return a `noop`
> logger, but a logger that applies both the `noop` 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:

```javascript
import fs from 'fs';
import woody from 'woody';

// open the file
const fd = fs.openSync('/var/log/my-log.log', 'w');
const logger = woody
.as(woody.bracketed())
.to((level, message) => {
// write to the file
try {
fs.writeSync(fd, `${level}: ${message}`);
} catch() { /* ... */ }
});
```

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.

### Integration with `debug`

There is out-of-the-box integration with the [debug](https://www.npmjs.com/package/debug)
package on npm:

```javascript
import woody from 'woody';

const debug = woody.debug().fork('woody')
, debugFoo = debug.fork('foo');

debug.log('foo');
debugFoo.log('qux');
debug.log('bar');
debugFoo.log('biz');

```

yields

> woody foo +0ms

> woody:foo qux +1ms

> woody bar +2ms

> woody:foo biz +3ms