3
\$\begingroup\$

I've been playing around with AOP in JavaScript to create a Logger that I can attach to classes and output what is going on (mainly instantiation, calling of functions and the results from functions). I've done this by creating a static class (static method in a class) that essentially is a factory that uses a proxy to trap the instantiation of a class (or just a function) and applies another proxy to the instantiated class to capture function calls on that class.

Beyond sanity/standards checking what I've written, I must admit I haven't come up with better tests than checking to make sure it instantiates a class correctly and that any function called on a class will return the expected results. I'd be interested to know of better ways of testing this.

I've had to tell it that it shouldn't try and apply itself, due to it being a static class and my not wanting to instantiate itself.

it takes an eventEmitter object as the first argument that should have a method of publish accepting at least a string.

class Logger {
  static ClassHandler(eventEmitter, obj) {
    const InstanceHandler = {
      get(target, prop, receiver){
        eventEmitter.publish(`${prop} called on ${target.constructor.name}`);
        const orig = Reflect.get(target, prop, receiver);
        if (typeof orig == "function" &&
            prop !== 'Logger') {
          return function (...args) {
            eventEmitter.publish(`Method ${prop} called with ${JSON.stringify(args)}`);
            let result =  orig.apply(this, args);
            eventEmitter.publish(`Result from ${prop} is: ${JSON.stringify(result)}`);
            return result;
          };
        } else {
          return orig;
        }
      }
    }

    const handler = {
      construct(target, args) {
        eventEmitter.publish(`${target.name} instantiated`);
        const instance = Reflect.construct(...arguments);
        return new Proxy(instance, InstanceHandler);
      },
      apply(target, thisArg, argumentsList) {
        eventEmitter.publish(`${target.name} called with with ${JSON.stringify(argumentsList)}`);
        const result =  Reflect.apply(...arguments);
        eventEmitter.publish(`Result from ${target.name} is: ${JSON.stringify(result)}`);
        return result;
      },
    }
    return new Proxy(obj, handler);
  }
}

module.exports = Logger;

So to use it, something like this:

SimpleClass.js

class SimpleClass {
  constructor() {

  }

  shoutOut() {
    return 'hello!';
  }

  returnValue(value) {
    return value;
  }
}

module.exports = SimpleClass;

Publisher.js

class Publisher {
  constructor() {}

  publish(event) {
    console.log(event);
  }
}

module.exports = Publisher;

index.js

const Logger = require('Logger');
const Publisher = require('Publisher');
const publisher = new Publisher();
const SimpleClass = Logger.ClassHandler(publisher, require('SimpleClass'));
const simpleClass = new SimpleClass();
simpleClass.shoutOut();

this is it at it's most simple and should produce the output:

SimpleClass instantiated

shoutOut called on SimpleClass

if you then called the returnValue('hi there') method you'd get;

SimpleClass instantiated

returnValue called on SimpleClass

Method returnValue called with ["hi there"]

Result from returnValue is: "hi there"

How can i better test this? Can I improve my Logger at all? Have I done the right thing with Proxies and Reflect?

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Browse other questions tagged or ask your own question.