How to safely wrap `console.log`?
Asked Answered
E

10

48

Suppose I want to include some calls to console.log for some legitimate production reason, say for something like a unit test harness. Obviously I would not want this to throw a premature exception if the browser doesn't have a console, or if no console is present.

What's the best way to create a simple log function to log stuff to the console, or silently fail without error if no console is present?

The accepted answer to the question linked above:

var log = Function.prototype.bind.call(console.log, console);
log.apply(console, ["this", "is", "a", "test"]);

Can this log function be called normally on IE, and the use of apply here is just to show it's possible? And, I assume from the linked question that this will fail if IE's console is closed when it runs, so log won't work even after the console opens, correct? If that's wrong, can someone explain how it works?

This ycombinator article seems relevant. Are they are talking about the same IE behavior as the question linked above?

Function.prototype.apply.apply(console.log, [console, arguments]);

Works both on IE9 broken console.log, and regular console.log from other vendors. Same hack as using Array.prototype.slice to convert arguments into a real array.

This works nicely in my chrome console.

function echo(){
  Function.prototype.apply.apply(console.log, [console, arguments]);
}

Simplified:

function echo(){
  Function.apply.call(console.log, console, arguments);
}

Add a check and return:

function echo(){
  return window.console && console.log &&
         Function.apply.call(console.log, console, arguments);
}

The example above looks adequate to me. I don't have IE on hand to test it, though. Is this a reasonable approach for safely wrapping console.log?


More questions

Following the link in nav's answer below, we see the code:

Function.prototype.call.call(console.log, console,
    Array.prototype.slice.call(arguments));

What is the purpose of converting arguments to an array in this case? I guess it must fail in some browser if you don't do this? And, opera weird behavior and console-less browsers aside, shouldn't something like this pretty much work for every other browser as well? And does prototype serve a purpose in the above examples, or are we just being pedantic... Function.call.call or Object.call.call or for that matter isNaN.call.call seem to work just as well as Function.prototype.call.call.

Erminois answered 9/1, 2012 at 8:32 Comment(1)
Worth mentioning that console.log is supported in all modern browsers even if the dev tools are not open. see caniuse.com/#feat=console-basicNewhall
H
7

Can this log function be called normally on IE, and the use of apply here is just to show it's possible?

Yes, and yes. That particular example was aimed squarely at the "is it a real function" part of the linked question.

And, I assume from the linked question that this will fail if IE's console is closed when it runs, so log won't work even after the console opens, correct?

Correct. As explained in my answer on that question, the console object is not exposed until the first time the developer tools are opened for a particular tab. Most developers use a console shim, in which case the Function#bind approach becomes a little obsolete because you may as well use the Function#apply.apply method.

What is the purpose of converting arguments to an array in this case?

There isn't one, it's redundant. Unless it's a custom log implementation, in which case the developer may have a reason to convert an arguments object to an array.

And does prototype serve a purpose in the above examples, or are we just being pedantic...

Well, yes and no. Some developer may have unwittingly changed Function.call to a custom function or value. Of course, they could break Function.prototype.call too, but this is far less likely to happen by accident.

Harmonium answered 9/1, 2012 at 19:59 Comment(5)
I'm afraid that this may not be accurate. On IE7 and IE8 (both through user reports and BrowserStack testing), console.log can be defined as an empty object, not as a function, and calling .apply will result in an error.Panelboard
@ComputerLinguist: when you say "console.log can be defined as an empty object", do you mean defined like that by the browser? What specific error message do you/your users get?Harmonium
I think the IE7 & IE8 developer tools themselves are responsible (whether or not they are in use - just having them installed is enough). Inspecting the value of 'console.log' shows an empty object. Trying to execute it causes an exception.Panelboard
@ComputerLinguist: interesting. If I get a bit of time I'll look into it, though I've never experienced this issue in the past.Harmonium
re: "the Function#bind approach becomes a little obsolete because you may as well use the Function#apply.apply method" bind will show the correct line number in dev tools; whereas a function wrapping an invocation using apply.apply will always show the line number that apply.apply is on.Machicolation
B
20

The problem with wrappers is that they will obfuscate file name and line number of the source of the log message.

Simple IE7 and below shim that preserves Line Numbering for other browsers:

