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 1.1.0 to 2.0.0

.logule.json

18

History.md

@@ -0,8 +1,20 @@

2.0.0 / 2012-11-14
==================
* config rewritten nicely in JSON and must have the `.json` extension
* if you rely on modules using the older config files leave your old config intact until they upgrade (all my modules will be updatet within the hour)
* rely on `peerDependencies` in logule-requiring modules rather than try to communicate across compatible copies of logule - this **removes global usage by forcing the user to decide**
* the new `.logule.json` is mostly similar, but some changes are required - the changes are mostly clear from the new default config, but for the new/unobvious attributes, look them up in the new readme
* expose an `EventEmitter` as the third optional transport for people who would like full control (and are stuck with logule because of module deps, but would like to use something else)
* all transports are muted/suppressed individually now!
* Namespaces can now be disabled with `nesting` set to zero
* Fixed Issue 13: line logs with colored strings to file
* Fixed Issue 14: .get() did not log to file if level was muted.
* No double `util.format.apply` via `console.log` by writing directly to `process.stdout`
1.1.0 / 2012-10-06
==================
Modules should not say anything when it doesn't need to. This discourages the use of log.trace
in free standing modules, which is bad, information is a good thing to have. Solution:
Modules should not say anything when it doesn't need to. This discourages the use of log.trace in free standing modules, which is bad, information is a good thing to have. Solution:
* `trace` and `debug` logs are by suppressed in the default config to allow probing of modules that use logule, but still not spam `stdout` by default
* `colors` dependency removed. Those who were using the accessors it added to `String.prototype` must require it separately, this behaviour spread implicit dependencies and was not intended.
* fix a bug where values on `process.logule` were being overwritten due to improper serialization
* fix a critical bug where values on `process.logule` were being overwritten due to improper serialization (deprecates 1.0.X)

@@ -9,0 +21,0 @@ 1.0.1 / 2012-10-02

277

