Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add optional dynamic debug instances (react on enable() and disable() once created) #209

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions Readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,22 @@ Then, run the program to be debugged as usual.

You can also exclude specific debuggers by prefixing them with a "-" character. For example, `DEBUG=*,-connect:*` would include all debuggers except those starting with "connect:".

## enable() and disable() functions

The module exports a `enable(namespaces)` function that updates the enabled namespaces, and `disable()` function that disables the logging. Note however that those functions just work on future created debug instances (unless they were dynamic instances).

## Dynamic debug instances

Dynamic debug instances are those whose behavior (whether they are printed or not) is updated in **runtime** for every call to the module exported `enable()` or `disabled()` methods (see above).

Dynamic debug instances are created by passing `true` as second argument to the module exported function:

```js
var dyndebug = require('debug')('myApp', true);
```

*IMPORTANT:* Dynamic debug instances are stored within the module so they may cause a memory leak technically (if instances are created in runtime rather than statically at the script start). To avoid that, call the `release()` method on them. Once `release()` is called the instance is no longer dynamic.

## Browser support

Debug works in the browser as well, currently persisted by `localStorage`. Consider the situation shown below where you have `worker:a` and `worker:b`, and wish to debug both. Somewhere in the code on your page, include:
Expand Down
221 changes: 171 additions & 50 deletions debug.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,18 @@ exports.skips = [];

exports.formatters = {};

/**
* Container for dynamic debug instances.
*/

exports.dynamics = {};

/**
* Dynamic debug instances counter.
*/

var dynamicCounter = 0;

