Skip to content

Instantly share code, notes, and snippets.

@bgrins
Last active August 1, 2023 16:32
Show Gist options
  • Star 95 You must be signed in to star a gist
  • Fork 22 You must be signed in to fork a gist
  • Save bgrins/5108712 to your computer and use it in GitHub Desktop.
Save bgrins/5108712 to your computer and use it in GitHub Desktop.
Prevent errors on console methods when no console present and expose a global 'log' function.

Javascript log Function

Every time I start a new project, I want to pull in a log function that allows the same functionality as the console.log, including the full functionality of the Console API.

There are a lot of ways to do this, but many are lacking. A common problem with wrapper functions is that the line number that shows up next to the log is the line number of the log function itself, not where log was invoked. There are also times where the arguments get logged in a way that isn't quite the same as the native function.

This is an attempt to once and for all document the function that I pull in to new projects. There are two different options:

  • The full version: Inspired by the plugin in HTML5 Boilerplate. Use this if you are writing an application and want to create a window.log function. Additionally, this will set all the console methods to an empty function if they don't exist to prevent errors from accidental console.log calls left in your code. Put this snippet at the top of the rest of your scripts - it will need to be evaluated first thing to work.
  • The portable version: Use this if you want to use it inside a plugin and/or don't want to muck with the global namespace. Just drop it at the bottom of your plugin and log away. log can be called before the function declaration, and will not add anything to the window namespace or modify the console objects.

View a live demo here: http://jsfiddle.net/bgrins/MZWtG/

// Full version of `log` that:
// * Prevents errors on console methods when no console present.
// * Exposes a global 'log' function that preserves line numbering and formatting.
(function () {
var method;
var noop = function () { };
var methods = [
'assert', 'clear', 'count', 'debug', 'dir', 'dirxml', 'error',
'exception', 'group', 'groupCollapsed', 'groupEnd', 'info', 'log',
'markTimeline', 'profile', 'profileEnd', 'table', 'time', 'timeEnd',
'timeStamp', 'trace', 'warn'
];
var length = methods.length;
var console = (window.console = window.console || {});
while (length--) {
method = methods[length];
// Only stub undefined methods.
if (!console[method]) {
console[method] = noop;
}
}
if (Function.prototype.bind) {
window.log = Function.prototype.bind.call(console.log, console);
}
else {
window.log = function() {
Function.prototype.apply.call(console.log, console, arguments);
};
}
})();
// Portable version of `log` that:
// * Doesn't expose log to the window.
// * Allows log() to be called above the function declaration.
// Because of this, you can just throw it in the bottom of a plugin and it won't mess with global scope or clutter your code
(function() {
// Your code here... log() away
function log () {
/* jshint -W021 */
if (window.console) {
// Only run on the first time through - reset this function to the appropriate console.log helper
if (Function.prototype.bind) {
log = Function.prototype.bind.call(console.log, console);
}
else {
log = function() {
Function.prototype.apply.call(console.log, console, arguments);
};
}
log.apply(this, arguments);
}
}
})();
// All at once (minified version):
function log(){/* jshint -W021 */if(window.console){if(Function.prototype.bind)log=Function.prototype.bind.call(console.log,console);else log=function(){Function.prototype.apply.call(console.log,console,arguments);};log.apply(this,arguments);}}
/* Log statements with `log` and `console.log` should appear the same, and show the correct line numbers */
function Test() {}
Test.prototype.extraMethod = function() { }
Test.methodAttachedToFunction = function(withParam) { }
function logAllWithWrapper() {
log("A single string");
log(123);
log(["An", "Array", "Of", "Strings"]);
log("The %s jumped over %d tall buildings", "person", 100);
log("The", "person", "jumped over ", 100, " tall buildings");
log("The object %o is inspectable!", { person: { jumpedOver: [100, "tall buildings"]}});
log('%cThis is red text on a green background', 'color:red; background-color:green');
log({ an: "obj", withNested: { objects: { inside: "of", it: true }}});
log(Test, Test.methodAttachedToFunction);
log(new Test());
log(document);
log(document.body);
log(document.body.childNode);
}
function logAllWithNative() {
console.log("A single string");
console.log(123);
console.log(["An", "Array", "Of", "Strings"]);
console.log("The %s jumped over %d tall buildings", "person", 100);
console.log("The", "person", "jumped over ", 100, " tall buildings");
console.log("The object %o is inspectable!", { person: { jumpedOver: [100, "tall buildings"]}});
console.log('%cThis is red text on a green background', 'color:red; background-color:green');
console.log({ an: "obj", withNested: { objects: { inside: "of", it: true }}});
console.log(Test, Test.methodAttachedToFunction);
console.log(new Test());
console.log(document);
console.log(document.body);
console.log(document.body.childNode);
}
log("\n-----------");
log("Logging all expressions with wrapper log function");
log("-----------\n");
logAllWithWrapper();
log("\n-----------");
log("Logging all expressions with native console.log function");
log("-----------\n");
logAllWithNative();
@dustingetz
Copy link

as a require.js module:

define([], function() {
    "use strict";

    var exports = {};

    /**
     * These incantations will preserve call-site line number in the console log.
     * The console API must exist on the window; if it doesn't exist, it must be shimmed.
     *
     * See: https://gist.github.com/bgrins/5108712
     *
     * Also see the console API: http://getfirebug.com/wiki/index.php/Console_API
     *
     * TODO - lazy vararg formatting
     */
    exports.log = (function() {
        var noop = function() {};

        var log;
        log = (window.console === undefined) ? noop
            : (Function.prototype.bind !== undefined) ? Function.prototype.bind.call(console.log, console)
            : function() { Function.prototype.apply.call(console.log, console, arguments); };

        return log;

    })();

    return exports;
});

@feross
Copy link

feross commented Apr 25, 2013

Instead of:
window.log = Function.prototype.bind.call(console.log, console);

you should use the simpler:
window.log = console.log.bind(console);

The .bind.call is excessive and confusing, imo.

Otherwise, great snippet!

@jmellicker
Copy link

What if you wanted to log something stringified, in a format like:
console.log(JSON.stringify(x, null, 2))
?

@JacobWay
Copy link

Function.prototype.apply.call(console.log, console, arguments);

I'm really confused by this statement.

  1. What does it do?
  2. How can I analyse this statement?
  3. Or with some thoughts, I can figure it out step by step?
  4. Can it simplify more statements to achieve the same result? for instance: var temp = Function.prototype.call(console.log, console, arguments); Function.prototype.apply(temp);

Thanks for the response.

@ikodev
Copy link

ikodev commented Oct 27, 2016

The portable solution seems to not keep the original line number of the log.
In place, it's display the line of :

function log(){/* jshint -W021 */if(window.console){if(Function.prototype.bind)log=Function.prototype.bind.call(console.log,console);else log=function(){Function.prototype.apply.call(console.log,console,arguments);};log.apply(this,arguments);}}

Same for you ?

@muzi131313
Copy link

muzi131313 commented Dec 18, 2016

window.line = function () {
var error = new Error(''),
brower = {
ie: !-[1,], // !!window.ActiveXObject || "ActiveXObject" in window
opera: ~window.navigator.userAgent.indexOf("Opera"),
firefox: ~window.navigator.userAgent.indexOf("Firefox"),
chrome: ~window.navigator.userAgent.indexOf("Chrome"),
safari: ~window.navigator.userAgent.indexOf("Safari"), // /^((?!chrome).)*safari/i.test(navigator.userAgent)?
},
todo = function () {
// TODO:
console.error('a new island was found, please told the line()'s author(roastwind)');
},
line = (function(error, origin){
// line, column, sourceURL
if(error.stack){
var line,
baseStr = '',
stacks = error.stack.split('\n');
stackLength = stacks.length,
isSupport = false;
// mac版本chrome(55.0.2883.95 (64-bit))
if(stackLength == 11 || brower.chrome){
line = stacks[3];
isSupport = true;
// mac版本safari(10.0.1 (12602.2.14.0.7))
}else if(brower.safari){
line = stacks[2];
isSupport = true;
}else{
todo();
}
if(isSupport){
line = ~line.indexOf(origin) ? line.replace(origin, '') : line;
line = ~line.indexOf('/') ? line.substring(line.indexOf('/')+1, line.lastIndexOf(':')) : line;
}
return line;
}else{
todo();
}
return '';
})(error, window.location.origin);
return this.lineNumber ? (this.fileName + ':' + this.lineNumber) : line;
}
window.log = function () {
if(window.debug){
var _line = window.line.apply(arguments.callee.caller),
args = Array.prototype.slice.call(arguments, 0).concat(['\t\t\t@'+_line]);
window.console.log.apply(window.console, args);
}
}

maybe this was help to you.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment