Update

I’ve made a significant update to this project which is targeted at primitive consoles (IE, Opera 11 and older, iOS 5 and older, and more). A separate blog post has more details or you can jump right ot the updated Github repo. The original post below still applies.

Many front-end web developers make use of the wonderful browser consoles that have matured in the past few years. While the tried-and-true console.log() often does the trick, its lack of support (particularly in IE) has led to the use of proxy functions, such as Paul Irish’s console.log wrapper and Ben Alman’s Debug() which prevent unsupportive browsers from throwing errors.

I had a need for logging data in every browser, not just ones that natively support console.log(). So I forked Paul’s function and expanded it to work with every browser I could test — IE6-9, Firefox 3.6 & 4+, Chrome 10+, Safari 5+, and Opera 11+.

This will be exhaustive, so you may want to jump directly to:

Live Demo Github Repo Source Code Minified Source Code

Current state of the console

Like Paul’s implementation, we’re simply going to create a function called log() and pass along any arguments it receives to console.log(). But before that we need to do a little setup to ensure that, when possible, console.log() is properly defined as a function in every browser.

First, let’s review console support in today’s browsers:

  • Chrome, Safari, Opera: native console.log()
  • Firefox: native console.log() with Firebug
  • IE9: native console.log(), but it needs a little nudge to turn on
  • IE8: While console.log() exists, it’s an object rather than a function — But we can still write to the console with a clever trick.
  • Others: We can inject Firebug Lite which will define console.log() as a function

Note that when I talk about IE I’m referring to the native versions — “IE8″ means IE8, not IE9 switched to IE8 mode with the Developer Tools.

IE's developer toolbar tricking you into thinking that it's running as a different version

IE9

Before we build log() we need to tell IE9 to use its own console and to consider console.log() to be a function. Many thanks to Andy E for this piece.

if (typeof console.log == "object" && Function.prototype.bind && console) {
	["log","info","warn","error","assert","dir","clear","profile","profileEnd"]
		.forEach(function (method) {
			console[method] = this.call(console[method], console);
		}, Function.prototype.bind);
}

For this particular case we really only need to define the log method, but it can’t hurt to flip the switch on the others as well.

Modern browsers

Now we can define log(). There’s no sense in re-inventing a very round wheel, so I’m just going to fork Paul Irish’s original console.log wrapper.