lib/configure.js
var fs = require('fs')
, path = require('path')
, events = require('events')
, dye = require('dye')
, set = require('subset')
, $ = require('autonomy')
, defaults = require('../.logule')
, t = require('typr')
, fallback = require('path').dirname(module.parent.filename)
, custom = require('confortable')('.logule', process.cwd(), fallback);
, cfgPath = require('confortable')('.logule.json', process.cwd(), fallback)
, C = {}; // exports object
fs.existsSync || (fs.existsSync = path.existsSync);
var exists = function (file) {
return fs.existsSync(file) && !fs.statSync(file).isDirectory();
};
// log levels locked down
C.levels = ["trace", "debug", "line", "info", "warn", "error", "zalgo"];
var rawCfg = (custom) ? require(custom) : {};
// find and merge the the custom config (if exists) with the default one
var mergeCfgs = function () {
var def = require('../.logule.json') // can't mess this one up
, cfg = {};
// ensure levels are always the ones defined in the defaults
var levObj = Object.keys(rawCfg.levels || {}).reduce(function (acc, key) {
if (key in defaults.levels) {
acc[key] = rawCfg.levels[key];
if (cfgPath) {
cfg = fs.readFileSync(cfgPath);
try {
cfg = JSON.parse(cfg);
}
catch (e) {
console.error("invalid JSON in custom logule config: %s", cfgPath);
throw e;
}
}
else {
console.error("cannot introduce new methods in " + custom);
}
return acc;
}, defaults.levels);
// remaining cfg elements can be read from after a merge
var cfg = $.extend(defaults, rawCfg);
// merge 1 level deep in config to ensure we have sensible defaults
Object.keys(cfg).forEach(function (sub) {
if (t.hasKeys(cfg[sub]) && t.hasKeys(def[sub])) {
$.extend(def[sub], cfg[sub]);
}
});
return def;
};
var cfg = mergeCfgs();
// misc colors
var prefixCol = dye[cfg.prefixCol];
var dateCol = dye[cfg.dateCol];
var lineCol = dye[cfg.lineCol];
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);
}
if (!(lineCol instanceof Function)) {
console.error("invalid color function for lineCol found in " + custom);
}
// line helper
var getStack = function () {
var orig = Error.prepareStackTrace;
Error.prepareStackTrace = function (err, stack) {
return stack;
};
var err = new Error();
Error.captureStackTrace(err, arguments.callee);
var stack = err.stack;
Error.prepareStackTrace = orig;
return stack;
};
// 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
C.line = function () {
// 3 frame diff now: {0: this, 1: _log, 2: line(), 3: actual callsite}
var frame = getStack()[3];
return path.basename(frame.getFileName()) + ':' + frame.getLineNumber();
};
// highly customizable, and efficient date formatting shortcut for _log
var f = cfg.formatting;
var getDate = $.noop;
if (f.dateType === 'precision') {
getDate = function () {
var d = new Date();
return d.toLocaleTimeString() + '.' + prep(d.getMilliseconds(), 3);
};
}
else if (f.dateType === 'none') {
getDate = $.noop;
}
else if (f.dateType === 'method') {
if (!(new Date())[f.dateMethod] instanceof Function) {
console.error("Logule found invalid dateMethod in " + custom);
// timestamp formatting
// helper -ensure n has at least d digits by padding zeroes in front
var prep = function (n, d) {
return ("000" + n).slice(-d); // works for 3 digits
};
var makeStamper = function (ts) {
if (Object(ts) === ts) {
return function () {
var n = new Date()
, d = '';
if (ts.date) {
var da = [n.getFullYear(), prep(n.getMonth() + 1, 2), prep(n.getDate(), 2)];
if (ts.reverse) {
da = da.reverse();
}
d = da.join(ts.delimiter) + ' ';
}
d += prep(n.getHours(), 2);
d += ':' + prep(n.getMinutes(), 2);
d += ':' + prep(n.getSeconds(), 2);
if (ts.precise) {
d += '.' + prep(n.getMilliseconds(), 3);
}
return d;
};
}
getDate = function () {
return (new Date())[f.dateMethod]();
if (ts === 'precision') {
return function () {
var d = new Date();
return d.toLocaleTimeString() + '.' + prep(d.getMilliseconds(), 3);
};
}
if (ts === 'none') {
return $.noop;
}
// last chance, must be Date member fn
if (!(new Date())[ts] instanceof Function) {
throw new Error(cfgPath + ": illegal timestamp option " + ts);
}
return function () {
return (new Date())[ts]();
};
};
// fs WriteStream
if (cfg.filestream && cfg.filestream.file) {
C.filestamp = makeStamper(cfg.filestream.timestamp);
var logPath = path.join(path.dirname(cfgPath), cfg.filestream.file);
C.filestream = fs.createWriteStream(logPath, {flags: 'a'});
C.filesuppressed = cfg.filestream.allow ?
set.difference(C.levels, cfg.filestream.allow) :
cfg.filestream.suppress;
C.filemutable = cfg.filestream.mutable;
}
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;
};
// EventEmitter
if (cfg.emitter && cfg.emitter.enabled) {
C.emitter = new events.EventEmitter();
C.emitsuppressed = cfg.emitter.allow ?
set.difference(C.levels, cfg.emitter.allow) :
cfg.emitter.suppress;
C.emitmutable = cfg.emitter.mutable;
}
else if (f.dateType === 'plain') {
getDate = function () {
return (new Date()).toLocaleTimeString();
};
}
else {
console.error("Logule found invalid dateType in " + custom);
}
// cache color calls in delimMap/levelMap for _log
var levels = Object.keys(levObj);
var max_lvl = set.maximum($.pluck('length', levels));
// stdout
// how much we pad namespaces to - max level length of levels
var maxLvl = set.maximum($.pluck('length', C.levels));
// Pads a str to a str of length len
var pad = function (str, len) {
return (str.length < len) ? str + new Array(len - str.length + 1).join(' ') : str;
if (str.length >= len) {
return str;
}
return str + new Array(len - str.length + 1).join(' ');
};
// exports
exports.levels = levels;
exports.delimMap = levels.reduce(function (acc, lvl) {
var fn = dye[levObj[lvl]];
if (!(fn instanceof Function)) {
console.error("invalid color function for level '" + lvl + "' found in " + custom);
}
acc[lvl] = fn(cfg.delimiter);
// allow fn1.fn2 syntax in config, but ensure every fn in split('.') is on dye
var getDyes = function (str, lvl) {
return (str || 'none').split('.').map(function (fn) {
if (fn === 'none') {
return $.id; // allow plain output
}
if (!(dye[fn] instanceof Function)) {
throw new Error(cfgPath + ": illegal " + lvl + " color: " + fn);
}
return dye[fn];
});
};
// verify colors are functions in dye
C.colors = ['namespace', 'timestamp', 'callsite'].reduce(function (acc, lvl) {
var fns = getDyes(cfg.colors[lvl], lvl);
acc[lvl] = $.apply(null, fns);
return acc;
}, {});
exports.levelMap = levels.reduce(function (acc, lvl) {
var padded = pad(lvl.toUpperCase(), max_lvl);
acc[lvl] = (cfg.bold.indexOf(lvl) >= 0) ? dye.bold(padded) : padded;
// delims are pre-executed on stdout.delimiter so logule.js can [].join with them
C.colors.delim = C.levels.reduce(function (acc, lvl) {
var fns = getDyes(cfg.delimiters[lvl], lvl);
acc[lvl] = $.apply(null, fns)(cfg.stdout.delimiter);
return acc;
}, {});
exports.getDate = getDate;
exports.dateCol = dateCol;
exports.lineCol = lineCol;
// levels are pre-executed on upper case level names (but padded to maxLvl)
C.colors.levels = C.levels.reduce(function (acc, lvl) {
var padded = pad(lvl.toUpperCase(), maxLvl);
var fns = getDyes(cfg.levels[lvl], lvl);
acc[lvl] = $.apply(null, fns)(padded);
return acc;
}, {});
// applies cfg.pad (for el zero iff set) and cfg.prefixCol
var padding = cfg.pad | 0;
exports.formatNamespace = function (n, i) {
n = (i === 0) ? pad(n + '', padding) : n;
return prefixCol(dye.bold(n));
// messages can also be styled for those craving full customization
C.colors.msg = C.levels.reduce(function (acc, lvl) {
var fns = getDyes(cfg.messages[lvl], lvl);
acc[lvl] = $.apply(null, fns);
return acc;
}, {});
// format the up to cfg.nesting leading namespaces, padding the first if set
var nsPad = cfg.stdout.pad | 0;
C.nsFormat = function (ns) {
var res = []
, len = Math.min(ns.length, cfg.stdout.nesting | 0);
for (var i = 0; i < len; i += 1) {
var n = ns[i];
if (i === 0 && nsPad) {
n = pad(n, nsPad);
}
res.push(C.colors.namespace(n));
}
return res;
};
if (cfg.logFile) {
var logPath = path.join(path.dirname(custom), cfg.logFile);
exports.fileStream = fs.createWriteStream(logPath, {flags: 'a'});
}
C.timestamp = makeStamper(cfg.stdout.timestamp);
C.suppressed = cfg.stdout.allow ?
set.difference(C.levels, cfg.stdout.allow) :
cfg.stdout.suppress;
C.stdoutmutable = cfg.stdout.mutable;
exports.nesting = cfg.nesting | 0;
exports.globallyOff = (cfg.useAllow) ? set.difference(levels, cfg.allow) : cfg.suppress;
module.exports = C;

@@ -5,47 +5,18 @@ var set = require('subset')

, cfg = require('./configure')
, zalgify = require('dye').zalgo
, basename = require('path').basename
, slice = Array.prototype.slice;
, col = cfg.colors
, slice = Array.prototype.slice
, moduleMaps = {};
// callsite helper
var getStack = function () {
var orig = Error.prepareStackTrace;
Error.prepareStackTrace = function (err, stack) {
return stack;
};
var err = new Error();
Error.captureStackTrace(err, arguments.callee);
var stack = err.stack;
Error.prepareStackTrace = orig;
return stack;
};
// Logger class
function Logger(id, isMain) {
this.id = id;
this.isMain = isMain;
}
// prototype defaults
var proto = {
removed : []
, namespaces : []
, id : '.'
};
// make undeletable instance defaults at the bottom of the prototype chain
Object.keys(proto).forEach(function (key) {
Object.defineProperty(Logger.prototype, key, {
value : proto[key]
, writable : false
, enumerable : false
, configurable : false
function Logger(opts) {
var self = this;
Object.keys(opts).forEach(function (key) {
Object.defineProperty(self, key, {
value: opts[key],
enumerable: false,
writable: true,
configurable: true
});
});
});
// store a process specific variable for logule to communicate across different copies
if (!process.logule) {
process.logule = {};
}
var moduleMaps = process.logule;

@@ -59,31 +30,70 @@ // When muting/unmuting, make sure the changes inherit

// Main logger function
Logger.prototype._log = function (lvl, msg) {
if (this.removed.indexOf(lvl) >= 0 || cfg.globallyOff.indexOf(lvl) >= 0) {
return this;
// The actual functions writing to stdout/file or emitting 'log' events
var toStdout = function (lvl, msg, ns) {
var output = cfg.nsFormat(ns);
output.unshift(col.levels[lvl]);
var ts = cfg.timestamp();
if (ts) {
output.unshift(col.timestamp(ts));
}
var date = cfg.getDate()
, ns = this.namespaces.map(cfg.formatNamespace).slice(0, cfg.nesting)
, outputArray = [cfg.levelMap[lvl]].concat(ns);
if (lvl === 'line') {
output.push(col.callsite(cfg.line()));
}
if (msg !== '') {
outputArray.push(msg);
output.push(col.msg[lvl](msg));
}
if (date) {
outputArray.unshift(cfg.dateCol(date));
}
process.stdout.write(output.join(col.delim[lvl]) + '\n');
};
console.log.call(console, outputArray.join(cfg.delimMap[lvl]));
var toEmitter = function (lvl, msg, ns) {
cfg.emitter.emit('log', {
time : new Date(),
level: lvl,
namespaces : ns,
line : (lvl === 'line') ? cfg.line() : undefined,
message : msg
});
};
if (cfg.fileStream) {
cfg.fileStream.write(JSON.stringify({
date : date
, level : lvl
, namespaces : this.namespaces
, message : msg
}) + '\n');
}
return this;
var toFile = function (lvl, msg, ns) {
cfg.filestream.write(JSON.stringify({
time : cfg.filestamp(),
level : lvl,
namespaces : ns,
line : (lvl === 'line') ? cfg.line() : undefined,
message : msg
}) + '\n');
};
var usingMute = cfg.stdoutmutable ||
(cfg.emitter && cfg.emitmutable) ||
(cfg.filestream && cfg.filemutable);
// One log method per level
cfg.levels.forEach(function (lvl) {
var soutLvl = (cfg.suppressed.indexOf(lvl) < 0)
, emitLvl = (cfg.emitter && cfg.emitsuppressed.indexOf(lvl) < 0)
, fileLvl = (cfg.filestream && cfg.filesuppressed.indexOf(lvl) < 0);
// if suppressed/disabled, never log, so cache these checks
Logger.prototype[lvl] = function () {
var args = format.apply(null, arguments)
, isMuted = (usingMute && this.removed.indexOf(lvl) >= 0);
if (soutLvl && (!cfg.stdoutmutable || !isMuted)) {
toStdout(lvl, args, this.namespaces);
}
if (emitLvl && (!cfg.emitmutable || !isMuted)) {
toEmitter(lvl, args, this.namespaces.slice());
}
if (fileLvl && (!cfg.filemutable || !isMuted)) {
toFile(lvl, args, this.namespaces);
}
return this;
};
});
/**

@@ -105,41 +115,23 @@ * Public methods

// One log method per level
set.difference(cfg.levels, ['line', 'zalgo']).forEach(function (lvl) {
Logger.prototype[lvl] = function () {
return this._log(lvl, format.apply(this, arguments));
};
});
// Generate line and zalgo separately
Logger.prototype.line = function () {
var frame = getStack()[1];
var loc = basename(frame.getFileName()) + ':' + frame.getLineNumber();
var msg = arguments.length ? cfg.delimMap.line + format.apply(this, arguments) : '';
return this._log('line', cfg.lineCol(loc) + msg);
};
Logger.prototype.zalgo = function () {
return this._log('zalgo', zalgify(format.apply(this, arguments)));
};
// Get a log method copy inheriting the current relevant instance settings
Logger.prototype.get = function (fn) {
Logger.prototype.get = function (fn, ns) {
if (cfg.levels.indexOf(fn) < 0) {
this.sub('logule').error("log.get() called with non-method:", fn);
return noop;
}
else if (this.removed.indexOf(fn) < 0 && cfg.globallyOff.indexOf(fn) < 0) {
// return proper fn if not muted and not suppressed
var sub = this.sub();
return function () {
sub[fn].apply(sub, arguments);
};
}
return noop;
var sub = this.sub(ns ? String(ns) : undefined);
return function () {
sub[fn].apply(sub, arguments);
};
};
// Get an instance copy inheriting current settings, but maintains own mutes + namespaces
// Get an instance copy inheriting settings/mutes/namespaces
Logger.prototype.sub = function () {
var sub = Object.create(this);
sub.namespaces = this.namespaces.concat(slice.call(arguments));
sub.isMain = false;
return sub;
return new Logger({
id: 'NA',
isMain: false,
namespaces: this.namespaces.concat(slice.call(arguments)),
removed: this.removed.slice()
});
};

@@ -175,3 +167,3 @@

// Prevent hacky prototype modifications via l.constructor.prototype
// Prevent leaky prototype hacks via l.constructor.prototype
Object.freeze(Logger.prototype);

@@ -185,4 +177,6 @@

}
var log = new Logger(parent.id, true);
var ns = [];
var rem = [];
// loop up the call tree to find previously used parameters

@@ -192,4 +186,4 @@ // best when graph == tree, else original parent is retrieved due to module caching

if (moduleMaps[m.id]) {
log.namespaces = moduleMaps[m.id].namespaces.slice();
log.removed = moduleMaps[m.id].removed.slice();
ns = moduleMaps[m.id].namespaces.slice();
rem = moduleMaps[m.id].removed.slice();
break;

@@ -199,11 +193,19 @@ }

if (moduleName) {
log.namespaces.push(moduleName);
ns.push(String(moduleName));
}
// communication API (frozen)
moduleMaps[parent.id] = {
namespaces : log.namespaces.slice()
, removed : log.removed.slice()
namespaces : ns.slice(),
removed : rem.slice()
};
return log;
return new Logger({
id: parent.id,
isMain: true,
namespaces: ns,
removed: rem
});
};
if (cfg.emitter) {
exports.emitter = cfg.emitter;
}
{
"author": "Eirik Albrigtsen <analsandblaster@gmail.com>",
"name": "logule",
"description": "An advanced console logging library",
"version": "1.1.0",
"description": "A multi-transport, peer-dependent logging library",
"version": "2.0.0",
"repository": {

@@ -12,7 +12,9 @@ "type": "git",

"engines": {
"node": ">=0.6.0"
"node": ">=0.8"
},
"keywords": [
"stdout",
"emitter",
"json",
"file",
"branch",

@@ -27,8 +29,9 @@ "filtration",

"dye": "~0.1.0",
"autonomy": "~0.3.0",
"subset": "~0.1.1",
"confortable": "~0.1.4"
"autonomy": "~0.5",
"subset": "~0.1.3",
"confortable": "~0.2.0",
"typr": "~0.2.0"
},
"devDependencies": {
"tap": "~0.2.5"
"tap": "~0.3.1"
},

@@ -38,4 +41,3 @@ "bugs": {

},
"license": "MIT",
"optionalDependencies": {}
"license": "MIT"
}
# Logule [![Build Status](https://secure.travis-ci.org/clux/logule.png)](http://travis-ci.org/clux/logule)
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.
Logule is a pretty, but heavily configurable logging utility for nodejs. It allows multiple transports (stdout + JSON filestream + emitted logs) as well as being configurable per user, per app and per module via localized config files.

@@ -34,12 +34,12 @@ ![simple output!](https://github.com/clux/logule/raw/master/imgs/outputsimple.png)

* [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)
* [Logging Methods](#logging-methods)
* [debug()](#debug)
* [line()](#line)
* [trace()](#trace)
* [info()](#info)
* [warn()](#warn)
* [error()](#error)
* [zalgo()](#zalgo)
* [get()](#get)
* [get()](#getlevel--namespace)
* [Mute API](#mute-api)
* [mute()](#mute)

@@ -49,2 +49,10 @@ * [unmute()](#unmute)

* [unmuteOnly()](#unmuteonly)
* [Configuration](#configuration)
* [Transports](#transports)
* [stdout](#stdout)
* [Emitter](#emitter)
* [Filestream](#filestream)
* [Style & Formatting](#style--formatting)
* [Timestamps](#timestamps)
* [Global Suppression](#global-suppression)
* [Branch Based Filtration](#branch-based-filtration)

@@ -55,2 +63,3 @@ * [Filtering Branches](#filtering-branches)

* [Colors](#colors)
* [npm Usage](#npm-usage)
* [Installation](#installation)

@@ -61,3 +70,3 @@ * [Running Tests](#running-tests)

## Basic Usage
Require a logule instance for the current file and use it everywhere inside it.
File scope usage; pass down the `module` object at the start.

@@ -79,90 +88,36 @@ ```js

log = require('logule').init(module, 'BUILD');
log.trace("Trying to compile main.js");
log.trace("Trying to compile client.js");
log.error("Failed");
```
**Namespaces inherit from the call tree in order of registration.** If your entry point required the 'BUILD' module, and this has a namespace, then 'BUILD' becomes the next namespace in the chain. When printing to `stdout` there's a default limit of nesting printed out of 3.
![one namespace output!](https://github.com/clux/logule/raw/master/imgs/output.png)
In this case, the extra output came from the parent module one file up the call tree and `trace` messages were turned on.
In this case, the entry point that required 'BUILD' had no namespace. See [Filtering Branches](#filtering-branches) for a more detailed example of how namespaces nest.
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.
## 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:
Subs are copies of logger instances that do not save settings to the call tree.
This allows you to have muted/unmuted logger instances inside a particular file without inadvertently muting or unmuting levels (resp.) from dependent modules.
```js
var log = require('logule').init(module, 'myFile');
var sandboxed = log.sub('CrazyClass').mute('debug');
// pass sandboxed logger to CrazyClass
var spammy = log.sub('spammy').unmute('trace');
// pass spammy logger to external/internal code
```
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.
A `log.sub()` will maintain the the original namespaces and mute settings of `log` as well as config suppressions (so unmute does not override config `suppress`). It can also optionally append one extra namespace to the ones existing, in this case, 'external' will be appended.
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.
See [Filtering Branches](#filtering-branches) for a more detailed example of how subs are used.
## 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.
## Logging Methods
There are 7 log levels (and one method getter) available available on `log`:
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:
### debug()
For debug info to help out during bad times. Suppressed by default, safe to leave in.
- 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`
Step 3 enables modules to bundle their own default config which can be overriden by apps by utilizing step 2.
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).
### 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.
- `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
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.
### Changing Colors
The following options affect output colors:
- `prefixCol` - namespace
- `dateCol` - time and date
- `lineCol` - location in .line()
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.
By default, `trace`, `debug` and `line` messages are suppressed.
### 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"
}
```
## 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.
Note that `trace` and `debug` never outputs anything by default. It must be enabled in the config!
The mystical `zalgo` and `line` provide some specialized logic however:
### line()
Line is prepends the filename and line of caller (as a namespace). It fetches this info from the stack directly.
For temporary debug messages that you sprinkle liberally throughout your nasty functions.
Prepends filename.js:lineNum to the message.
Note these fetch new stacks at each call and are not meant to be used in production. They are always shown in the default config, so don't leave these in code.

@@ -178,4 +133,16 @@ ```js

In this case, config suppression was disabled.
### trace()
For info messages that are too common to see all the time. Suppressed by default, safe to leave in.
### info()
For messages that show events/standard progressions.
These will be shown by default in any module you embed logule in when installed on a new box (i.e. without a custom config).
Ensure you're not talking when you should not need to - consider `trace.
### warn()
For warnings. Shown by default.
### error()
For errors. Shown by default in bold. Not meant to handle Error objects, just the logging please.
### zalgo()

@@ -190,12 +157,22 @@ [H̸̡̪̯ͨ͊̽̅̾̎Ȩ̬̩̾͛ͪ̈́̀́͘ ̶̧̨̱̹̭̯ͧ̾ͬC̷̙̲̝͖ͭ̏ͥͮ͟Oͮ͏̮̪̝͍M̲̖͊̒ͪͩͬ̚̚͜Ȇ̴̟̟͙̞ͩ͌͝S̨̥̫͎̭ͯ̿̔̀ͅ](https://github.com/clux/dye#zalgo)

### 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.
Disclaimer; this is mostly for fun. Find your own use case or suppress it. Note that zalgolization is only applied to stdout, not to the `filestream` or `emitter` transports.
### get(level [, namespace])
Levels can be retrieved as single functions that do not chain by using this method.
Useful if you have a debug module that only needs `log.debug`, so you can pass down a perhaps namespaced function to it.
```js
var dbg = log.get('debug');
dbg("works like log.debug - but nothing else accessible via this non-chainging var");
dbg("works like log.debug - but this function does not chain and expose other methods");
```
Note that if `debug` remains suppressed in the config, or it's `muted` somewhere up the call tree - then you would only get a noop from `.get('debug')`.
Note that `get()` result obeys the mute API and config suppression entirely like the individual level methods.
*Warning:* while you simulate this with `log.debug.bind(log)`, this would chain and allow modules to break out of the constricted environment.
## Mute API
These methods are shortcuts for modifying the private list of muted levels.
Muting affects by default `stdout` and `filestream`, but the config allows changing this to any combination of transports being affected.
### mute()

@@ -213,9 +190,9 @@ Suppress logs for passed in methods.

```js
log.mute('warn', 'info');
var l2 = log.sub('forModuleX').unmute('warn');
log.mute('warn');
var l2 = log.sub('forModuleX').unmute('warn', 'info');
log.warn('muted');
l2.warn('works!');
log.warn('muted');
```
Remember that you cannot unmute `trace` and `debug` unless it has been globally unsuppressed in the active .logule config.
NB: unmute does not override config suppression. It has lower precedence.

@@ -238,9 +215,137 @@ ### muteOnly()

## Configuration
Configuration of colors, style, date formatting, transports, setting mutability and global suppression levels are done via via config files. The [default configuration file](https://github.com/clux/logule/blob/master/.logule.json) results in output looking like the images herein.
Configs are located via the [confortable](https://github.com/clux/confortable) module. This module performs priority based config searches. In particular, it is used here with the following path priorities:
- 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`
Step 3 enables modules to bundle their own default config which can be overriden by apps by utilizing step 2.
The found config file is merged one level deep with the default config, so you don't have to include more in your config than what you disagree with.
### Transports
Logule supports 3 transports: `stdout`, `filestream` and `emitter`.
These all have similar options in the config, but by default only `stdout` is enabled.
**NB: Logule does not strip colors.** If you log pre-colored strings, those colors will show up in the other transports as well!
All transports have the following options:
- `suppress` - list of levels to globally suppress from the transport
- `mutable` - a boolean to indicate whether the mute API affects the transport
- `timestamp` - an object/string to indicate timesta
#### stdout
All logs are by default written directly to `process.stdout`
#### Emitter
If `emitter` has an `enabled` attribute set to `true`, logule will expose an `EventEmitter` instance on `require('logule').stream`.
Then you can listen to `"log"` events:
```js
var logule = require('logule');
var e = logule.emitter;
var log = logule.init(module);
e.on('log', function (obj) {
// plain replacement logging
console.log(obj.time.valueOf() + ' - ' + obj.level + ' - ' + obj.message);
});
```
The types of the keys in `obj` are as follows:
```js
{
time : Date
level: String
namespaces : Array of Strings
message : String
}
```
In the case of level being `'line'`, a `line` key is also available with the short location identifier string `line()` typically outputs.
#### Filestream
If `filestream` has a `file` attribute filled in (with a cwd relative path), this file will be appended to with JSON log messages (one message per line - so JSON.parse forEach line in file.split('\n') will work).
By default the individual JSON messages use the current format:
```js
{
"time": "2012-11-08T11:08:25.092Z",
"level": "error",
"namespaces": ["build"],
"message": "message part, without stdout formatting"
}
```
Where the `time` value may differ depending on the `timestamp` config option.
NB: A `"line"` key is present if level is `"line"` like in the EventEmitter.
### Style & Formatting
The [first four blocks](https://github.com/clux/logule/blob/master/.logule.json#L2-22) in the default config describes the default style set used by the `stdout` transport and are all of the form `levelName : fn` where `fn` is any function in the module [dye](https://github.com/clux/dye). Functions can even be composed by delimiting them with a dot; e.g. `bold.blue`.
The `delimiters` object contains the subtle default styling of the delimiters joining the optional timestamp, the log level, the optional namespaces and the message.
The `levels` object contain the styling for the log levels. By default we only apply `bold` to the critical messages.
The `messages` object contain styling fot the messages (i.e. the result of `util.format(args..)`). By default only zalgo gets message level formatting.
The `colors` object contain the misc. styling used:
- `namespace` - the default blue namespaces
- `timestamp` - the default grey optional timestamp
- `callsite` - the default green file.js:line prefix added by .line()
NB: Levels already listed in the `delimiters`, `levels` or `messages` objects, can be disabled by overriding them in your personal/module's config with the value of `"none"`.
### Timestamps
Timestamps can be configured via the "timestamp" key in the config which is overloaded as follows:
- `"none"` - nothing prepended; log output starts at type, e.g. the `INFO` part
- `precision` - prepends HH:MM:SS:MSS + delimiter via above + padded `getMilliseconds`
- `dateMethod` - prepends the result of any custom method on `Date.prototype`
- `object` - full customization mode
Example of using the custom mode:
```js
"timestamp": {
"date" : true,
"reverse" : false,
"delimiter" : "-",
"precise" : false
}
```
This prepends the date, in the form YYYY-MM-DD (i.e. normal non-reversed european style), and adds the timestamp after the date without precision (i.e. just `toLocaleTimeString`).
Most sensible methods on `Date.prototype` are:
- `toJSON` - default for file
- `valueOf`
- `toLocaleTimeString` - default for stdout
- `toGMTString`
Note that the first two can be perfectly serialized/deserialized with `Date.parse` and are thusly a good format for the filestream JSON transport.
### Global Suppression
Set the `suppress` flag to globally turn all listed log methods into chaining no-ops.
Alternatively list the exceptions under `allow` instead if you like to suppress most levels.
See the [Branch based filtration](#branch-based-filtration) section for more granular control.
By default, `trace` and `debug` messages are suppressed.
## 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.
**NB: The following techniques require your disired transport(s) to be `mutable` in the config.**
### 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:

@@ -273,3 +378,3 @@

With the following code, `a.js` sets the an app default of _no info messages_, 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`.
With the following code, `a.js` sets the an app default of _no info messages_, which is overridden by `b.js`, so the unmute 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`.

@@ -290,3 +395,3 @@ Note that any `mute`/`unmute` calls to a `sub()` does not propagate to other files:

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, and by default, the config prevents unmuting of `trace` and `debug`.
To enforce strict suppression of certain levels, the config file is the way to go.

@@ -305,3 +410,3 @@ ### Muting Chatty Modules

// b.js
var l = require('logule').init(module).mute('warn', 'info').sub().muteOnly();
var l = require('logule').init(module).mute('warn', 'info').sub().unmute('warn', 'info');
var c = require('./c');

@@ -311,10 +416,31 @@ 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 everything on a `sub` that will be used inside this file to preserve the same behaviour inside `b.js` only.
Here we mute main logger from `b.js` (the one from `init`), but unmute 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. Note that modules can safely write `trace` and `debug` messages since the default config mutes these.
Essentially the reverse process of [Muting chatty modules](#muting-chatty-modules), there are two cases, you own the file c.js (modify imports line to mute it):
```js
// c.js
var l = require('logule').init(module, 'leaf').unmute('info');
l.info('works');
```
Or if you don't own the file (so unmute above in the hierarchy):
```js
// b.js
var l = require('logule').init(module).unmute('info').sub().mute('info');
l.info('works');
```
This preserves muting of `b.js`, but opens up for its descendants.
## Colors
The ASNI color code wrapping and zalgolizer is provided by [dye](https://github.com/clux/dye), wheras it used to rely on `colors`. Dye does not introduce implicit global dependencies on `String.prototype`, and provides more sensible zalgolizations.
## npm Usage
When logging with `logule >=2` inside an npm published library/executable, the practice is to put `logule` inside `package.json` `peerDependencies` and NOT the normal `dependencies`. This ensures all modules use the same code and thus logule can encapsulate everything needed to process ALL the logs an application uses. Logule's API is stable, so simply restricting to `"logule": "~2"` will suffice.
In `"logule": "~1"`, bundling of separate copies per npm module was the standard and so logule then adopted the method of communicating with other copies via `process.logule` to compensate for not having any one central piece of code where all logs went through. Ultimately, decisions were being made on behalf of the config so this worked well.
## Installation

@@ -321,0 +447,0 @@

@@ -1,16 +0,11 @@

var logule = require('../').init(module)
var l = require('../').init(module)
, test = require('tap').test
, levels = ['trace', 'debug', 'info', 'warn', 'error', 'zalgo', 'line']
, pubs = ['get', 'mute', 'unmute', 'muteOnly', 'unmuteOnly', 'sub']
, l = logule.sub('suppressed');
l.mute.apply(l, levels); // l is always suppressed
, pubs = ['get', 'mute', 'unmute', 'muteOnly', 'unmuteOnly', 'sub'];
test("chaining", function (t) {
levels.forEach(function (lvl) {
var sub = l.sub('wee')
, single = sub.get(lvl);
// NB: some of the get calls return noop and will never chain
t.equal(l, l[lvl](1), 'l.' + lvl + " chains");
t.equal(l.get(lvl)(1), undefined, "l.get('" + lvl + "') does not chain");
t.equal(sub.info('wat'), sub, "sub chains");
t.equal(single('wat'), undefined, "sub single returns undefined");
t.equal(l.get(lvl)('wat'), undefined, "get " + lvl + " does not chains");
});

@@ -22,8 +17,3 @@ t.end();

test("exports", function (t) {
var expectedExports = levels.concat(pubs);
pubs.forEach(function (p) {
if (p === 'data') {
return;
}
t.ok(l[p] !== undefined, "l." + p + " is exported");

@@ -43,5 +33,4 @@ t.type(l[p], 'function', "l." + p + " is function");

test("subs", function (t) {
t.ok(logule === logule, "obvious test");
t.ok(logule !== logule.sub(), "logule.sub() does not return this");
t.ok(l !== l.sub(), "l.sub() does not return same instance");
t.end();
});

@@ -1,308 +0,78 @@

var logule = require('../').init(module)
var l = 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')
, testMsg = "this is a test message"
, l = logule.sub('suppressed');
l.mute.apply(l, levels);
, suppressed = ['trace', 'debug'] // will never speak
, stack = [];
// monkey-patch process.stdout.write to intercept console.log calls
var hook = function (cb) {
var writeStdOut = process.stdout.write;
//var writeStdErr = process.stderr.write;
process.stdout.write = cb;
//process.stderr.write = cb;
// hook into stdout to see what's being sent to it
process.stdout.write = (function(write) {
return function(buf, encoding, fd) {
write.call(process.stdout, buf, encoding, fd);
stack.push(buf.toString()); // our extra
};
}(process.stdout.write));
// return an undo damage fn returned
return function () {
process.stdout.write = writeStdOut;
//process.stderr.write = writeStdErr;
};
var didPrint = function (str) {
return stack[stack.length-1].indexOf(str) >= 0;
};
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;
var verifyOutput = function (log, canSpeak, lvl, ns, isSingle) {
var oldsize = stack.length
, count = 0
, testMsg = "this is %s number %d with %j"
, expected = "this is message number " + count + " with {}";
// return an undo damage fn returned
return function () {
process.stdout.write = writeStdOut;
process.stderr.write = writeStdErr;
};
};
// try to log the thing
var fn = (isSingle) ? log : log[lvl].bind(log);
var ret = fn(testMsg, 'message', count, {}, "extra");
t.equal(ret, isSingle ? undefined : log, "should chain iff not single fn");
var stdlog = logule.sub('STDOUT')
, 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;
};
// output from all shortcut methods is sensible
levels.forEach(function (lvl) {
var oldsize = output.length
, include = lvl.toUpperCase();
stdlog[lvl](testMsg);
t.ok(lastIncludes(include), "captured stdlog contains correct log type");
if (lvl !== 'zalgo')
t.ok(lastIncludes(testMsg), "captured stdlog contains input message");
t.ok(lastIncludes("STDOUT"), "captured stdlog contains namespace");
t.equal(oldsize + 1, output.length, "hook pushed a str onto the output array");
});
// output from get(lvl) functions is sensible
levels.forEach(function (lvl) {
stdlog.sub('GOTTEN').get(lvl)(testMsg);
var include = lvl.toUpperCase();
if (lvl !== 'zalgo')
t.ok(lastIncludes(testMsg), "stdlog.get('" + lvl + "') logged the message");
t.ok(lastIncludes(include), "stdlog.get('" + lvl + "') preserves log level correctly");
t.ok(lastIncludes('STDOUT'), "stdlog.get('" + lvl + "') preserves namespace1");
t.ok(lastIncludes('GOTTEN'), "stdlog.get('" + lvl + "') preserves namespace2");
});
stdlog.info('storing previous message');
t.ok(lastIncludes("storing previous message"), "storing works before testing suppressed functions");
var oldmsg = last();
// suppressed methods do not send to stdout
levels.forEach(function (lvl) {
var stdsub = stdlog.sub().mute(lvl)
, single = stdsub.get(lvl);
stdsub[lvl](testMsg);
t.equal(oldmsg, last(), "suppressed logger function does not send to stdout");
single(testMsg);
t.equal(oldmsg, last(), "suppressed logger single function does not send to stdout");
});
// subs of suppressed do not send to stdout
levels.forEach(function (lvl) {
var stdsub = stdlog.sub('THIS_DIES').mute(lvl);
stdsub.sub('SUBSUB')[lvl](testMsg);
t.equal(oldmsg, last(), "sub()." + lvl + " does not send to stdout when parent was suppressed");
stdsub.sub('SUBSUB').get(lvl)(testMsg);
t.equal(oldmsg, last(), "sub().get('" + lvl + "') does not send to stdout when parent was suppressed");
});
// but re-allowed ones will
levels.forEach(function (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.unmute(lvl);
stdsub[lvl]('i am resurrected');
t.ok(lastOut !== last(), "ressurrected method logs " + lvl + "again");
lastOut = last(); // save new lastOut for later
});
// muting a level does not affect other levels
levels.forEach(function (lvl) {
var stdsub = stdlog.sub('SEMI').mute(lvl);
// via normal approach
levels.forEach(function (lvl2) {
if (lvl2 === lvl) {
return;
if (!isSingle) {
if (canSpeak) {
t.ok(log.removed.indexOf(lvl) < 0, lvl + " can speak and is not removed");
}
var oldsize = output.length
, include = lvl2.toUpperCase();
stdsub[lvl2](testMsg);
t.ok(lastIncludes("SEMI"), "semi suppressed logger outputs when " + lvl2 + " not suppressed");
if (lvl2 !== 'zalgo')
t.ok(lastIncludes(testMsg), "semi suppressed logger outputs when " + lvl2 + " not suppressed");
t.ok(lastIncludes(include), "semi suppressed logger does indeed output as the log level matches " + lvl2);
t.equal(oldsize + 1, output.length, "hook pushed a str onto the output array (semi suppress)");
});
// via get
levels.forEach(function (lvl2) {
if (lvl2 === lvl) {
return;
else if (suppressed.indexOf(lvl) < 0){
t.ok(log.removed.indexOf(lvl) >= 0, lvl + " can not speak and is removed");
}
var include = lvl2.toUpperCase();
var oldsize = output.length;
stdsub.get(lvl2)(testMsg);
t.ok(lastIncludes("SEMI"), "semi suppressed logger outputs when " + lvl2 + " not suppressed");
if (lvl2 !== 'zalgo')
t.ok(lastIncludes(testMsg), "semi suppressed logger single outputs when " + lvl2 + " not suppressed");
t.ok(lastIncludes(include), "semi suppressed logger single does indeed output as the log level matches " + lvl2);
t.equal(oldsize + 1, output.length, "hook pushed a str onto the output array (semi suppress single)");
});
// nor does it affect future subs
levels.forEach(function (lvl2) {
if (lvl2 === lvl) {
return;
}
var include = lvl2.toUpperCase();
var oldsize = output.length;
stdsub.sub('subSemi')[lvl2](testMsg);
t.ok(lastIncludes("SEMI"), "semi suppressed logger sub outputs when " + lvl2 + " not suppressed");
t.ok(lastIncludes("subSemi"), "semi suppressed logger sub outputs when " + lvl2 + " not suppressed");
if (lvl2 !== 'zalgo')
t.ok(lastIncludes(testMsg), "semi suppressed logger single outputs when " + lvl2 + " not suppressed");
t.ok(lastIncludes(include), "semi suppressed logger does indeed output as the log level matches " + lvl2);
t.equal(oldsize + 1, output.length, "hook pushed a str onto the output array (semi suppress sub)");
});
// nor future subs' gets
levels.forEach(function (lvl2) {
if (lvl2 === lvl) {
return;
}
var oldsize = output.length;
var include = lvl2.toUpperCase();
stdsub.sub('subSemi').get(lvl2)(testMsg);
t.ok(lastIncludes("SEMI"), "semi suppressed logger sub single outputs when " + lvl2 + " not suppressed");
t.ok(lastIncludes("subSemi"), "semi suppressed logger sub single outputs when " + lvl2 + " not suppressed");
if (lvl2 !== 'zalgo')
t.ok(lastIncludes(testMsg), "semi suppressed logger sub single outputs when " + lvl2 + " not suppressed");
t.ok(lastIncludes(include), "semi suppressed logger sub single does indeed output as the log level matches " + lvl2);
t.equal(oldsize + 1, output.length, "hook pushed a str onto the output array (semi suppress sub single)");
});
});
// chaining works even when things are suppressed
levels.forEach(function (lvl) {
if (lvl === 'info') {
return;
}
var stdsub = stdlog.sub('chainer').mute(lvl)
, oldsize = output.length;
stdsub[lvl]('suppressed message').info('working message')[lvl]('another suppressed');
t.equal(oldsize + 1, output.length, "hook pushed a str onto the output array (chained 3 calls but 2 suppressed)");
t.ok(lastIncludes('working message'), "chaining message for " + lvl + " onto suppressed info does log what we want");
});
// multi output
levels.forEach(function (lvl) {
stdlog.sub('multi')[lvl](testMsg, 160000, 'WOWZA', {});
if (lvl !== 'zalgo') {
t.ok(lastIncludes(testMsg), "multi argument message to " + lvl + " contains argument 1");
t.ok(lastIncludes(160000), "multi argument message to " + lvl + " contains argument 2");
t.ok(lastIncludes("WOWZA"), "multi argument message to " + lvl + " contains argument 3");
t.ok(lastIncludes("{}"), "multi argument message to " + lvl + " contains argument 4");
if (!canSpeak) {
t.equal(stack.length, oldsize, "no message was printed");
}
});
// multi output single
levels.forEach(function (lvl) {
stdlog.sub('multi').get(lvl)(testMsg, 160000, 'WOWZA', {});
if (lvl !== 'zalgo')
{
t.ok(lastIncludes(testMsg), "multi argument message to " + lvl + " contains argument 1");
t.ok(lastIncludes(160000), "multi argument message to " + lvl + " contains argument 2");
t.ok(lastIncludes("WOWZA"), "multi argument message to " + lvl + " contains argument 3");
t.ok(lastIncludes("{}"), "multi argument message to " + lvl + " contains argument 4");
}
});
// unmuteOnly affects only complement
levels.forEach(function (lvl) {
var stdsub = stdlog.sub('BLAH').unmuteOnly(lvl);
var oldsize = output.length
, include = lvl.toUpperCase();
stdsub[lvl](testMsg);
t.ok(lastIncludes("BLAH"), "unmuteOnly " + lvl + " does not mute self");
if (lvl !== 'zalgo')
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;
else {
t.equal(stack.length, oldsize + 1, "a message was printed");
t.ok(didPrint(lvl.toUpperCase()), "msg contains correct log type");
if (ns) {
t.ok(didPrint(ns), "msg contains namespace");
}
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;
if (lvl !== 'zalgo') {
t.ok(didPrint(expected), "msg contains input message");
t.ok(didPrint("extra"), "msg contains extra param (not %s'd)");
}
var oldsize = output.length
, include = lvl2.toUpperCase();
stdsub[lvl2](testMsg);
t.ok(lastIncludes("BLOH"), "muteOnly " + lvl + " does not mute " + lvl2);
if (lvl2 !== 'zalgo')
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();
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;
};
}
count += 1;
};
var stdlog = logule.sub('STDERR')
, output = [""];
// verify all use cases
levels.forEach(function (lvl, i) {
var canSpeak = suppressed.indexOf(lvl) < 0;
var unhook = hook(function (str, enc, fd) {
output.push(str);
});
// verify normal log methods
var normal = l.sub('NS' + i);
verifyOutput(normal, canSpeak, lvl, 'NS' + i, false);
var last = function () {
return output[output.length - 1];
};
// verify get methods
var single = l.sub('GS' + i).get(lvl);
verifyOutput(single, canSpeak, lvl, 'GS' + i, true);
var lastIncludes = function (x) {
return last().indexOf(x) >= 0;
};
// verify suppress
var noworky = l.sub('HA').unmuteOnly('info');
verifyOutput(noworky, lvl === 'info', lvl, 'HA', false);
levels.forEach(function (lvl) {
if (stderrs.indexOf(lvl) >= 0) {
stdlog[lvl]("histderr");
t.ok(lastInclude("histderr"), "stderr log works")
var include = 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");
}
// verify unsuppress (of suppressed ones)
var worky = noworky.sub('WO').muteOnly('warn');
verifyOutput(worky, canSpeak && lvl !== 'warn', lvl, 'WO', false);
});
unhook();
t.end();
});
*/

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