/* console shim*/
(function () {
    var f = function () {};
    if (!window.console) {
        window.console = {
            log:f, info:f, warn:f, debug:f, error:f
        };
    }
}());
Bareilly answered 17/6, 2013 at 17:51 Comment(7)
can you explain? I don't understand how this solves that problem.Brunt
nevermind. i see it just polyfills console.log etc. So there's no way to do a short cut "log(message)" without loosing line number of the invocation?Brunt
right, if you wrap the console than the wrapper function is what shows up. I use console.log for a bit of debugging - so ability to click through to the source line is a must.Bareilly
I hear ya. I went looking for a wrapper that would preserve line number, and found your answer. It really sucks having to type 'console.log' -- what if we just did window.log = console.log;Brunt
sure.. i guess you can do it, but what does it buy you? you loose other methods.Bareilly
i would rather type log() instead of console.log()Brunt
sure, however we are supposed to scrub the log statements out of production code. and there are log removers out there as part of the build process. i am certain they are looking for console.log lines.Bareilly
H
16

Sorry, there was a bug in my post. Don't know how I missed it.

The PROPER way to create a global console object, if it does not exist:

if (typeof console === "undefined"){
    console={};
    console.log = function(){
        return;
    }
}
Heliocentric answered 22/7, 2012 at 18:43 Comment(1)
There are more functions that people might be using though, e.g. assert, warn etc.Ringmaster
H
7

Can this log function be called normally on IE, and the use of apply here is just to show it's possible?

Yes, and yes. That particular example was aimed squarely at the "is it a real function" part of the linked question.

And, I assume from the linked question that this will fail if IE's console is closed when it runs, so log won't work even after the console opens, correct?

Correct. As explained in my answer on that question, the console object is not exposed until the first time the developer tools are opened for a particular tab. Most developers use a console shim, in which case the Function#bind approach becomes a little obsolete because you may as well use the Function#apply.apply method.

What is the purpose of converting arguments to an array in this case?

There isn't one, it's redundant. Unless it's a custom log implementation, in which case the developer may have a reason to convert an arguments object to an array.

And does prototype serve a purpose in the above examples, or are we just being pedantic...

Well, yes and no. Some developer may have unwittingly changed Function.call to a custom function or value. Of course, they could break Function.prototype.call too, but this is far less likely to happen by accident.

Harmonium answered 9/1, 2012 at 19:59 Comment(5)
I'm afraid that this may not be accurate. On IE7 and IE8 (both through user reports and BrowserStack testing), console.log can be defined as an empty object, not as a function, and calling .apply will result in an error.Panelboard
@ComputerLinguist: when you say "console.log can be defined as an empty object", do you mean defined like that by the browser? What specific error message do you/your users get?Harmonium
I think the IE7 & IE8 developer tools themselves are responsible (whether or not they are in use - just having them installed is enough). Inspecting the value of 'console.log' shows an empty object. Trying to execute it causes an exception.Panelboard
@ComputerLinguist: interesting. If I get a bit of time I'll look into it, though I've never experienced this issue in the past.Harmonium
re: "the Function#bind approach becomes a little obsolete because you may as well use the Function#apply.apply method" bind will show the correct line number in dev tools; whereas a function wrapping an invocation using apply.apply will always show the line number that apply.apply is on.Machicolation
A
6

I like to use:

'console' in window && console.log("Boom!");

It works in all browsers and is easy to understand.

Aware answered 21/6, 2013 at 4:10 Comment(0)
N
3

Try using the code snippet below... (this is my preferred approach because it makes you independent of window.console)

var logger = (function (c) {
    "use strict";
    var m = {
        log: function (a, t) {
            if (!c) { return; /* return or call your custom function here */ }
            var l = c.log,
                f = t === undefined ? l : (this.__dict__[t] || l);
            f.apply(c, a)
        },
        __dict__: {
            "trace": c.trace,
            "debug": c.debug,
            "info": c.info,
            "warn": c.warn,
            "error": c.error
        }
    };

    return {
        trace: function () { m.log(arguments, "trace"); },
        debug: function () { m.log(arguments, "debug"); },
        info: function () { m.log(arguments, "info"); },
        warn: function () { m.log(arguments, "warn"); },
        error: function () { m.log(arguments, "error"); },
        log: function () { m.log(arguments, undefined); }
    };
}(window.console))

So you may now try these in your code and see the result

logger.info("Hello");
logger.trace("Hello");
logger.debug("Hello");
logger.warn("Hello");
logger.error("Hello");
logger.log("Hello");
Noble answered 18/2, 2014 at 14:26 Comment(0)
C
1