if (!window.log) {
	window.log = function () {
    log.history = log.history || [];  // store logs to an array for reference
    log.history.push(arguments);
	if (typeof console.log == 'function') {
		// Modern browsers
		if ((Array.prototype.slice.call(arguments)).length == 1 && typeof Array.prototype.slice.call(arguments)[0] == 'string') {
			console.log( (Array.prototype.slice.call(arguments)).toString() );
		}
		else {
			console.log( Array.prototype.slice.call(arguments) );
		}
	}
	// to be continued...

Notice that if() condition on line 7. Paul’s use of Array.prototype.slice.call() is great because you can pass along any amount and variety of objects, strings, functions, etc directly to the console. However, there’s one unfortunate side effect: in Firebug and Chrome (and possibly others), if the argument array’s sole content is a single string greater than 50 characters, it will be truncated faster than you can say Llanfairpwllgwyngyllgoger…wllllantysiliogogogoch.

So we’ll check the array length, and if it’s 1, and that 1 thing is a string, then we’ll convert the entire argument array to a string before passing it along. You could use console.error() to avoid truncation, but then it will look like an error when it’s not.

Opera

Update: Since I first wrote this post, Opera has updated Dragonfly such that the console now properly displays basic types — arrays are enumerated, objects are presented in a tree-like structure, etc. I have removed the if (window.opera) { section from the code. You can ignore this section and skip down to IE8. I’ll leave this section in place in case anyone has a need to test on Opera 11.

So that takes care of the modern browsers… except Opera. Opera seems to display the arguments as a whole Array() rather than splitting them apart. So if you passed several arguments you’d just see this:

Opera console showing "Array()" rather than the actual arguments

And that’s not too helpful. We can get around this with a rather boring while() loop to log each argument one by one.

var log = function () {
			// Modern browsers
			if (typeof console != 'undefined' && typeof console.log == 'function') {
				// Opera 11
				if (window.opera) {
					var i = 0;
					while (i < arguments.length) {
						console.log("Item " + (i+1) + ": " + arguments[i]);
						i++;
					}
				}
				// All other modern browsers
				else if ((Array.prototype.slice.call(arguments)).length == 1 && typeof Array.prototype.slice.call(arguments)[0] == 'string') {
					console.log( (Array.prototype.slice.call(arguments)).toString() );
				}
				else {
					console.log( Array.prototype.slice.call(arguments) );
				}
			}
			// to be continued...

Which results in:

Opera's console listing each argument on a separate line

It’s a little messy and if you use log() a lot you’ll find that your console will fill up fast, so you’ll have to decide how important Opera support is to you.

IE8

As we discussed earlier, IE8 has a console but your scripts can’t call console.log() directly, so we’re going to add a special condition for it. Andy’s aforementioned post included a similar bit of code for writing to IE8’s console, however in actual IE8 (as opposed to IE9 switched to IE8 mode) Function.prototype.bind is not defined. Instead, I’m using @kangax‘s alternative, Function.prototype.call.call().

			else if (!Function.prototype.bind && typeof console != 'undefined' && typeof console.log == 'object') {
				Function.prototype.call.call(console.log, console, Array.prototype.slice.call(arguments));
			}

One slight alteration: I changed the second part of the condition from console to typeof console != 'undefined'). It seems a little silly, but IE7 actually throws an error without the typeof check.

Older browsers

Now we’re going to inject Firebug Lite (“FBL”) for all the other browsers — namely, IE7 and older — by adding a <script> tag to the DOM. (Personally, I like FBL better than IE’s and Opera’s consoles anyway, so you may want to use this for any browser where (typeof console.log != 'function' || window.opera) is true.) Doing this will expose console.log() as a function, which means your calls to log() will end up being handled by the “Modern browsers” section in the beginning of the function.

You can pull it directly from getfirebug.com, or use a local copy (point to the /path/to/firebug-lite/build/firebug-lite.js file).

In my experience FBL takes a few moments to load and begin accepting console logs, even locally. To compensate for this delay, this piece of code is split into two conditions. The first one loads FBL and will only run the first time you call log():

		else {
			// Inject Firebug lite
			if (!document.getElementById('firebug-lite')) {
				// Include the script
				var script = document.createElement('script');
				script.type = "text/javascript";
				script.id = 'firebug-lite';
				script.src = '/lib/js/firebug-lite/build/firebug-lite.js';
				// If you want to expand the console by default, uncomment this line
				//document.getElementsByTagName('HTML')[0].setAttribute('debug','true');
				document.getElementsByTagName('HEAD')[0].appendChild(script);
				setTimeout(function(){log(Array.prototype.slice.call(arguments));}, 1500);
			}
			else {
				// Script was included but hasn't finished loading yet
				setTimeout(function(){log(Array.prototype.slice.call(arguments));}, 500);
			}
		}
	} // <= that's the end of log(), btw
}

The second condition is caught when your script calls log() before FBL has finished loading. Notice the id that I added to the <script> tag in the first condition — that tells us that FBL has begun loading. But since console.log is still not defined (otherwise we wouldn’t have gotten to this point in the code) we know that the script hasn’t fully loaded yet. Therefore we can simply use a setTimeout to try our call again every half second until it succeeds.

“It is so big”

Yeah, it is. Compared to Paul’s 6 lines, this version is a beast. But consider this: you log to the console during development. You’re not going to leave this in your production code (I hope!). So this function — and let’s face it, it’s not that long, especially when minified — is likely to be used only via localhost or over a LAN connection. I think the ability to log data in every browser is well worth a couple dozen lines of code tucked out of sight down at the bottom of dev.js.

I look forward to any suggestions or improvements you may have.

Live Demo Github Repo Source Code Minified Source Code

  • Pingback: log() – A lightweight wrapper for console.log « Paul Irish

  • http://whattheheadsaid.com Andy E

    An interesting read!

    When I referred to IE 8 in my post, I mentioned that you would need the compatibility implementation of `Function.prototype.bind`, which is found in many forms around the web; most notably on the Mozilla JS docs (MDC).

    Admittedly, when I wrote the post it was based around the SO questions that were about IE 9, so bind was the simplest approach. @kangax’s method works great for all versions of IE (by the way, yours needs to be apply.call instead of call.call) — there are some subtle differences, but I don’t think they would matter in any real-world situations.

    tl;dr version; if you’re only using IE 9 or you’re including shims for ECMA-262 5 methods then you may as well use the bind approach. Otherwise, use @kangax’s.

    • http://patik.com/ Craig

      Andy, I did see your reply to my comment and I understand what you meant — I meant to follow up but my reply snowballed into this big long post. I wanted to create a method that could be simply dropped into a project without any additional shims (Firebug Lite aside…). My apologies if it seems like I’m implying you were wrong — you were talking about IE9, I was talking about IE8.

      In retrospect I probably shouldn’t have referenced your blog at that point. For me, the real takeaway from your post — and the spark for this whole log() rewrite — was the other IE9 piece which ‘redefines’ console.log() as a function rather than an object.

      I’ll test apply.call and update my code.

  • Jimmy

    So, this is great, but I have having an issue where it will stop my development sites from rendering if the user is on FF 3.6 without the console enabled. I have been able to replicate this issue on a PC & a mac.

    • http://patik.com/ Craig

      Can you post an example for me to check out? I just tried the demo in Firefox 3.6.15, both with and without Firebug, and it worked fine.

      • Jimmy

        I will have to put something together, because the site I am working on with it is just on development & staging servers, so there is not a good way to get you to it. I think that the key difference is that you are not console.log’ing on the test page until the user preforms an action, and I have I am console.log’ing while the page is rendering, so my page dies. I will strip out the basic part of the site into a page & put somewhere. Follow back up shortly. Thanks for looking into this so quickly.

      • Jimmy

        So, I put together a very basic site that just had jquery 1.5.2, an h1 with “Hello World!”, and 2 console.logs, and it worked fine in 3.6 with the console disabled, so I started adding parts to the page to make it more like the real page… It is the jquery-ui tabs that is was really breaks. Go to http://test.spinen.com & you should see a very simple h1 with the example tabs from jquery-ui’s site. Then go to it with FF 3.6 & the console disabled–the tabs will not render. I maybe doing something stupid, but I think that I have it correct–as simple of a page it is, I don’t see how I could mess too much up. Anyhow, thanks for looking into this with me & I look forward in hear back from you.

      • Jimmy

        Sorry for loading up your post with comments, but one more thing…

        I made a second page that is exactly like my test without any console.log so that you can see that it renders in FF 3.6 with or without the console enabled. You can see it at http://test.spinen.com/noconsole.html

      • http://patik.com/ Craig

        Jimmy,

        The problem is that you’re calling console.log() which is not defined in FF3.6 unless you have Firebug installed and enabled. The call to console.log caused your function to throw an error so it never executed the tab-initialization code later on in the function.

        Note that in this blog post I’m defining a function called log() which will in turn call console.log iff it exists. The idea is to have a function that is safe to call without throwing in errors in browsers that don’t have a console.

        You can see a corrected version of your test page here. I added two lines to the first tab’s contents to show whether log() and console are defined.

  • Jimmy

    OK. I guess that I was thinking of it being a cross browser way of dealing with console.log similar to the way that firebugx.js works (http://code.google.com/p/fbug/source/browse/branches/firebug1.2/lite/firebugx.js?r=964), so that it uses console.log if it is there or refactors it to work for a different browser or worst case just makes it do nothing. I did not notice that your log function was a stand alone function & not a method of console that you were over writing the original method. Thanks for looking into this & clarifying the architecture of the function.

  • http://blog.getbyid.com/ doppelganger

    Hello,

    there is a big problem with the console override — overrided console.log() call will produce error displayed with the wrong reference to the code. Not the actual one which produced the error will be displayed, but the one where override is happened. Do you think there is a way to repair it somehow?

    • http://patik.com/ Craig Patik

      I’m not sure what you mean. This function does not actually override the browser’s native console.log, it just uses it if it’s available.

      Are you saying that if an error actually occurs at line 44, and log() is at line 66, then a logged error will appear to be coming from line 66? Either way, could you post a simple example?

      • Josh Sanderson

        I noticed the same limitation. And yes Craig, your summary is correct. When using console.log, the console displays the actual file and line number were the log message was generated. This is often useful. When using this code, the line number is always the same, located within consolelog.js.

        This might just be a necessary tradeoff when using a cross browser logging abstraction with all the functionality you are providing.

  • Ian Webb

    This is great! Would you mind adding a license and copyright statement, both as a LICENSE file to the Github and a header in the files? I’d suggest the MIT/BSD/GPL tri-license like Sizzle uses, that should give the most flexibility for anyone to use it. Thanks!

    • http://patik.com/ Craig Patik

      I plan to do so. In the meantime, consider it MIT/BSD/GPL.

      Also, I plan to add a variation which will allow you to simply use console.log() in your code instead of just log(). The effect would be the same (it will still write to your console or load Firebug), but some might prefer to use the standard “console.log” when coding. I also need to check whether Opera has updated the way they display logged items and give IE10 a shot.

  • Anonymous

    I just wanted to say THANK YOU! :) You saved my day!

  • Jørn Vegard Røsnes

    Hi, you should avoid Array.forEach, not supported in all IE7/IE8.
    ref http://stackoverflow.com/questions/2790001/fixing-javascript-array-functions-in-internet-explorer-indexof-foreach-etc

    • http://patik.com/ Craig Patik

      I am only using Array.forEach in the block of code that will run only under IE9. If you want added protection, you can always include this polyfill from Mozilla’s developer docs which will enable the method for IE7/8 and other older browsers.

  • http://www.htmltweaks.com/ Stephan Wagner

    Top Script, but as you said, it’s quite some code, and if you won’t leave it in your production code, you will have an error if you’d forgotten to remove a log(). 
    I’ve wrote this simple wrapper, with which i’m debugging quite happily: var log = function(msg, force) {
    force ? alert(msg) : (console && console.log ? console.log(msg) : null);
    };It simply logs the message if there is a console and if there is none it will be ignored or you can force an alert() popup.I’ve wrote a little article, if anyone cares:http://www.htmltweaks.com/Debugging_JavaScript_with_the_Console

  • CaptPolymath

    If you just want a “safe” console.log() function, and don’t care about enabling it in IE, etc, you can test for the console object, and if it doesn’t exist, create it with an empty log() function:

    if (typeof console === “undefined”){
        console = function(){
            this.log = function(){
                return;
            }
        }
    }

    This way you can use console.log() any time you want, without any special wrapper functions or extra lines of code with each use. In unsupported browsers, this function simply returns.

  • CaptPolymath

    Sorry, there’s a bug in my own code. Don’t know how I missed this. To properly declare console as an object in unsupported browsers, place this before any calls to console.log():

    if (typeof console === “undefined”){
    console={};
    console.log = function(){
    return;
    }
    }

  • Mike Taylor

    You should update the part about Opera–Dragonfly has changed in how it handles the arguments now (and it looks nothing like your screenshot anymore :P).

    • http://patik.com/ Craig Patik

      I’ve removed the Opera block from the code. Updating the blog momentarily.

  • Pingback: Detailed console logging « console.blog()

  • Brian J. Ackermann

    I’m finding that log() still generates an error on IE9 (probably others too), when added to an asp.net mvc4 jquerymobile application. I’m not sure where the failure comes from though….

  • Pingback: 如何避免console.log()引起兼容问题… | 咖啡薄荷

  • Pingback: Something you do not want to do in IE8 | Dreaming..*

  • Guest

    This was asked on stackoverflow:

    http://stackoverflow.com/q/14086675/866206

  • KingKongFrog

    This was asked on stackoverflow.com:

    http://stackoverflow.com/q/14086675/866206

  • Some Name

    Why you made this? I don’t have any wish develop my app in IE 6….