Saturday 16 January 2021

Log Function/Method Calls

Quite often it's useful to log information about function/method calls, including the parameters values. I've coded a simple function that helps me with that in JavaScript. We could combine it with some AOP stuff to wire the call that logs the information at the beginning of each function... but that's not the topic for this post and I'm doing it manually.

The code looks like this:


//self represents the "this" value
function getFnCallInfo(self, fn, ...args){
    const serializeParameter = (param) => {
        //typeof null is "Object", but typeof undefined is "undefined"
        if (param === null)
            return "null";

        switch (typeof param){
            case "undefined":
                return "undefined";
            case "function":
                return "function: " + param.name;
            default:
                return param.toString();
        }
    };
    
    let fnStr = fn.toString();
    let start = fnStr.search(/\(/);
    let end = fnStr.search(/\)/);
    let paramNamesStr = [...fnStr].slice(start + 1, end).join("");
    let paramNames = paramNamesStr.split(",").map(param => param.trim());
    let namesValues = paramNames.map((paramName, index) => paramName + " = " + (index < args.length
        ? serializeParameter(args[index]) 
        : undefined)
    );
    let thisStr = self 
        ? self.constructor.name + "."
        : "";
    return(`${thisStr}${fn.name} invoked with:\n\t${namesValues.join("\n\t")}`);
}

//we use it like this:
class French extends Person {
    sayGreeting(toName, includeLocation){
        //arguments.callee is deprecated
        console.log(getFnCallInfo(this, this.sayGreeting, ...arguments));
        .....
    }
}

let guillaume = new French("Guillaume", "Paris");
guillaume.sayGreeting("hi", true);

//output:
//French.sayGreeting invoked with:
//	toName = hi
//	includeLocation = true


I would love not having to type the name of the function being logged (this.sayGreeting) when invoking getFnCallInfo, cause if we change the function name we have to make sure we also change it in the call. In the past we could just use arguments.callee, but that's been deprecated since quite a while... Well, it's not much of a problem, if we're using any decent IDE with rename support we're done... I don't feel much like explaining the code... just see that I use the arguments pseudo array in the invokation rather than typing the parameter names (and I convert it into a normal array with the spread operator). I leverage function.toString to get the parameter names, as I had already done in this previous post

No comments:

Post a Comment