New Case Study:See how Anthropic automated 95% of dependency reviews with Socket.Learn More
Socket
Sign inDemoInstall
Socket

logule

Package Overview
Dependencies
Maintainers
1
Versions
26
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

logule - npm Package Compare versions

Comparing version 0.9.1 to 1.0.0

bl.js

18

History.md

@@ -0,1 +1,19 @@

1.0.0 / 2012-09-29
==================
* explicit dependency injection replaced with internal bookkeeping, see readme for how to use this
**code must now initialize a logger after require**: `var log = require('logule').init(module)`
* `verify` method removed
* `suppress` renamed to `mute`
* `allow` renamed to `unmute`
* `muteOnly` method added
* `unmuteOnly` method added
* Config entry: `delimiter` should now include whitespace on both ends. I.e. '-' -> ' - '
* Config entry: `nesting` specifies the maximal nesting number allowed for namespaces.
* Config entry: `lineCol` to specify color of file:line prefix as it no longer uses namespaces (and therefore does not count towards `nesting` total). Defaulted it to green.
* Config option: `dateType = 'none'` now allowed to remove dates from output entirely
* Empty messages now remove the last delimiter
* config merger code is now in a separate file
0.9.1 / 2012-09-15

@@ -2,0 +20,0 @@ ==================

307

logule.js

@@ -1,133 +0,9 @@

var c = require('colors')
var set = require('subset')
, util = require('util')
, $ = require('autonomy')
, set = require('subset')
, semver = require('semver')
, util = require('util')
, fs = require('fs')
, path = require('path')
, version = require('./package').version
, defaults = require('./.logule')
, fallback = require('path').dirname(module.parent.filename)
, custom = require('confortable')('.logule', process.cwd(), fallback)
, cfg = require('./configure')
, basename = require('path').basename
, slice = Array.prototype.slice
, concat = Array.prototype.concat;
fs.existsSync || (fs.existsSync = path.existsSync);
var exists = function (file) {
return fs.existsSync(file) && !fs.statSync(file).isDirectory();
};
// Pads a str to a str of length len
var pad = function (str, len) {
return (str.length < len) ? str + Array(len - str.length + 1).join(' ') : str;
};
// obtained by self-executing config fn
var levels
, delimMap
, levelMap
, getDate
, prefixCol
, dateCol
, fileStream
, globallyOff;
(function () { // get config and precompute everything needed
var rawCfg = (custom) ? require(custom) : {};
// extend levels on inner levels => no method removals => DI works
var levObj = $.extend(defaults.levels, rawCfg.levels || {});
// remaining cfg elements can be read from after a merge
var cfg = $.extend(defaults, rawCfg);
// prepare for JSON streaming if requested in config
if (cfg.logFile) {
var logPath = path.join(path.dirname(custom), cfg.logFile);
fileStream = fs.createWriteStream(logPath, {flags: 'a'});
}
// cache color calls in delimMap/levelMap for _log
levels = Object.keys(levObj);
delimMap = levels.reduce(function (acc, lvl) {
var fn = c[levObj[lvl]];
if (!(fn instanceof Function)) {
console.error("invalid color function for level '" + lvl + "' found in " + custom);
}
acc[lvl] = fn(cfg.delimiter);
return acc;
}, {});
var max_lvl = set.maximum($.pluck('length', levels));
levelMap = levels.reduce(function (acc, lvl) {
var padded = pad(lvl === 'zalgo' ? cfg.zalgo : lvl.toUpperCase(), max_lvl);
acc[lvl] = (cfg.bold.indexOf(lvl) >= 0) ? c.bold(padded) : padded;
return acc;
}, {});
// misc colors
prefixCol = c[cfg.prefixCol]; // used by _log
dateCol = c[cfg.dateCol]; // used by _log
if (!(prefixCol instanceof Function)) {
console.error("invalid color function for prefixCol found in " + custom);
}
if (!(dateCol instanceof Function)) {
console.error("invalid color function for dateCol found in " + custom);
}
// prepad a number with zeroes so that it's n characters long
var prep = function (numb, n) {
return ("000" + numb).slice(-n); // works for n <= 3
};
// highly customizable, and efficient date formatting shortcut for _log
var f = cfg.formatting;
if (f.dateType === 'precision') {
getDate = function () {
var d = new Date();
return d.toLocaleTimeString() + '.' + prep(d.getMilliseconds(), 3);
};
}
else if (f.dateType === 'method') {
if (!(new Date())[f.dateMethod] instanceof Function) {
console.error("Logule found invalid dateMethod in " + custom);
}
getDate = function () {
return (new Date())[f.dateMethod]();
};
}
else if (f.dateType === 'custom') {
getDate = function () {
var n = new Date()
, d = '';
if (f.showDate) {
var da = [n.getFullYear(), prep(n.getMonth() + 1, 2), prep(n.getDate(), 2)];
if (f.reverseDate) {
da = da.reverse();
}
d = da.join(f.dateDelim) + ' ';
}
d += prep(n.getHours(), 2);
d += ':' + prep(n.getMinutes(), 2);
d += ':' + prep(n.getSeconds(), 2);
if (f.showMs) {
d += '.' + prep(n.getMilliseconds(), 3);
}
return d;
};
}
else if (f.dateType === 'plain') {
getDate = function () {
return (new Date()).toLocaleTimeString();
};
}
else {
console.error("Logule found invalid dateType in " + custom);
}
// global suppression
globallyOff = (cfg.useAllow) ? set.difference(levels, cfg.allow) : cfg.suppress;
}());
// callsite helper

