Search code examples
javascriptnode.jsdebuggingoptimizationdead-code

How do you add verbose logging code to functions without affecting performance?


Performance is important for a certain class I'm writing.

I thought about calling a function like so:

debug('This is a debug message, only visible when debugging is on');

And the contents would be like

function debug(message) {
    if (DEBUG) console.log(message);
}

So I wonder: is this enough for V8 to flag this as "dead code" if the DEBUG variable never changes?

Edit: I'm more worried about the performance in Node than on the browser, so removing the code while minifying would be insufficient.

Edit2: I made a JSPerf benchmark out of the proposed solutions, and they are very surprising: http://jsperf.com/verbose-debug-loggin-conditionals-functions-and-no-ops/3


Solution

  • There's a couple of solutions available(aside Petah's...):

    1. Use UglifyJS2 conditional compilation:

    You can use the --define (-d) switch in order to declare global variables that UglifyJS will assume to be constants (unless defined in scope). For example if you pass --define DEBUG=false then, coupled with dead code removal UglifyJS will discard the following from the output:

    if (DEBUG) {
        console.log("debug stuff");
    }
    

    UglifyJS will warn about the condition being always false and about dropping unreachable code; for now there is no option to turn off only this specific warning, you can pass warnings=false to turn off all warnings.

    Another way of doing that is to declare your globals as constants in a separate file and include it into the build. For example you can have a build/defines.js file with the following:

    const DEBUG = false;
    const PRODUCTION = true;
    // etc.
    and build your code like this:
    

    uglifyjs build/defines.js js/foo.js js/bar.js... -c UglifyJS will notice the constants and, since they cannot be altered, it will evaluate references to them to the value itself and drop unreachable code as usual. The possible downside of this approach is that the build will contain the const declarations.

    1. Use a wrapper function.

    For example you have this method:

    exports.complicatedMethod = function (arg1, arg2, arg3) {
        stuff...
    };
    

    You add logging to it by wrapping it in a logger function:

    function logger(fn) {
        if (!DEBUG) {
            return fn;
        }
        return function () {
            console.log(fn.name, arguments); // You can also use `fn.toString()` to get the argument names.
            fn.apply(this, arguments);
        };
    }
    
    exports.complicatedMethod = logger(function (arg1, arg2, arg3) {
        stuff...
    });
    

    This way the only performance hit would be at startup time. You can also use AOP method with the above wrapper function:

    exports.complicatedMethod = function (arg1, arg2, arg3) {
        stuff...
    };
    
    if (DEBUG) {
        for (var name in exports) {
           exports[name] = logger(exports[name]);
        }
    }
    

    And you can pass information to the logger by adding properties to the function:

    exports.complicatedMethod.description = 'This function only shows how tired i was when I was writing it and nothing else!';
    

    You can have a look at this question where someone created code that creates a logger for functions in an object recursively. Also check this answer of mine.

    1. Use C Pre Processor.

    You can just do something like this:

    #if DEBUG
      console.log("trace message");
    #endif
    

    or something like this

    #if DEBUG
    #define DEBUG_LOG(x) console.log(x);
    #else
    #define DEBUG_LOG(x) //console.log(x);
    #endif
    

    Then you can do this in your code

    DEBUG_LOG('put a random message her to confuse sys admins!')
    

    Or you use it's npm warapper: laudanumscript

    1. Create a sweetjs macro.

    I haven't been able to find conditional compilation with sweetjs, but I'm sure it wouldn't be too hard to implement it. The end syntax would be(or should be!) similar to cpp.