/**
* Previously assigned color.
*/
Expand Down Expand Up @@ -55,74 +67,145 @@ function selectColor() {
* Create a debugger with the given `namespace`.
*
* @param {String} namespace
* @param {[Boolean]} isDynamic
* @return {Function}
* @api public
*/

function debug(namespace) {
function debug(namespace, isDynamic) {
// Return a non dynamic debug instance.
if (!isDynamic) {
// define the `disabled` version
function disabled() {
}
disabled.enabled = false;

// define the `disabled` version
function disabled() {
}
disabled.enabled = false;
// define the `enabled` version
function enabled() {
// set `diff` timestamp
var curr = +new Date();
var ms = curr - (prevTime || curr);
enabled.diff = ms;
enabled.prev = prevTime;
enabled.curr = curr;
prevTime = curr;

// add the `color` if not set
if (null == enabled.useColors) enabled.useColors = exports.useColors();
if (null == enabled.color && enabled.useColors) enabled.color = selectColor();

// define the `enabled` version
function enabled() {
var args = Array.prototype.slice.call(arguments);

var self = enabled;
args[0] = exports.coerce(args[0]);

// set `diff` timestamp
var curr = +new Date();
var ms = curr - (prevTime || curr);
self.diff = ms;
self.prev = prevTime;
self.curr = curr;
prevTime = curr;
if ('string' !== typeof args[0]) {
// anything else let's inspect with %o
args = ['%o'].concat(args);
}

// apply any `formatters` transformations
var index = 0;
args[0] = args[0].replace(/%([a-z%])/g, function(match, format) {
// if we encounter an escaped % then don't increase the array index
if (match === '%%') return match;
index++;
var formatter = exports.formatters[format];
if ('function' === typeof formatter) {
var val = args[index];
match = formatter.call(enabled, val);

// now we need to remove `args[index]` since it's inlined in the `format`
args.splice(index, 1);
index--;
}
return match;
});

if ('function' === typeof exports.formatArgs) {
args = exports.formatArgs.apply(enabled, args);
}
var logFn = enabled.log || exports.log || console.log.bind(console);
logFn.apply(enabled, args);
}
enabled.enabled = true;

// add the `color` if not set
if (null == self.useColors) self.useColors = exports.useColors();
if (null == self.color && self.useColors) self.color = selectColor();
var fn = exports.enabled(namespace) ? enabled : disabled;

var args = Array.prototype.slice.call(arguments);
fn.namespace = namespace;

args[0] = exports.coerce(args[0]);
// Fake release() method.
fn.release = function() {};

if ('string' !== typeof args[0]) {
// anything else let's inspect with %o
args = ['%o'].concat(args);
}
return fn;
}

// apply any `formatters` transformations
var index = 0;
args[0] = args[0].replace(/%([a-z%])/g, function(match, format) {
// if we encounter an escaped % then don't increase the array index
if (match === '%%') return match;
index++;
var formatter = exports.formatters[format];
if ('function' === typeof formatter) {
var val = args[index];
match = formatter.call(self, val);

// now we need to remove `args[index]` since it's inlined in the `format`
args.splice(index, 1);
index--;
// Return a dynamic debug instance.
else {
function dynamic() {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

-1 for all this copypasta

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What do you mean??

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the dynamic function is nearly identical to the enabled function

Copy link
Contributor Author

@ibc ibc Mar 1, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right. We can talk about code optimization if the PR is at least considered by the author, which said nothing yet about it.

if (!dynamic.enabled) {
return;
}
return match;
});

if ('function' === typeof exports.formatArgs) {
args = exports.formatArgs.apply(self, args);
// set `diff` timestamp
var curr = +new Date();
var ms = curr - (prevTime || curr);
dynamic.diff = ms;
dynamic.prev = prevTime;
dynamic.curr = curr;
prevTime = curr;

// add the `color` if not set
if (null == dynamic.useColors) dynamic.useColors = exports.useColors();
if (null == dynamic.color && dynamic.useColors) dynamic.color = selectColor();

var args = Array.prototype.slice.call(arguments);

args[0] = exports.coerce(args[0]);

if ('string' !== typeof args[0]) {
// anything else let's inspect with %o
args = ['%o'].concat(args);
}

// apply any `formatters` transformations
var index = 0;
args[0] = args[0].replace(/%([a-z%])/g, function(match, format) {
// if we encounter an escaped % then don't increase the array index
if (match === '%%') return match;
index++;
var formatter = exports.formatters[format];
if ('function' === typeof formatter) {
var val = args[index];
match = formatter.call(dynamic, val);

// now we need to remove `args[index]` since it's inlined in the `format`
args.splice(index, 1);
index--;
}
return match;
});

if ('function' === typeof exports.formatArgs) {
args = exports.formatArgs.apply(dynamic, args);
}
var logFn = dynamic.log || exports.log || console.log.bind(console);
logFn.apply(dynamic, args);
}
var logFn = enabled.log || exports.log || console.log.bind(console);
logFn.apply(self, args);
}
enabled.enabled = true;

var fn = exports.enabled(namespace) ? enabled : disabled;
dynamic.enabled = exports.enabled(namespace);
dynamic.namespace = namespace;

fn.namespace = namespace;
// Append to the container-
var idx = dynamicCounter++;
exports.dynamics[idx] = dynamic;

return fn;
// Set release() method.
dynamic.release = function() {
delete exports.dynamics[idx];
};

return dynamic;
}
}

/**
Expand All @@ -136,6 +219,10 @@ function debug(namespace) {
function enable(namespaces) {
exports.save(namespaces);

// Reset names and skips.
exports.names = [];
exports.skips = [];

var split = (namespaces || '').split(/[\s,]+/);
var len = split.length;

Expand All @@ -148,6 +235,9 @@ function enable(namespaces) {
exports.names.push(new RegExp('^' + namespaces + '$'));
}
}

// Update dynamic debug instances.
updateDynamics();
}

/**
Expand All @@ -157,7 +247,15 @@ function enable(namespaces) {
*/

function disable() {
exports.enable('');
// Clear stored namespaces.
exports.save(null);

// Reset names and skips.
exports.names = [];
exports.skips = [];

// Update dynamic debug instances.
updateDynamics(true);
}

/**
Expand Down Expand Up @@ -195,3 +293,26 @@ function coerce(val) {
if (val instanceof Error) return val.stack || val.message;
return val;
}

/**
* Update dynamic debug instances.
*
* @param {[Boolean]} disableAll
* @api private
*/

function updateDynamics(disableAll) {
var idx, dynamic;

for (idx in exports.dynamics) {
if (exports.dynamics.hasOwnProperty(idx)) {
dynamic = exports.dynamics[idx];

if (!disableAll) {
dynamic.enabled = exports.enabled(dynamic.namespace);
} else {
dynamic.enabled = false;
}
}
}
}
24 changes: 24 additions & 0 deletions example/dynamic.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
process.env.DEBUG = '*';


var debug = require('../');


var dyndebug1 = debug('dyndebug1', true);
dyndebug1('OK: should show dyndebug1');

debug.disable();
console.log('DEBUG should be undefined, and it is:', process.env.DEBUG);
dyndebug1('ERROR: should not show dyndebug1');

var dyndebug2 = debug('dyndebug2', true);
dyndebug2('ERROR: should not show dyndebug2');

debug.enable('dyndebug1');
console.log('DEBUG should be dyndebug1, and it is:', process.env.DEBUG);
dyndebug1('OK: should show dyndebug1');

debug.enable('dyndebug2');
console.log('DEBUG should be dyndebug2, and it is:', process.env.DEBUG);
dyndebug1('ERROR: should not show dyndebug1');
dyndebug2('OK: should show dyndebug2');