Adding Logging To Objects With Help Of Metaprogramming

Metaprogramming in JS is not as much as big buzz word like say in Ruby. Partly perhaps because the whole experience of coding in JavaScript is one big meta-programming experience. But sometimes there are those small little hacks that make our life as developers easier, and I think “metaprogramming” is just the right term to describe them.

Let’s say you want to modify the behaviour of an existing Backbone view.

Have you ever had this feeling. You look at someone’s code. And you’re just trying to understand, what’s happening there. What methods are being invoked, and in what particular order. Wouldn’t it be nice if Backbone could log every method being invoked into console, maybe with some meta information like when it started, when it finished, how long did it take, etc.?

Well, there’s a really easy way to do this.

Let’s say, we have a Backbone view with several binded events, lot’s of methods and stuff. Let’s create a function that adds logging capability to it. It takes a view, and makes methods log themselves.

Naturally I call it ‘logify’.

function logify(cl) {
}

First of all we should know the names of methods. Methods reside in prototype property because a Backbone view is a constructor function. So we should take getOwnPropertyNames and then take only functions from it.

function logify(cl) {
  // Take the prototype, cause it's where all the methods reside
  var proto = cl.prototype;
  // Take owned methods
  var owned = Object.getOwnPropertyNames(proto);
  var methods = owned.filter(function (x) { return typeof proto[x] == 'function' });
};

OK, here’re we go. Now let’s replace the old methods with the new ones. The ones that would take log something to console and then invoke the old methods.

function logify(cl) {
  // ...
  // Let's replace every method with a simple wrapper
  methods.forEach(function (n) {
    var old = proto[n]; // old method
    proto[n] = function() {
      console.log("-> " + n + " invoked");
      res = old.apply(this, arguments); // call the old fn in context of current object and with the arguments
      return res; // don't forget to return the value
    };
  });
};

There are two things to notice. First, we can’t just invoke the old methods. We should pass to it both the arguments and the this context. We’re doing it with help of apply function. Second, we shouldn’t forget to return the value produced by the old method.

Now, as it’s done, it’s easy to add some additional logic to it. Here’s an example of logify logging the time took for method to execute.

function logify(cl) {
  // Take the prototype, cause it's where all the methods reside
  var proto = cl.prototype;
  // Take owned methods
  var owned = Object.getOwnPropertyNames(proto);
  var methods = owned.filter(function (x) { return typeof proto[x] == 'function' });
  // Let's replace every method with a simple wrapper
  methods.forEach(function (n) {
    var old = proto[n]; // old method
    proto[n] = function() {
      var tick = Date.now();
      console.log("-> " + n + " started");
      res = old.apply(this, arguments); // call the old fn in context of current object and with the arguments
      var tack = Date.now();
      var d = (tack - tick) / 1000;
      console.log("-> #{n} exited in #{d}s");
      return res; // don't forget to return the value
    };
  });

And that’s about it. Now we can take any view (or actually any ‘class’) and make it log itself:

logify(MyView);

See ya!

 
4
Kudos
 
4
Kudos

Now read this

Browsing changed files in Vim with FZF

I use junegunn/fzf to browse files in Vim. It’s an extremely fast fuzzy finder written in Go. If you compare it to something like [CtrlP](kien/ctrlp.vim) you’ll find it blazingly fast. And what’s cool, it doesn’t need to cache files... Continue →