npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

woody

v0.1.2

Published

Tiny logging combinator library for node

Downloads

25

Readme

npm version Build Status

Tiny logging combinator library for node and the browser

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

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

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:

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:

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.

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:

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:

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

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 package on npm:

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