Console.log for all or selected function calls with stack trace

I needed to debug some function calls, I needed to know which function is called and when, I decide to not to use debugger and step because it would be slow so I’ve written the function that logs all function calls, and include stack trace if needed. The function can be called in few ways:

  1. globals('createUser', true)

    will log one function call and include stack trace, if second argument is omitted or false it will not print stack trace

  2. globals(['createUser', 'deleteUser'], true)

    will log both functions and include stack trace

  3. globals({'createUser': true, 'deleteUser': false})

    will log both but only the first one will have stack trace

  4. globals()

    will log all global function calls

  5. globals(true)

    will log all function calls and include stack trace for all functions

Here is the function itself:

function globals(arg, stack) {
  var show_stack;
  if (typeof arg == 'boolean') {
    stack = arg;
    arg = undefined;
  }
  var is_valid_name;
  if ($.isPlainObject(arg)) {
    var keys = Object.keys(arg);
    is_valid_name = function(name) { return keys.indexOf(name) != -1; };
    show_stack = function(name) { return arg[name]; };
  } else {
    show_stack = function() { return stack; };
    if (arg === undefined) {
      is_valid_name = function() { return true };
    } else if (arguments[0] instanceof Array) {
      is_valid_name = function(name) { return arg.indexOf(name) != -1; };
    } else {
      is_valid_name = function(name) { return arg == name; };
    }
  }
  document.addEventListener("DOMContentLoaded", function() {
    Object.keys(window).forEach(function(key) {
      var original = window[key];
      if (typeof original == 'function' &&
          !original.toString().match(/\[native code\]/) &&
          'globals' != key && is_valid_name(key)) {
        window[key] = function() {
          var args = [].map.call(arguments, function(arg) {
            if (arg instanceof $.fn.init) {
              return '[Object jQuery]';
            } else if (arg === undefined) {
              return 'undefined';
            } else {
              return JSON.stringify(arg);
            }
          }).join(', ');
          console.log(key + '(' + args + ')');
          if (show_stack(key)) {
            console.log(new Error().stack);
          }
          return original.apply(this, arguments);
        };
        // just in case some code parse function as strings
        window[key].toString = function() {
          return original.toString();
        };
      }
    });
  });
}

this function can be easily extended to log methods calls on an object just use object instead of window. Here is example of higher order function that create debug functions, here is the code with few improvements:

function debug(object, name) {
  var fn = function(arg, stack) {
    var show_stack;
    if (typeof arg == 'boolean') {
      stack = arg;
      arg = undefined;
    }
    var is_valid_name;
    if ($.isPlainObject(arg)) {
      var keys = Object.keys(arg);
      is_valid_name = function(name) { return keys.indexOf(name) != -1; };
      show_stack = function(name) { return arg[name]; };
    } else {
      show_stack = function() { return stack; };
      if (arg === undefined) {
        is_valid_name = function() { return true };
      } else if (arguments[0] instanceof Array) {
        is_valid_name = function(name) { return arg.indexOf(name) != -1; };
      } else {
        is_valid_name = function(name) { return arg == name; };
      }
    }
    document.addEventListener("DOMContentLoaded", function() {
      var functions = Object.keys(object).filter(function(name) {
        return typeof object[name] == 'function';
      });
      functions.forEach(function(key) {
        var original = object[key];
        var str = original.toString();
        if (!str.match(/\[native code\]/) && !str.match(/<#debug>/) &&
            key != 'debug' && !original.__debug &&
            is_valid_name(key)) {
          object[key] = function() {
            var args = [].map.call(arguments, function(arg) {
              if (arg instanceof HTMLElement) {
                return '[NODE "' + arg.nodeName + '"]';
              } else if (arg instanceof $.fn.init) {
                return '[Object jQuery]';
              } else if (arg === undefined) {
                return 'undefined';
              } else if (arg === window) {
                return '[Object Window]';
              } else if (arg == document) {
                return '[Object document]';
              } else {
                return JSON.stringify(arg);
              }
            }).join(', ');
            console.log((name?name + '.': '') + key + '(' + args + ')');
            if (show_stack(key)) {
              console.log(new Error().stack);
            }
            return original.apply(this, arguments);
          };
          object[key].toString = function() {
            return str;
          };
          object[key].__debug = true;
          object[key].prototype = original.prototype;
          for (var i in original) {
            if (original.hasOwnProperty(i)) {
              object[key][i] = original[i];
            }
          }
        }
      });
    });
  };
  fn.toString = function() {
    return '<#debug>';
  };
  return fn;
}

You can debug all jQuery methods using:

debug(jQuery, '$')();

second argument is label for log

the function that returned accept the same arguments as the first function. You can call the function with window object to get the same function so if you want log only one function with stack trace will look like this:

var globals = debug(window);
globals('addExcludedWarning', true)

Another cool idea is to include time of the function invocation, that is left as an exercise, Hint you can use performance.now() function to check current time in milliseconds.

Advertisements

, ,

  1. Leave a comment

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: