console.log wrapper that keeps line numbers and supports most methods? [closed]

How can i write a console log wrapper that:

  • Keeping the recorded line number and file name of the log statement intact
  • Provides access to all log severity methods (error, log, debug, ...) and shows them in the console as they where logged
  • does provide some fallback (for example calls the log method when the browser does not support error)
  • can be switched off in a central location, so I can switch off logging for production
  • does handle the case that no console exists, and does not throw errors

Since logging in Java Script is so inconsistent, there must be some solution. Implementing it myself is a little bit tedious, but there seems to be no good library.

I currently found this logger that provides all the features, but it does mess up the line numbers. http://benalman.com/projects/javascript-debug-console-log/


There is my own log4javascript, which has its own logging console but also provides a wrapper around console.log. It fulfils all your criteria except keeping line numbers intact, which is impossible to achieve if you're wrapping calls to console.log() etc. in another function.

var log = log4javascript.getLogger("main");
var appender = new log4javascript.BrowserConsoleAppender();
log.addAppender(appender);
log.debug("Hello world");

I would also recommend log4javascript and explain how you can still keep the information about the printed filename and line, at least in Chrome.

I am not talking about changing the filename and line printed by Chrome but you can get to the information you are interested in and append it to the log statement. My solution has been a quick hack but I think with a little more work you can get nicely formatted log statements. It probably has also a heavy performance-impact, but since you won't leave your logs activated in production this shouldn't be too much of a problem.

The Concept

In Chrome you can create an Error object which provides a stack property that shows you your current stack location and somewhere in the stack string you find the file and line number of your calling script.

  > new Error().stack
  "Error
    at eval at <anonymous> (eval at evaluate (unknown source))
    at eval at evaluate (unknown source)
    at FrameMirror.evaluate (native)
    at Object.evaluate (unknown source)
    at Object._evaluateOn (unknown source)
    at Object._evaluateAndWrap (unknown source)
    at Object.evaluateOnCallFrame (unknown source)
    at meinAjaxAufruf (http://localhost:8080/numberajax.js:21:9)
    at HTMLInputElement.onkeyup (http://localhost:8080/numberajax.html:15:188)"

For a log4javascript call the stack trace might look something like this:

"Error
    at Object.append (http://localhost:8080/log4javascript_uncompressed.js:1921:17)
    at Object.doAppend (http://localhost:8080/log4javascript_uncompressed.js:1047:9)
    at Object.callAppenders (http://localhost:8080/log4javascript_uncompressed.js:647:27)
    at Object.log (http://localhost:8080/log4javascript_uncompressed.js:640:10)
    at Object.debug (http://localhost:8080/log4javascript_uncompressed.js:748:9)
    at meinAjaxAufruf (http://localhost:8080/numberajax.js:36:16)
    at HTMLInputElement.onkeyup (http://localhost:8080/numberajax.html:16:188)"

And the file and line that made the log4javascript call and that i am interested in is

at meinAjaxAufruf (http://localhost:8080/numberajax.js:36:16)

The Solution

I am guessing that the stack depth from the script your interested in to where the actual console call happens is always the same. So now you simply have to find out where the BrowserConsoleAppender makes its window.console access and add the line you are interested in to the formatted string. I did the following changes to log4javascript_uncompressed.js (version 1.4.2 line 1913):

} else if (window.console && window.console.log) { // Safari and Firebug
        var formattedMesage = getFormattedMessage();

        //---my additions
        var isChrome = navigator.userAgent.indexOf("Chrome") !== -1;
        if(isChrome){
            var stack = new Error().stack;
            var lineAccessingLogger = stack.split("\n")[6];
            formattedMesage += "\n" + lineAccessingLogger;
        }
        //---

        // Log to Firebug using its logging methods or revert to the console.log
        // method in Safari
        if (window.console.debug && Level.DEBUG.isGreaterOrEqual(loggingEvent.level)) {
            window.console.debug(formattedMesage);
        } else if (window.console.info && Level.INFO.equals(loggingEvent.level)) {
        ...

Now instead of

17:53:22,872 DEBUG - sending /NumberServlet?zahl=1&text=
                                                 log4javascript.js:154

I get

17:55:53,008 DEBUG - sending /NumberServlet?zahl=1&text=

    at meinAjaxAufruf (http://localhost:8080/numberajax.js:36:16) log4javascript_uncompressed.js:1930

It sure isn't a nice solution :), but I get what I need.

With a little more knowledge of the framework I suppose one could change the PatternLayout in a way that you can define how to print the file name/location and line number.

edit Instead of my prior solution I made some modifications to the PatternLayout.prototype.format function, so now I can use the additional option %l to define where and how I want to output the calling file and its line. I published my changes and a usage example as a Gist.


We had this issue with our log wrapper also and it turns out there is a fantastic, simple workaround using partial function application:

if(DEBUG_ENABLED && (typeof console != 'undefined')) {
    this.debug = console.log.bind(console);
}
else {
    this.debug = function(message) {};
}

With this, your browser will detect the correct line number and file of the source you wanted to log.


Crossposting from related question (A proper wrapper for console.log with correct line number?) but with updated solution to address multiple methods.


I liked @fredrik's answer, so I rolled it up with another answer which splits the Webkit stacktrace, and merged it with @PaulIrish's safe console.log wrapper. "Standardizes" the filename:line to a "special object" so it stands out and looks mostly the same in FF and Chrome.

Testing in fiddle: http://jsfiddle.net/drzaus/pWe6W/9/

_log = (function (methods, undefined) {

    var Log = Error; // does this do anything?  proper inheritance...?
    Log.prototype.write = function (args, method) {
        /// <summary>
        /// Paulirish-like console.log wrapper.  Includes stack trace via @fredrik SO suggestion (see remarks for sources).
        /// </summary>
        /// <param name="args" type="Array">list of details to log, as provided by `arguments`</param>
        /// <param name="method" type="string">the console method to use:  debug, log, warn, info, error</param>
        /// <remarks>Includes line numbers by calling Error object -- see
        /// * http://paulirish.com/2009/log-a-lightweight-wrapper-for-consolelog/
        /// * https://stackoverflow.com/questions/13815640/a-proper-wrapper-for-console-log-with-correct-line-number
        /// * https://stackoverflow.com/a/3806596/1037948
        /// </remarks>

        // via @fredrik SO trace suggestion; wrapping in special construct so it stands out
        var suffix = {
            "@": (this.lineNumber
                    ? this.fileName + ':' + this.lineNumber + ":1" // add arbitrary column value for chrome linking
                    : extractLineNumberFromStack(this.stack)
            )
        };

        args = args.concat([suffix]);
        // via @paulirish console wrapper
        if (console && console[method]) {
            if (console[method].apply) { console[method].apply(console, args); } else { console[method](args); } // nicer display in some browsers
        }
    };
    var extractLineNumberFromStack = function (stack) {
        /// <summary>
        /// Get the line/filename detail from a Webkit stack trace.  See https://stackoverflow.com/a/3806596/1037948
        /// </summary>
        /// <param name="stack" type="String">the stack string</param>

        // correct line number according to how Log().write implemented
        var line = stack.split('\n')[3];
        // fix for various display text
        line = (line.indexOf(' (') >= 0
            ? line.split(' (')[1].substring(0, line.length - 1)
            : line.split('at ')[1]
            );
        return line;
    };

    // method builder
    var logMethod = function(method) {
        return function (params) {
            /// <summary>
            /// Paulirish-like console.log wrapper
            /// </summary>
            /// <param name="params" type="[...]">list your logging parameters</param>

            // only if explicitly true somewhere
            if (typeof DEBUGMODE === typeof undefined || !DEBUGMODE) return;

            // call handler extension which provides stack trace
            Log().write(Array.prototype.slice.call(arguments, 0), method); // turn into proper array & declare method to use
        };//--  fn  logMethod
    };
    var result = logMethod('log'); // base for backwards compatibility, simplicity
    // add some extra juice
    for(var i in methods) result[methods[i]] = logMethod(methods[i]);

    return result; // expose
})(['error', 'debug', 'info', 'warn']);//--- _log

To keep it simple, I've the below wrapper for console methods:

var noop = function () {};
window.consolex = {
    debug : window.console && window.console.debug && console.debug.bind(console) || noop,
    log : window.console && window.console.log && console.log.bind(console) || noop,
    warn: window.WARN = window.console && window.console.warn && console.warn.bind(console) || noop,
    error: window.ERROR = window.console && window.console.error && console.error.bind(console) || noop
};

Also, for better logs in IE and older browsers, please read: Detailed console logging