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.

Community
  • 1
  • 1
Dagg Nabbit
  • 75,346
  • 19
  • 113
  • 141
  • Worth mentioning that `console.log` is supported in **all modern browsers** even if the dev tools are not open. see http://caniuse.com/#feat=console-basic – John Henckel Jun 21 '16 at 14:13

10 Answers10

21

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
        };
    }
}());
dbrin
  • 15,525
  • 4
  • 56
  • 83
  • can you explain? I don't understand how this solves that problem. – chovy Aug 19 '13 at 23:00
  • 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? – chovy Aug 19 '13 at 23:04
  • 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. – dbrin Aug 19 '13 at 23:06
  • 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`; – chovy Aug 19 '13 at 23:09
  • sure.. i guess you can do it, but what does it buy you? you loose other methods. – dbrin Aug 19 '13 at 23:21
  • i would rather type log() instead of console.log() – chovy Aug 20 '13 at 03:08
  • 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. – dbrin Aug 20 '13 at 03:24
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;
    }
}
ChrisN
  • 302
  • 2
  • 9
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.

Andy E
  • 338,112
  • 86
  • 474
  • 445
  • 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. – Lilith River Oct 15 '13 at 17:53
  • @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? – Andy E Oct 16 '13 at 08:04
  • 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. – Lilith River Oct 16 '13 at 21:36
  • @ComputerLinguist: interesting. If I get a bit of time I'll look into it, though I've never experienced this issue in the past. – Andy E Oct 17 '13 at 09:27
  • 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. – Sam Hasler Aug 08 '14 at 08:26
6

I like to use:

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

It works in all browsers and is easy to understand.

etoxin
  • 4,908
  • 3
  • 38
  • 50
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");
Ammar Hasan
  • 2,436
  • 16
  • 22
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;
        } 
   };
}
dougajmcdonald
  • 19,231
  • 12
  • 56
  • 89
  • What happens if you do this in IE while the console is hidden (so no `window.console`) and then the console is opened later? – Dagg Nabbit Aug 25 '12 at 00:37
  • Untested but I'd imagine that the either the log original console function (defined here) would be overridden when IE defines its own. – dougajmcdonald Aug 25 '12 at 06:40
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().
Thomas W
  • 13,940
  • 4
  • 58
  • 76
  • 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. – Dagg Nabbit May 02 '13 at 04:26
  • @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. – Thomas W May 03 '13 at 00:46
  • 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 3` – Dagg Nabbit May 03 '13 at 01:46
  • 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. – Thomas W May 03 '13 at 02:48
  • 1
    Sure there is: `console.log.apply(console, arguments)`, or the weird workaround for IE in my question. – Dagg Nabbit May 03 '13 at 02:50
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

Nathan Long
  • 122,748
  • 97
  • 336
  • 451
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);
-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;
}
Kage
  • 81
  • 1
  • 2
  • 11
  • 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? – Dagg Nabbit Feb 08 '13 at 11:56
  • 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. – Kage Feb 27 '13 at 12:14