@@ -147,13 +23,15 @@ var getStack = function () {

// Logger class
function Logger() {}
function Logger(id, isMain) {
this.id = id;
this.isMain = isMain;
}
// prototype defaults
var proto = {
version : version
, removed : []
, size : 0
removed : []
, namespaces : []
, id : '.'
};
// make undeletable instance defaults up the prototype chain
// make undeletable instance defaults at the bottom of the prototype chain
Object.keys(proto).forEach(function (key) {

@@ -168,28 +46,67 @@ Object.defineProperty(Logger.prototype, key, {

// store a process specific variable for logule to communicate across different copies
// communication happen only on frozen API parts
if (!process.logule) {
process.logule = {}
}
var moduleMaps = process.logule;
exports.init = function (parent, moduleName) {
if (!parent || !parent.id) {
var fail = new Logger('logule');
fail.error("invalid logule usage - .init() needs the modules `module` object");
return null;
}
var log = new Logger(parent.id, true);
// loop up the call graph to find previously used parameters
// best when graph == tree, else original parent is retrieved due to module caching
for (var m = parent; m ; m = m.parent) {
if (moduleMaps[m.id]) {
$.extend(log, moduleMaps[m.id]);
break;
}
}
if (moduleName) {
log.namespaces.push(moduleName);
}
moduleMaps[parent.id] = {
namespaces : log.namespaces
, removed : log.removed
};
return log;
};
// Logger base method
Logger.prototype._log = function (lvl) {
var args = arguments.length > 1 ? slice.call(arguments, 1) : []
, d = delimMap[lvl];
var args = arguments.length > 1 ? slice.call(arguments, 1) : [];
if (this.removed.indexOf(lvl) >= 0 || globallyOff.indexOf(lvl) >= 0) {
if (this.removed.indexOf(lvl) >= 0 || cfg.globallyOff.indexOf(lvl) >= 0) {
return this;
}
var date = getDate();
var padding = this.size;
var ns = this.namespaces.map(function (n, i) {
n = (i === 0) ? pad(n + '', padding) : n; // only pad first namespace level
return prefixCol(c.bold(n)) + " " + d;
});
var date = cfg.getDate()
, msg = util.format.apply(this, args)
, ns = this.namespaces.map(cfg.formatNamespace).slice(0, cfg.nesting)
, outputArray = [cfg.levelMap[lvl]].concat(ns);
var message = util.format.apply(this, args);
console.log.apply(console, [dateCol(date), d, levelMap[lvl], d].concat(ns, message));
if (msg !== '') {
outputArray.push(msg);
}
if (date) {
outputArray.unshift(cfg.dateCol(date));
}
fileStream && fileStream.write(JSON.stringify({
date : date
, level : lvl
, namespaces : this.namespaces
, message : message
}) + '\n');
console.log.call(console, outputArray.join(cfg.delimMap[lvl]));
if (cfg.fileStream) {
cfg.fileStream.write(JSON.stringify({
date : date
, level : lvl
, namespaces : this.namespaces
, message : msg
}) + '\n');
}
return this;

@@ -201,3 +118,3 @@ };

// Generate one helper method per specified level
levels.forEach(function (name) {
cfg.levels.forEach(function (name) {
if (name !== 'line' && name !== '_log') {

@@ -213,12 +130,10 @@ Logger.prototype[name] = function () {

var frame = getStack()[1];
this.namespaces.push(frame.getFileName() + ":" + frame.getLineNumber());
this._log.apply(this, concat.apply(['line'], arguments));
this.namespaces.pop();
var loc = basename(frame.getFileName()) + ':' + frame.getLineNumber();
this._log.apply(this, concat.apply(['line', cfg.lineCol(loc)], arguments));
return this;
};
// Return a single Logger helper method
Logger.prototype.get = function (fn) {
if (levels.indexOf(fn) < 0) {
if (cfg.levels.indexOf(fn) < 0) {
this.namespaces.push('logule');

@@ -228,29 +143,29 @@ this.error('Invalid Logule::get call for non-method: ' + fn);

}
else if (this.removed.indexOf(fn) < 0) {
var self = this
, l = this.sub();
else if (this.removed.indexOf(fn) >= 0 || cfg.globallyOff.indexOf(fn) >= 0) {
// level was muted locally or globally, result would just be an expensive noop
return $.noop;
}
if (fn === 'line') {
return function () {
self.line.apply(l, arguments);
};
}
var self = this
, sub = this.sub();
if (fn === 'line') {
return function () {
self._log.apply(l, concat.apply([fn], arguments));
self.line.apply(sub, arguments);
};
}
return $.noop;
return function () {
self._log.apply(sub, concat.apply([fn], arguments));
};
};
// Set the padding to size s
Logger.prototype.pad = function (s) {
this.size = s | 0;
return this;
};
// Suppress logs for specified levels
// Method is cumulative across new subsnamespaces/gets
Logger.prototype.suppress = function () {
// affects this logger and new loggers inheriting from parent call graph
Logger.prototype.mute = function () {
if (arguments.length) {
this.removed = set.union(this.removed, slice.call(arguments, 0));
// ensure subs / new inits cannot use these functions either
if (this.isMain) {
moduleMaps[this.id].removed = this.removed.slice();
}
}

@@ -260,8 +175,11 @@ return this;

// Allow logs for specific levels
// Method is cumulative across new subs/gets
Logger.prototype.allow = function () {
// Allow logs for specific levels for this logger only (does not override global mutes)
// affects this logger and new loggers inheriting from parent call graph
Logger.prototype.unmute = function () {
if (arguments.length) {
this.removed = set.difference(this.removed, slice.call(arguments, 0));
// allow subs / new inits to use these functions as well
if (this.isMain) {
moduleMaps[this.id].removed = this.removed.slice();
}
}

@@ -271,23 +189,30 @@ return this;

// Sets removed to (levels \ args)
Logger.prototype.unmuteOnly = function () {
this.removed = set.difference(cfg.levels, slice.call(arguments, 0));
if (this.isMain) {
moduleMaps[this.id].removed = this.removed.slice();
}
return this;
};
// Sets removed to (args)
Logger.prototype.muteOnly = function () {
this.removed = slice.call(arguments, 0);
if (this.isMain) {
moduleMaps[this.id].removed = this.removed.slice();
}
return this;
};
// Subclass from a pre-configured Logger class to get extra namespace(s)
// Since module level sub already called, this is for same module subs only now
Logger.prototype.sub = function () {
var sub = Object.create(this);
sub.namespaces = concat.apply(this.namespaces, arguments);
sub.isMain = false;
return sub;
};
// Verify that an instance is an up to date Logger instance
Logger.prototype.verify = function (inst) {
if (!inst || inst.constructor !== Logger) {
return false;
}
// inst.version only varies by patch number positively
return semver.satisfies(inst.constructor.prototype.version, "~" + version);
};
// Prevent hacky prototype modifications via l.constructor.prototype
Object.freeze(Logger.prototype);
// Expose a root instance of Logger
module.exports = new Logger();

@@ -5,3 +5,3 @@ {

"description": "An advanced console logging library",
"version": "0.9.1",
"version": "1.0.0",
"repository": {

@@ -22,4 +22,3 @@ "type": "git",

"subset": "~0.1.0",
"confortable": "~0.1.4",
"semver": "~1.0.13"
"confortable": "~0.1.4"
},

@@ -26,0 +25,0 @@ "devDependencies": {

# Logule [![Build Status](https://secure.travis-ci.org/clux/logule.png)](http://travis-ci.org/clux/logule)
Logule is a heavily configurable logging utility for nodejs. By default it prints only to stdout like `console.log`, but additionally it prefixes the current time, the log level, and optionally, prefixed namespaces (with optional padding). A log file can also be configured to stream JSON formatted log messages to a file for inspection of the raw data via short scripts.
Logule is a pretty, but heavily configurable logging utility for nodejs. It allows multiple transports (stdout + streaming JSON) as well as being configurable per user, per app and per module (with that priority) via recursively placed config files.
Shortcut methods for the log levels are available as (by default): `log.error`, `log.warn`, `log.info`, `log.debug`, `log.trace`, `log.zalgo`, and as a bonus, `log.line`. These methods are additionally chainable.
![simple output!](https://github.com/clux/logule/raw/master/imgs/outputsimple.png)
It favours a combination of Dependency Injection and config based control to allow for both tree-based log level filtration (via DI), and globally controllable log levels (via config files).
## Key Design Goal
Logging is a simple yet deceptively hairy problem. You don't want foreign modules to spam your app with needless messages, but you also don't want them to not say anything if you are passing bad stuff to them either. You want logs to look pretty, but you also want everyone else's logs to look like yours, and if that's not possible, you want to turn their logs off.
## Usage
Basic usage:
What you really want, is not simply configurability, but a *hierarchy of configurability and suppression*. You want to be able to:
````javascript
var logule = require('logule');
logule
- Mute specific log levels globablly
- Mute non-fatal info from certain branches of code
- Mute chatty modules
- Unmute new/experimental modules during development
as well as being able to configure *how* your:
- module logs by default
- app logs by default (perhaps overriding individual module defaults)
- apps log by default (by providing sensible overall configs)
Manipulating these settings should be super easy as it's most useful during development and debug sessions where time is of the essence.
Logule strives to adhere these goals and beyond that tries to maintain a stable API. Features so far has been greatly improved via issues/pull requests contributions, so please follow this path if there is anything you feel deserves attention.
## Index
* [Basic Usage](#basic-usage)
* [Namespaces](#namespaces)
* [Subs](#subs)
* [Configuration](#configuration)
* [Date Formatting](#date-formatting)
* [Changing Colors](#changing-colors)
* [Global Suppression](#global-suppression)
* [Stream JSON](#stream-json)
* [Instance Methods](#instance-methods)
* [Defaults](#defaults)
* [line()](#line)
* [zalgo()](#zalgo)
* [get()](#get)
* [mute()](#mute)
* [unmute()](#unmute)
* [muteOnly()](#muteonly)
* [unmuteOnly()](#unmuteonly)
* [Branch Based Filtration](#branch-based-filtration)
* [Filtering Branches](#filtering-branches)
* [Muting Chatty Modules](#muting-chatty-modules)
* [Unmuting New Modules](#unmuting-new-modules)
* [Installation](#installation)
* [Running Tests](#running-tests)
* [License](#license)
## Basic Usage
Require a logule instance for the current file and use it everywhere inside it.
````js
var log = require('logule').init(module);
log
.error("this is an error message")
.warn("warning")
.info("info msg")
.debug("chained debug");
.debug("chained %s", "debug");
````

@@ -24,6 +69,6 @@

## Namespaces
To add a namespace prefix, subclass logule with it:
To add a namespace to this module, add a second parameter to `init()`.
````javascript
log = logule.sub('BUILD');
````js
log = require('logule').init(module, 'BUILD');
log.trace("Trying to compile main.js");

@@ -36,179 +81,215 @@ log.error("Failed");

### Multiple Namespaces
Pass in more strings to get more namespaces prefixed
Namespaces nest and are assigned in the order of registration (`init()` calls) to match the call tree. See [Filtering Branches](#filtering-branches) for an example.
````javascript
var log = logule.sub('BUILD', 'COMPILE');
log.debug('log has two prefixes');
## Subs
Sometimes you want to create namespaces a little more granularly, perhaps you would like to dependency inject a sandboxed version of your logger to an internal or external class. Well, this is easy:
````js
var log = require('logule').init(module, 'myFile');
var sandboxed = log.sub('CrazyClass').mute('debug');
// pass sandboxed logger to CrazyClass
````
### Namespace Padding
Call `.pad(size)` on a logger instance to specify a fixed indentation level for each namespace.
A `log.sub()` will maintain the the default namespaces and mute settings of `log`. It can also optionally append one extra namespace to the ones existing, in this case, 'CrazyClass' will be appended.
````javascript
log.pad(16);
log.warn('my namespaces are padded');
````
Since the output of `var log = require('logule').init(module)`, `log.sub()` and `log.sub().sub()` (etc) all act similarly and on the same API, the variable `log` will in this document be used to refer to a logger instance that came from any of these origins.
Messages will here begin `(16 + delimiter_size)*num_namespaces` characters out.
Large namespaces (>specified size), will stand out from the crowd.
## Configuration
Rich configuration of colors, style, date formatting and global muting of certain log levels are all available via config files. The [default configuration file](https://github.com/clux/logule/blob/master/.logule) (which *contains documentation*) results in output looking like the images herein.
## Line
An awesome feature inspired by [nlogger](https://github.com/igo/nlogger) - but using logule
semantics; `logule.line()` reads the line and filename of the calling function
by directly inspecting the stack.
Configs are located via [confortable](https://github.com/clux/confortable). Which is a module that performs priority based config searches. In particular, it is used here with the following path priorities:
````javascript
log = logule.sub('CRAZYDEBUG');
log.debug('dumping lines to console');
log.line('who called me?');
log.line('and now?');
```
- 1. execution directory
- 2a). if (`execDir` outside `$HOME`) `$HOME`
- 2b). if (`execDir` inside `$HOME`) Up to and including `$HOME` in `..` increments
- 3. directory of `module.parent`
![line output!](https://github.com/clux/logule/raw/master/imgs/line.png)
Step 3 enables modules to bundle their own default config which can be overriden by apps by utilizing step 2.
## Passing log around
### Dependency Injection
#### Subclasses
A good use of `.sub()` involve inheriting based on namespaces, and linking
instances together.
The found config file is merged carefully with the default config, so you don't have to include more in your config than you disagree with. Also note you cannot remove the default log levels (lest we break dependency injection).
````javascript
var log = logule.sub('BUILD');
var sublog = log.sub('COMPILE');
````
### Date Formatting
How or if to prepend the date has been the most controversial choice previously made for you in early versions of logule. Those days are now gone, however, and multiple different date formatting types exist.
Here `sublog` would provide same output as `logule.sub('BUILD', 'COMPILE')`.
- `plain` -> prepends HH:MM:SS + delimiter via `toLocaleTimeString`
- `precision` -> prepends HH:MM:SS:MSS + delimiter via above + padded `getMilliseconds`
- `method` -> prepends the result of any custom method on `Date.prototype`
- `none` -> Nothing prepended. Log output starts at type, e.g. the `INFO` part.
- `custom` -> Allows four extra settings.
It is advantageous to do 'one namespace sub at a time', as then it is easier
to filter log output from large chunks of code at a time,
as well as maintaining a sensible log hierarchy.
If `custom` set, you can also prepend the date to either `plain` or `precision`, i.e. prepend YYYY-MM-DD, possibly reversing it if you're american, and possibly changing the delimiter.
A `log.sub()` maintains all padding, suppressed log levels, its locked status,
and namespace properties set on the original `log` instance. Therefore, it works as if there's
an implicit link between the sub and its parent.
### Changing Colors
The following options affect output colors:
#### Suppress
Suppressing logs from an instance is done in a very neat, propagating,
and non-breaking way. `.suppress(methods...)` suppresses output
from specified methods, but still allows them to be called, and they still chain.
- `prefixCol` - namespace
- `dateCol` time and date
- `lineCol` location in .line()
````javascript
log.suppress('debug', 'info');
log.warn('works').info('suppressed').error('works').debug('suppressed');
Additionally `levels` define the color of the delimiter in each log method.
Every string used to describe colors must be exported by the `colors` module to work.
### Global Suppression
Set the `suppress` flag to globally turn all listed log methods into chaining no-ops.
Alternatively list the exceptions under `allow` instead and set `useAllow` to `true`.
See the [Branch based filtration](#branch-based-filtration) section for more granular control.
### Stream JSON
If `logFile` is filled in, this file will be appended to with JSON log messages (one message per line). Thus, you can read the file and split by newline, or watch the file and emit/filter based on each JSON line you receive.
The individual JSON messages use the current format:
````js
{
"date": "08:14:11",
"level": "error",
"namespaces": ["build"],
"message": "message part, how it appeared in terminal"
}
````
All subclasses subsequently created from a suppressed instance,
will also be suppressed. To unsuppress, use `.allow()`.
## Instance Methods
### Defaults
The methods available on a logger instance are: `trace`, `debug`, `info`, `line`, `warn`, `error`, and `zalgo`. They only vary their delimiter color and some might be boldened depending on the config setting.
#### Allow
Allows modules down in the hierarchy to log things that have been suppressed
by supers. This only works if global log levels have not been enforced.
The mystical `zalgo` and `line` provide some specialized logic however:
````javascript
log.suppress('debug', 'info');
var l2 = log.sub('forModuleX');
l2.allow('debug');
l2.debug('works!')
#### line()
Line is prepends the filename and line of caller (as a namespace). It fetches this info from the stack directly.
````js
var log = require('logule').init(module, 'broken');
log.debug('dumping lines to console');
log.line();
log.line();
```
![line output!](https://github.com/clux/logule/raw/master/imgs/line.png)
#### zalgo()
H̸̡̪̯ͨ͊̽̅̾̎Ȩ̬̩̾͛ͪ̈́̀́͘ ̶̧̨̱̹̭̯ͧ̾ͬC̷̙̲̝͖ͭ̏ͥͮ͟Oͮ͏̮̪̝͍M̲̖͊̒ͪͩͬ̚̚͜Ȇ̴̟̟͙̞ͩ͌͝S̨̥̫͎̭ͯ̿̔̀ͅ
````js
log.zalgo('core melting')
````
#### Get Method
A debug module should only need `log.debug`. You can save typing,
and enforce this behaviour by calling `.get('debug')` on an instance,
to return the correctly bound instance method to pass down.
#### get()
A debug module may only need `log.debug`. You can save typing, and enforce this behaviour by calling `.get('debug')` on an instance, to return the correctly bound instance method to pass down.
````javascript
````js
var dbg = log.get('debug');
dbg("works like log.debug - nothing else accessible through this var");
dbg("works like log.debug - but nothing else accessible via this non-chainging var");
````
Note that if `log` have called `.suppress('debug')` earlier - or if it is a `.sub()`
of an instance that have called `.suppress('debug')`, then you would only get
a suppressed function from `.get('debug')`.
Note that if `log` have muted or suppressed (i.e. in the config) debug - then you would only get a noop from `.get('debug')`.
### Tree Based Log Levels
By only using `.sub()` instances inheriting from a single base instance,
you can implement tree based log levels at start time by calling
`.suppress()` and `.allow()` on the base instance - or any branch point
you would like.
#### mute()
Suppress logs for passed in methods.
````javascript
var log = logule.sub('APP');
//log.suppress('info','debug'); // uncomment to globally suppress
````js
log.mute('debug', 'info');
log.warn('works').info('muted').error('works').debug('muted');
````
var modelsLog = log.sub('MODEL'); // pass this to models
//modelsLog.suppress('warn'); // uncomment to suppress warnings below
#### unmute()
Unmutes logs for passed in methods.
var eventsLog = modelsLog.sub('EVENT'); // pass this down from models to events
//eventsLog.allow('debug'); // uncomment to temporarily allow debugs in this module
````js
log.mute('debug', 'info');
var l2 = log.sub('forModuleX').unmute('debug');
l2.debug('works!');
log.debug('muted');
````
Tree based log levels is the safe, overridable version of log levels.
To strictly enforce suppression of certain levels, use config files.
#### muteOnly()
A convenience for muting all levels passed in, and unmuting all others.
## Configuration
Since logule >= 0.7, rich configuration of colors, style, date formatting and global suppression of certain log levels is available. The [default configuration file](https://github.com/clux/logule/blob/master/.logule) results in output looking like the older versions.
````js
log.muteOnly('debug', 'trace'); // only trace & debug muted
log.muteOnly(); // muteOnly nothing === unmute everything
````
When starting a node script requiring logule, logule will search from the execution directory for a `.logule` file. If that fails, it will keep searching one directory up until $HOME is hit.
#### unmuteOnly()
A convenience for unmuting all levels passed in, and muting the others.
If no config is found, one final search is done in the parent's (the module that requires logule) directory, and the resulting config is merged carefully with the default one bundled with logule.
````js
log.unmuteOnly('error'); // only errors unmuted
log.unmuteOnly(); // unmuteOnly nothing === mute everything
````
### Stream JSON
If `logFile` is set in `.logule`, this file will be appended to with JSON log messages (one message per line). Thus, you can read the file and split by newline, or watch the file and emit/filter based on each JSON line you receive.
### Branch Based Filtration
Controlling global levels is done via config files, but the levels not globally suppressed therein can temporarily muted/unmuted at any branch point and these settings will propagate down the call tree.
The individual JSON messages use the current format (here prettified):
````javascript
{
"date": "08:14:11",
"level": "error",
"namespaces": ["build"],
"message": "message part, how it appeared in terminal"
}
#### Filtering Branches
The examples for mute/unmute only shows the basic API for using subs. You do not have to create subs and pass them down via dependency injection. You can of course do this, but if you write short modules, it's generally easier to let `init()` do the heavy lifting.
To get the most out of call tree filtration consider the following example of an application structure:
````
a.js
└──┬b.js
└───c.js
````
### Config Ideas
#### Custom Prototype Log Methods
Config files can fully reconfigure/add new log methods with your own names. The prototype methods created will be directly taken from the level object in the config file, and these will log with the specified color and with the same (upper cased in print) level. Note that you can not remove the original methods (only suppress them) as to do so would break DI.
When just using mute/unmute on an instance returned directly by `init()` logule will remember the call tree and apply the same rules to the ones further down the tree by default:
Note that `line` which will additionally include the file and line of callsite when used, and `zalgo` will have some idiosyncratic formatting.
````js
// a.js
var l = require('logule').init(module, 'app').mute('debug');
var b = require('./b');
#### Global Filtration
Set the `suppress` flag to globally turn all listed log methods into chaining no-ops.
If most methods listed should be disabled, quickly list the exceptions under the `allow` flag and set `useAllow` to `true`.
// b.js
var l = require('logule').init(module);
var c = require('./c');
l.debug('muted');
l.unmute('debug');
l.debug('works');
// c.js
var l = require('logule').init(module, 'leaf');
l.debug('works');
````
## Verifying Logule Validity
When passing logule subs around, it might be useful for separate code to test
whether what is received is an appropriate Logule instance or not.
Unfortunately, instanceof testing against your installed logule will only work
when your code is not separated into modules.
With the following code, `a.js` sets the app default of _no debug logging_, which is overridden by `b.js`, and propagates to `c.js`. Note that the `app` namespace set in `a.js` propagates down to both `b.js` and `c.js`, but `c.js` will show two namespaces: `app` and `leaf` provided the config setting `nesting >= 2`.
Therefore, to support npm module style where there are possibly multiple installations
of logule spread around, the module can test that the one passed in,
has a version compatible with the module's own using a built in helper function.
Note that any mute/unmute calls to a `sub()` does not propagate to other files:
````javascript
var logule = require('logule');
function (injectedLogule) {
if (logule.verify(injectedLogule)) {
// injectedLogule exists, and its version is ~ to the module's own
} else {
// injectedLogule invalid or out of date: use logule
}
}
````js
// a.js as above
// b.js
var l = require('logule').init(module).sub().unmute('debug');
l.debug('works');
// c.js
var l = require('logule').init(module);
l.debug('still muted');
````
Note that single functions like `logule.get('info')` will of course not pass this test.
If your API expects a single logger function, then
you should simply type test the input as a function.
In short tree based log levels is the safe, *overridable version* of log levels.
To enforce strict suppression of certain levels, the config file is the way to go.
#### Muting Chatty Modules
Say you want to mute warnings in the file `c.js` above. If you own the file, you easily just edit the first line to be:
## Zalgo
H̸̡̪̯ͨ͊̽̅̾̎Ȩ̬̩̾͛ͪ̈́̀́͘ ̶̧̨̱̹̭̯ͧ̾ͬC̷̙̲̝͖ͭ̏ͥͮ͟Oͮ͏̮̪̝͍M̲̖͊̒ͪͩͬ̚̚͜Ȇ̴̟̟͙̞ͩ͌͝S̨̥̫͎̭ͯ̿̔̀ͅ
````js
// c.js
var l = require('logule').init(module).mute('warn');
````
````javascript
log.zalgo("all is lost");
However, if you don't own the file, perhaps it's deep down in the npm hierarchy for instance, you can propagate more simply from `b.js`.
````js
// b.js
var l = require('logule').init(module).mute('warn').sub().unmute('warn');
var c = require('./c');
l.debug('muted');
l.warn('unmuted, but down the call tree it is muted');
````
Here we mute the main logger from `b.js` (the one from `init`), but unmute warnings to a `sub` that will be used inside this file to preserve the same behaviour inside `b.js` only.
#### Unmuting New Modules
Essentially the inverse of [Muting chatty modules](#muting-chatty-modules), here we unmute one file above or in the file itself if we own it.
## Installation

@@ -215,0 +296,0 @@

@@ -1,7 +0,7 @@

var logule = require('../')
var logule = require('../').init(module)
, test = require('tap').test
, levels = ['trace', 'debug', 'info', 'warn', 'error', 'zalgo', 'line']
, pubs = ['get', 'suppress', 'allow', 'sub', 'pad', 'verify']
, pubs = ['get', 'mute', 'unmute', 'muteOnly', 'unmuteOnly', 'sub']
, l = logule.sub('suppressed');
l.suppress.apply(l, levels); // l is always suppressed
l.mute.apply(l, levels); // l is always suppressed

@@ -41,23 +41,2 @@ test("chaining", function (t) {

test("verify", function (t) {
t.ok(logule.verify(logule), "logule.verify(logule) is logule");
t.ok(!(logule.verify(logule.get('info'))), "logule.verify(logule.get('info')) is false");
t.ok(logule.sub('arst').verify(logule), "logule.sub('arst').verify(logule) is true");
t.ok(logule.verify(logule.sub('arst')), "logule.verify(logule.sub('arst')) is true");
t.ok(logule.verify(l), "logule.verify(l)");
t.ok(logule.verify(l.sub('arst')), "logule.verify(l.sub('arst'))");
t.ok(l.verify(logule), "l.verify(logule)");
t.ok(l.sub('arst').verify(logule), "l.sub('arst').verify(logule)");
t.ok(!l.verify(), "!l.verify()");
t.ok(!l.verify(null), "!l.verify(null)");
t.ok(!l.verify({}), "!l.verify({})");
t.ok(!l.verify({data:{version:null}}), "!l.verify(fakeObj)");
levels.forEach(function (lvl) {
t.ok(l.get(lvl) instanceof Function, "l.get('" + lvl + "') returns a function");
});
t.end();
});
test("subs", function (t) {

@@ -68,2 +47,1 @@ t.ok(logule === logule, "obvious test");

});

@@ -1,4 +0,5 @@

var logule = require('../')
var logule = require('../').init(module)
, test = require('tap').test
, levels = ['trace', 'debug', 'info', 'warn', 'error', 'zalgo', 'line']
, stderrs = ['error', 'warn', 'zalgo']
, log = logule.sub('LOGULE').get('info')

@@ -8,12 +9,15 @@ , testMsg = "this is a test message"

, l = logule.sub('suppressed');
l.suppress.apply(l, levels);
l.mute.apply(l, levels);
// monkey-patch process.stdout.write to intercept console.log calls
var hook = function (cb) {
var write = process.stdout.write;
var writeStdOut = process.stdout.write;
//var writeStdErr = process.stderr.write;
process.stdout.write = cb;
//process.stderr.write = cb;
// return an undo damage fn returned
return function () {
process.stdout.write = write;
process.stdout.write = writeStdOut;
//process.stderr.write = writeStdErr;
};

@@ -24,2 +28,15 @@ };

test("stdout", function (t) {
var hook = function (cb) {
var writeStdOut = process.stdout.write;
var writeStdErr = process.stderr.write;
process.stdout.write = cb;
process.stderr.write = cb;
// return an undo damage fn returned
return function () {
process.stdout.write = writeStdOut;
process.stderr.write = writeStdErr;
};
};
var stdlog = logule.sub('STDOUT')

@@ -67,3 +84,3 @@ , output = [];

levels.forEach(function (lvl) {
var stdsub = stdlog.sub().suppress(lvl)
var stdsub = stdlog.sub().mute(lvl)
, single = stdsub.get(lvl);

@@ -78,3 +95,3 @@ stdsub[lvl](testMsg);

levels.forEach(function (lvl) {
var stdsub = stdlog.sub('THIS_DIES').suppress(lvl);
var stdsub = stdlog.sub('THIS_DIES').mute(lvl);
stdsub.sub('SUBSUB')[lvl](testMsg);

@@ -88,7 +105,7 @@ t.equal(oldmsg, last(), "sub()." + lvl + " does not send to stdout when parent was suppressed");

levels.forEach(function (lvl) {
var stdsub = stdlog.sub('supandallow').suppress(lvl)
var stdsub = stdlog.sub('supandallow').mute(lvl)
, lastOut = last();
stdsub[lvl]('i am suppressed');
t.equal(lastOut, last(), "suppresed message ignored for " + lvl);
stdsub.allow(lvl);
stdsub.unmute(lvl);
stdsub[lvl]('i am resurrected');

@@ -99,5 +116,5 @@ t.ok(lastOut !== last(), "ressurrected method logs " + lvl + "again");

// suppressing a level does not affect other levels
// muting a level does not affect other levels
levels.forEach(function (lvl) {
var stdsub = stdlog.sub('SEMI').suppress(lvl);
var stdsub = stdlog.sub('SEMI').mute(lvl);

@@ -168,3 +185,3 @@ // via normal approach

}
var stdsub = stdlog.sub('chainer').suppress(lvl)
var stdsub = stdlog.sub('chainer').mute(lvl)
, oldsize = output.length;

@@ -194,2 +211,48 @@ stdsub[lvl]('suppressed message').info('working message')[lvl]('another suppressed');

// unmuteOnly affects only complement
levels.forEach(function (lvl) {
var stdsub = stdlog.sub('BLAH').unmuteOnly(lvl);
var oldsize = output.length
, include = (lvl === 'zalgo') ? zalgo : lvl.toUpperCase();
stdsub[lvl](testMsg);
t.ok(lastIncludes("BLAH"), "unmuteOnly " + lvl + " does not mute self");
t.ok(lastIncludes(testMsg), "unmuteOnly " + lvl + " msg to self contains testMsg");
t.ok(lastIncludes(include), "unmuteOnly " + lvl + " msg includes level");
t.equal(oldsize + 1, output.length, "hook pushed a str onto the output array (semi suppress)");
// but all others muted
levels.forEach(function (lvl2) {
if (lvl2 === lvl) {
return;
}
var oldsize = output.length
stdsub[lvl2](testMsg);
t.equal(oldsize, output.length, "hook did not push a string onto the output array");
});
});
// muteOnly mutes everything but level
levels.forEach(function (lvl) {
var stdsub = stdlog.sub('BLOH').muteOnly(lvl);
var oldsize = output.length
stdsub[lvl](testMsg);
t.equal(oldsize, output.length, "hook did not push a string onto the output array");
// but all others muted
levels.forEach(function (lvl2) {
if (lvl2 === lvl) {
return;
}
var oldsize = output.length
, include = (lvl2 === 'zalgo') ? zalgo : lvl2.toUpperCase();
stdsub[lvl2](testMsg);
t.ok(lastIncludes("BLOH"), "muteOnly " + lvl + " does not mute " + lvl2);
t.ok(lastIncludes(testMsg), "muteOnly " + lvl + " msg to self contains testMsg");
t.ok(lastIncludes(include), "muteOnly " + lvl + " msg includes level");
t.equal(oldsize + 1, output.length, "hook pushed a str onto the output array (semi suppress)");
});
});
unhook();

@@ -199,1 +262,44 @@ t.end();

/*test("stderr", function (t) {
var hook = function (cb) {
var writeStdErr = process.stderr.write;
process.stderr.write = cb;
// return an undo damage fn returned
return function () {
process.stderr.write = writeStdErr;
};
};
var stdlog = logule.sub('STDERR')
, output = [""];
var unhook = hook(function (str, enc, fd) {
output.push(str);
});
var last = function () {
return output[output.length - 1];
};
var lastIncludes = function (x) {
return last().indexOf(x) >= 0;
};
levels.forEach(function (lvl) {
if (stderrs.indexOf(lvl) >= 0) {
stdlog[lvl]("histderr");
t.ok(lastInclude("histderr"), "stderr log works")
var include = (lvl === 'zalgo') ? zalgo : lvl.toUpperCase();
t.ok(lastIncludes(include), "stderr test includes lvl type");
}
else {
stdlog[lvl]("nothere");
t.ok(!lastIncludes("nothere"), "stdlog should not log to stderr for this level");
}
});
unhook();
t.end();
});
*/

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc