Custom console log function, a console.log wrapper

23.9k views Asked by At
function log( msgOrObj ){
    if(dev_mode){
        console.log({
            'message': msgOrObj,
            'caller': arguments.callee.caller.toString()
        });
    }
}

So, I have attempted to write a simple custom console log function (as above). However I am struggling to find which file and line the caller came from. The most I can see is the function that called it.

Has anyone done anything similar? Or is this even possible?

example used in somescript.js from line 70:

log('some very important message!')
8

There are 8 answers

0
AudioBubble On BEST ANSWER

So, this is what I went for in the end (where shout is a bespoke function only running in dev mode):

function log( msgOrObj ){
    if(dev_mode){
        if( typeof(window.console) != 'undefined' ){
            try {  invalidfunctionthrowanerrorplease(); }
            catch(err) {  var logStack = err.stack;  }
            var fullTrace = logStack.split('\n');
            for( var i = 0 ; i < fullTrace.length ; ++i ){
                fullTrace[i] = fullTrace[i].replace(/\s+/g, ' ');
            }
            var caller = fullTrace[1],
                callerParts = caller.split('@'),
                line = '';

            //CHROME & SAFARI
            if( callerParts.length == 1 ){
                callerParts = fullTrace[2].split('('), caller = false;
                //we have an object caller
                if( callerParts.length > 1 ){
                    caller = callerParts[0].replace('at Object.','');
                    line = callerParts[1].split(':');
                    line = line[2];
                }
                //called from outside of an object
                else {
                    callerParts[0] = callerParts[0].replace('at ','');
                    callerParts = callerParts[0].split(':');
                    caller = callerParts[0]+callerParts[1];
                    line = callerParts[2];
                }
            }
            //FIREFOX
            else {
                var callerParts2 = callerParts[1].split(':');
                line = callerParts2.pop();
                callerParts[1] = callerParts2.join(':');
                caller = (callerParts[0] == '') ? callerParts[1] : callerParts[0];
            }
            console.log( ' ' );
            console.warn( 'Console log: '+ caller + ' ( line '+ line +' )' );
            console.log( msgOrObj );
            console.log({'Full trace:': fullTrace });
            console.log( ' ' );
        } else {
            shout('This browser does not support console.log!')
        }
    }
}

log() when declared before the rest of the application can be called anywhere from within the app and give the developer all the information required plus will not run out of dev mode.

(http://webconfiguration.blogspot.co.uk/2013/12/javascript-console-log-wrapper-with.html)

0
Tsanyo Tsanev On

Instead of using arguments you can do

function log( msg ) {
    if (dev_mode) {
        var e = new Error(msg);
        console.log(e.stack);
    }
}

This will show you the order in which all the functions were called (including line numbers and files). You can just ignore the first 2 lines of the stack (one will contain the error message and one will contain the log function since you are creating the error object within the function).

If you want a more robust logging - use A proper wrapper for console.log with correct line number? as @DoXicK suggested

0
Joe On

Yes but it's very hacky and not cross browser-safe. You can use this as a starting point. It borrows from this answer.

window.trace = function stackTrace() {
    var err = new Error();
    return err.stack;
}

window.my_log = function (x) {
    var line = trace();
    var lines = line.split("\n");
    console.log(x + " " + lines[2].substring(lines[2].indexOf("("), lines[2].lastIndexOf(")") + 1))
}


window.my_log("What light through yonder window breaks?")

Produces:

What light through yonder window breaks? (<anonymous>:2:42) 
0
milks On

The only way I've seen to reliably extract this kind of info is to throw an error and then extract the caller info from the stack trace, something along the lines of:

function log( msgOrObj ){
    if(dev_mode){

        try {
            in_val_id(); // force an error by calling an non-existent method
        catch(err) {
            // some regex/string manipulation here to extract function name
            // line num, etc. from err.stack
            var caller = ...
            var lineNo = ...
        }

        console.log({
            'message': msgOrObj,
            'caller': caller,
            'lineNo': lineNo
        });
    }
}

The stack in Chrome is in this form:

ReferenceError: in_val_id is not defined
at log (<anonymous>:4:13)
at <anonymous>:2:14
at <anonymous>:2:28
at Object.InjectedScript._evaluateOn (<anonymous>:581:39)
at Object.InjectedScript._evaluateAndWrap (<anonymous>:540:52)
at Object.InjectedScript.evaluate (<anonymous>:459:21) 

you can extract the function name with:

caller = err.stack.split('\n')[3].split('at ')[1].split(' (')[0];

using a regex here might be more performant. You'll probably need different approaches to extract this info with different browsers.

A word of warning though; throwing and handling errors is expensive so outputting a lot of log messages in this way is likely to impact on general performance, though this may be acceptable if it is specifically for a debug mode

0
NodeNodeNode On

There are a couple options to quickly go about this.

1 - Use console.error Not very convenient, actual errors will go unnoticed and seeing a lot of red in your console output may have a negative impact on your morale. In short - don't use, unless it's for a very small script or some test

2 - Add your log method to the prototype of Object to get the current scope/ module name/ etc. Much more flexible and elegant.

Object.prototype.log = function(message){
    console.log({
        'message': message,
        'caller': this, 
        'stack':arguments.callee.caller.toString()
    });
};

Use (anywhere) as:

this.log("foo");

You could add the techniques from this thread to get exact function name inside your object, as so:

    var callerFunc = arguments.callee.caller.toString();
    callerFuncName = (callerFunc.substring(callerFunc.indexOf("function") + 9, callerFunc.indexOf("(")) || "anoynmous");

Yet make sure your scope is named... forcing you to go from this:

Module.method = function(){}

To this:

Module.method = function method(){}

As for line numbers, calling (new Error()) will give you access to the line number where it was called - and not even on all browsers.

Creating an elegant debugging function is a piece of work

As much as I hate to admit it, the other answer implying reg-exps over a try result seems to be the faster cure for your problem.

0
Andrew S On

I use this in Node and its particularly effective. Console.log is just a function it can be reassigned as well as stored for safe keeping and returned back after we are done. I've no reason to believe this would not work in a browser too.

//Store console.log function in an object so
//we can still use it.
theConsole = {};
theConsole.log = console.log;

//This function is called when console.log occurs
//arguments[0] is what would normally be printed.
console.log = function(){
    theConsole.log(">" + arguments[0]);
}

//Call our console.log wrapper
console.log("Testing testing 123");
console.log("Check one two");

//Put back the way it was
console.log = theConsole.log;
console.log("Now normal");
0
Oliveira On

Try this

window.log = (() => {
  if (dev_mode) {
    return console.log;
  } else return () => {};
})();
1
Ani On

It seems you all are struggling too much. I have a simple one-line solution:-

//Just do this, that I have done below: HAVE FUN

var log=console.log;

log(`So this way is known as Aniket's way.`);
log(`Don't be too serious this is just the fun way of doing same thing`);

log(`Thank You`)