34

I have a script that I can't change that makes a lot of console.log calls. I want to add another layer and respond if the calls contain certain strings. This works in Firefox, but throws an "Illegal invocation" error in Chrome on the 4th line:

var oldConsole = {};
oldConsole.log = console.log;
console.log = function (arg) {
    oldConsole.log('MY CONSOLE!!');
    oldConsole.log(arg);
}

Any ideas how to get around that? I also tried cloning the console...

BuZZ-dEE
  • 6,075
  • 12
  • 66
  • 96
sprugman
  • 19,351
  • 35
  • 110
  • 163

7 Answers7

53

You need to call console.log in the context of console for chrome:

(function () {
  var log = console.log;
  console.log = function () {
    log.call(this, 'My Console!!!');
    log.apply(this, Array.prototype.slice.call(arguments));
  };
}());

Modern language features can significantly simplify this snippet:

{
  const log = console.log.bind(console)
  console.log = (...args) => {
    log('My Console!!!')
    log(...args)
  }
}
zzzzBov
  • 174,988
  • 54
  • 320
  • 367
  • nice... been looking for something like this. Can this be used to decorate any function in javascript? – Shane Feb 09 '12 at 18:46
  • 2
    @Shane, this is the basic pattern for intercepting a function call, however I'd recommend against using it unless absolutely necessary. It's much better to just modify the function directly, or use OOP concepts. – zzzzBov Feb 09 '12 at 19:06
  • building a framework where there's a lot of run time assembly of components, this would be a handy tool for an externally driven approach to debugging components or modifying existing components with plugins, etc. Is there a performance hit for this approach? Just curious what the drawbacks would be. I won't be modifying intrinsic functions, only framework component methods. – Shane Feb 10 '12 at 03:35
  • every function you call is a "performance hit", but asynchronous JavaScript firing sporadically shouldn't bog down significantly. – zzzzBov Feb 10 '12 at 03:44
  • looks like function chaining is significantly faster. Just made a test. http://jsperf.com/wrapped-function – Shane Feb 10 '12 at 04:30
  • Looks like I spoke too soon, firefox seems to struggle with it a bit, but Chrome and Safari don't show much of a difference. – Shane Feb 10 '12 at 04:35
  • Just ran it in everything I've got, looks like it's good enough to use, thanks again man. This makes life a lot easier for some of the weird little problems we are dealing with. :) – Shane Feb 10 '12 at 04:44
  • 2
    this doesnt accurately report line numbers – Barry Chapman Mar 19 '19 at 05:38
  • 1
    The `Array.prototype.slice.call(arguments)` is redundant, unless [the browser doesn't support ECMAScript 5.1](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/apply#Browser_compatibility). You can just call `log.apply(this, arguments);` – Patrick Roberts Jan 02 '20 at 18:44
  • @PatrickRoberts, this answer is from 2012 and is almost 8 years old, so it can be significantly improved for modern browsers. – zzzzBov Jan 02 '20 at 19:17
  • 1
    @zzzzBov I understand your answer is from 2012, but `Array.prototype.slice.call(arguments)` would have been redundant for most browsers even then. I appreciate the update in response though. – Patrick Roberts Jan 02 '20 at 19:29
8

I know it's an old post but it can be useful anyway as others solution are not compatible with older browsers.

You can redefine the behavior of each function of the console (and for all browsers) like this:

// define a new console
var console = (function(oldCons){
    return {
        log: function(text){
            oldCons.log(text);
            // Your code
        },
        info: function (text) {
            oldCons.info(text);
            // Your code
        },
        warn: function (text) {
            oldCons.warn(text);
            // Your code
        },
        error: function (text) {
            oldCons.error(text);
            // Your code
        }
    };
}(window.console));

//Then redefine the old console
window.console = console;
Ludovic Feltz
  • 11,416
  • 4
  • 47
  • 63
  • cleanest answer i've seen yet for this type of question – Luiz Aug 15 '18 at 21:41
  • [Window.console is read-only property](https://developer.mozilla.org/en-US/docs/Web/API/Window/console) How can you override it? – TheGuy Aug 29 '18 at 13:14
5

You can also use the same logic, but call it off the console object so the context is the same.

if(window.console){
  console.yo = console.log;
  console.log = function(str){
    console.yo('MY CONSOLE!!');
    console.yo(str);
  }
}
rgthree
  • 7,217
  • 17
  • 21
3

With ES6 new spread operator you can write it like this

(function () {
  var log = console.log;
  console.log = function () {
    log.call(this, 'My Console!!!', ...arguments);
  };
}());
Devnegikec
  • 631
  • 5
  • 11
1

Can be simply:

console.log = (m) => terminal.innerHTML = JSON.stringify(m)
#terminal {background: black; color:chartreuse}
$ > <span id="terminal"></span>
<hr>        
<button onclick="console.log('Hello world!!')">3V3L</button>
<button onclick="console.log(document)">3V3L</button>
<button onclick="console.log(Math.PI)">3V3L</button>
NVRM
  • 11,480
  • 1
  • 88
  • 87
1

To fully intercept the console, we can override every methods :

const bindConsole=function(onMessage){
    Object.keys(console)
    .filter(type=>typeof(console[type])==='function')// *1
    .forEach(type=>{
        let _old=console[type];
        console[type] = function (...args) {
            _old.apply(console,args);
            onMessage(type,args);// *2
        };
    });
};

For old browsers :

var bindOldConsole=function(onMessage){
    for(var k in console){// *1
        if(typeof(console[k])=='function')(function(type){
            var _old=console[type];
            console[type] = function () {
                _old.apply(console,arguments);
                onMessage(type,arguments);
            };
        })(k);
    }
};

  • *1 Looks like console has only methods but better be sure.

  • *2 You may block cyclic calls to console from onMessage by replacing this line with :

if(!isCyclic())onMessage(type,args);

// es6. Not sure concerning old browsers :(
const isCyclic=function (){
    let erst=(new Error()).stack.split('\n');
    return erst.includes(erst[1],2);
};
yorg
  • 600
  • 5
  • 7
0

Since I cannot comment (yet) on @ludovic-feltz answer, here is his answer corrected to allow string interpolation in the console :

// define a new console
var console = (function(oldCons){
    return {
        log: function(...text){
            oldCons.log(...text);
            // Your code
        },
        info: function (...text) {
            oldCons.info(...text);
            // Your code
        },
        warn: function (...text) {
            oldCons.warn(...text);
            // Your code
        },
        error: function (...text) {
            oldCons.error(...text);
            // Your code
        }
    };
}(window.console));

//Then redefine the old console
window.console = console;
marrco
  • 128
  • 7