As a slight variation on Chris' answer, simply define 'log' as a property of 'console' with an empty function:

if (typeof console === "undefined") {
    console = {
        log: function () {
            return;
        } 
   };
}
Condense answered 8/8, 2012 at 13:9 Comment(2)
What happens if you do this in IE while the console is hidden (so no window.console) and then the console is opened later?Erminois
Untested but I'd imagine that the either the log original console function (defined here) would be overridden when IE defines its own.Condense
T
0

Paul Irish has a nice light wrapper/replacement for console.log().

http://paulirish.com/2009/log-a-lightweight-wrapper-for-consolelog/

Advantages:

  • Prevent errors if a console isn’t around (i.e. IE)
  • Maintains a history of logs, so you can look in the past if your console is added afterwards (e.g. firebug lite)
  • Light & simple.
  • Very quick to type -- log() or window.log().
Tetragonal answered 2/5, 2013 at 3:43 Comment(5)
Can you explain why this might be preferable to the solution given at the end of the question? I think I'd prefer to keep the behavior identical to console.log, rather than wrapping all the arguments in an array, unless there's some compelling reason to use the code you linked.Erminois
@Dagg Nabbit: The behaviour is identical to console.log, that's one of the points. (Console.log can accept & log multiple arguments). The 'arguments' pseudo-array is the Javascript way to access arbitrary arguments & pass these to the 'real' console.log, if that function is present.Tetragonal
It's not identical, though. It wraps the arguments in an array, try it. log(1,2,3) -> [1, 2, 3] ... console.log(1,2,3) -> 1 2 3Erminois
Thanks Dagg - well, I guess then there's probably no way to pass multi-args to console.log() without them appearing as such. Since I need a log wrapper & like not having to manually build log strings, it's still the one I'm using.Tetragonal
Sure there is: console.log.apply(console, arguments), or the weird workaround for IE in my question.Erminois
S
0

Use Consolation

My ridiculously overengineered console:

  • prevents errors if there's no console
  • prevents logging in production if you left console.log statements in your code
  • supports console.error, console.group, and all such other methods
  • still gives you backtraces to your log statements

It's amazing.

But really, you just shouldn't leave console statements lying around in your code.

Behold and tremble! Presenting: Consolation.js

Sklar answered 28/8, 2013 at 16:53 Comment(0)
L
0

coffeescript:

empty_function = ->
  return
if !window.console?
  window.console = {}
  for fn in ['log', 'info', 'warn', 'debug', 'error']
    if (typeof window.console[fn] isnt 'function')
      window.console[fn] = empty_function

js:

(function() {
  var empty_function, fn, i, len, ref;

  empty_function = function() {};

  if (window.console == null) {
    window.console = {};
    ref = ['log', 'info', 'warn', 'debug', 'error'];
    for (i = 0, len = ref.length; i < len; i++) {
      fn = ref[i];
      if (typeof window.console[fn] !== 'function') {
        window.console[fn] = empty_function;
      }
    }
  }

}).call(this);
Liaoyang answered 22/1, 2017 at 12:15 Comment(0)
G
-1

My solution is a little different. I create a standard shortcut for all console.log calls: In my case kag(whatever I want to report in the console).

I test for IE, if IE I send the results to an alert box. If Chrome then displays in the console. This also means IE will always work even in console is closed:

Code:

var iever = getInternetExplorerVersion();
if(iever>-1){
function kag(params){
    alert(params);
}
} else {
var kag = console.log.bind(console, "REPORT: ");
}

function getInternetExplorerVersion() {
var rv = -1;
if (navigator.appName == 'Microsoft Internet Explorer') {
var ua = navigator.userAgent;
var re  = new RegExp("MSIE ([0-9]{1,}[\.0-9]{0,})");
if (re.exec(ua) != null){
  rv = parseFloat( RegExp.$1 );
}
}
return rv;
}
Glutamate answered 6/2, 2013 at 9:31 Comment(2)
What if I want to test things in IE using the console? What if another UA that doesn't identify itself as IE has the same problem as IE?Erminois
Didn't you see that the console.log message is shown as a standard alert in IE. I test my apps in IE, Chrome, Safari, Firefox and Opera. Only IE seems to suffer with the console.log issue.Glutamate

© 2022 - 2024 — McMap. All rights reserved.