A proper wrapper for console.log with correct line number?
Asked Answered
C

30

191

I'm developing an application and placing a global isDebug switch. I would like to wrap console.log for more convenient usage.

//isDebug controls the entire site.
var isDebug = true;

//debug.js
function debug(msg, level){
    var Global = this;
    if(!(Global.isDebug && Global.console && Global.console.log)){
        return;
    }
    level = level||'info';
    Global.console.log(level + ': '+ msg);
}

//main.js
debug('Here is a msg.');

Then I get this result in Firefox console.

info: Here is a msg.                       debug.js (line 8)

What if I want to log with line number where debug() gets called, like info: Here is a msg. main.js (line 2)?

Covarrubias answered 11/12, 2012 at 7:36 Comment(5)
You could use console.log for info, console.warn for warning and console.error for error, instead of adding something in console.log via a wrapper function.Renee
@AlvinWong Yeah I know that, but the problem is I need a global debug switch, which controls whether console needs to be used. To achieve such goal, a wrapper seems to be the only way?Covarrubias
For Google Chrome see https://mcmap.net/q/136831/-console-log-wrapper-that-keeps-line-numbers-and-supports-most-methods-closed In your case the pattern would be debug.jsOracle
You should either rephrase your question or select another answer. You asked for a wrapper with line numbers and accepted an answer that neither wraps nor provide line numbers.Nigrify
@Nigrify Thank you for the heads-up. I tried Jacob Phillips' answer and it worked perfert.Covarrubias
E
12

I found a simple solution to combine the accepted answer (binding to console.log/error/etc) with some outside logic to filter what is actually logged.

// or window.log = {...}
var log = {
  ASSERT: 1, ERROR: 2, WARN: 3, INFO: 4, DEBUG: 5, VERBOSE: 6,
  set level(level) {
    if (level >= this.ASSERT) this.a = console.assert.bind(window.console);
    else this.a = function() {};
    if (level >= this.ERROR) this.e = console.error.bind(window.console);
    else this.e = function() {};
    if (level >= this.WARN) this.w = console.warn.bind(window.console);
    else this.w = function() {};
    if (level >= this.INFO) this.i = console.info.bind(window.console);
    else this.i = function() {};
    if (level >= this.DEBUG) this.d = console.debug.bind(window.console);
    else this.d = function() {};
    if (level >= this.VERBOSE) this.v = console.log.bind(window.console);
    else this.v = function() {};
    this.loggingLevel = level;
  },
  get level() { return this.loggingLevel; }
};
log.level = log.DEBUG;

Usage:

log.e('Error doing the thing!', e); // console.error
log.w('Bonus feature failed to load.'); // console.warn
log.i('Signed in.'); // console.info
log.d('Is this working as expected?'); // console.debug
log.v('Old debug messages, output dominating messages'); // console.log; ignored because `log.level` is set to `DEBUG`
log.a(someVar == 2) // console.assert
  • Note that console.assert uses conditional logging.
  • Make sure your browser's dev tools shows all message levels!
Earphone answered 5/2, 2017 at 20:52 Comment(2)
Because it doesn't give any line number nor working examples showing the log level.Epigoni
The line number will be the same as if using the console directly. I updated the answer with usage examples. It doesn't have many votes because I answered it two years later :)Earphone
M
141

This is an old question and all the answers provided are overly hackey, have MAJOR cross browser issues, and don't provide anything super useful. This solution works in every browser and reports all console data exactly as it should. No hacks required and one line of code Check out the codepen.

var debug = console.log.bind(window.console)

Create the switch like this:

isDebug = true // toggle this to turn on / off for global control

if (isDebug) var debug = console.log.bind(window.console)
else var debug = function(){}

Then simply call as follows:

debug('This is happening.')

You can even take over the console.log with a switch like this:

if (!isDebug) console.log = function(){}

If you want to do something useful with that, you can add all the console methods and wrap it up in a reusable function that gives not only global control, but class level as well:

var Debugger = function(gState, klass) {
  
  this.debug = {}

  if (gState && klass.isDebug) {
    for (var m in console)
      if (typeof console[m] == 'function')
        this.debug[m] = console[m].bind(window.console, klass.toString()+": ")
  } else {
    for (var m in console)
      if (typeof console[m] == 'function')
        this.debug[m] = function(){}
  }
  return this.debug
}

isDebug = true //global debug state

debug = Debugger(isDebug, this)

debug.log('Hello log!')
debug.trace('Hello trace!')

Now you can add it to your classes:

var MyClass = function() {
  this.isDebug = true //local state
  this.debug = Debugger(isDebug, this)
  this.debug.warn('It works in classses')
}
Motorman answered 4/10, 2015 at 0:21 Comment(10)
Correct me if I'm wrong, but this doesn't allow you to add any additional functionality though, correct? You're essentially only aliasing the console object? A crude example - there is no way to console.log() the event twice for every debug.log() ?Sumbawa
@A.B.Carroll You could console.log twice by binding a custom log() function containing two calls to console.log, however the line numbers would reflect the line that console.log actually resides on, not where debug.log is called. However, you could do things like add dynamic prefixes/suffixes etc.. There are also ways to compensate for the line number issue, but thats another question i think. Check this project out for an example: github.com/arctelix/iDebugConsole/blob/master/README.mdMotorman
This method doesn't work in Firefox from version 47 to 49 inclusive. And was fixed only in version 50.0a2. Well FF50 will be released in 2 week, but I spend several hours to realise why it's not working. So I think this information may be helpfull for someone. linkCeramist
I believe what @A.B.Carroll meant is everything inside the instance can't be used in runtime. for another instance, the global state can only be defined in instantiation, so if you later change this.isDebug to false, it won't matter. I just don't know if there's any way around this, maybe it's by design. In that sense, the isDebug is a quite misleading var and should be a const instead.Bluejacket
Got another solution for you; If you define the property as a getter, where it returns a console or noop function, that will handle your case. So in your case...Object.defineProperty(Debugger, 'log', { get: function() { return this.isDebug ? console.log : noop; } }). It's a weird one to get your head around at first, as the property is a returned function you then call as a method, so you can do the same debug.log("hello world"). I came up with this after reading your post. :) fyi; I've used noop as a reference to an empty function for a bit of efficiency.Chemar
it help if you give also a good example of how write good code and rewrite your code to be more clearly, with variables full names and no code names. And define functions in a declaration way, not statement way.Shirting
This doesn't answer the question "What if I want to log with line number where debug() gets called?"Medallion
totallynoob.com/… this solution allows additional text as a prefix in the custom console logBecalmed
Where is the line number then ?Boreal
You don't need the trailing space in the added argument (klass.toString()+": ") because the browser already adds spaces between arguments: console.log("hello ", "world") will put two spaces between those two words. @​Kasra & @Medallion – the line number is at the right of the console message line. Were this wrapped without bind, that line number would refer to the console.log call within the wrapper's definition rather than the invocation of the wrapper.Sanjiv
I
33

You can maintain line numbers and output the log level with some clever use of Function.prototype.bind:

function setDebug(isDebug) {
  if (isDebug) {
    window.debug = window.console.log.bind(window.console, '%s: %s');
  } else {
    window.debug = function() {};
  }
}

setDebug(true);

// ...

debug('level', 'This is my message.'); // --> level: This is my message. (line X)

Taking it a step further, you could make use of the console's error/warning/info distinctions and still have custom levels. Try it!

function setDebug(isDebug) {
  if (isDebug) {
    window.debug = {
      log: window.console.log.bind(window.console, 'log: %s'),
      error: window.console.error.bind(window.console, 'error: %s'),
      info: window.console.info.bind(window.console, 'info: %s'),
      warn: window.console.warn.bind(window.console, 'warn: %s')
    };
  } else {
    var __no_op = function() {};

    window.debug = {
      log: __no_op,
      error: __no_op,
      warn: __no_op,
      info: __no_op
    }
  }
}

setDebug(true);

// ...

debug.log('wat', 'Yay custom levels.'); // -> log: wat: Yay custom levels.    (line X)
debug.info('This is info.');            // -> info: This is info.        (line Y)
debug.error('Bad stuff happened.');     // -> error: Bad stuff happened. (line Z)
Inculpable answered 22/9, 2013 at 2:52 Comment(6)
I've been trying for a while now to automatically prefix the output from console.debug(...) with function name and arguments - any thoughts on how to do that?Scissel
I've been looking at the multitude of console wrappers/shims/etc. and this is the first one I've come across that combines preserving line numbers with customising the output. It's a clever use of the fact that .bind also does some currying for you, you can bind one or more arguments in addition to the context. You could take it a step further and pass it a noop function with a .toString method that could run code when the log method is called! See this jsfiddleInsufficient
Maybe not in all browsers (haven't looked into it), but replacing the %s with %o in Chrome will print the parameters in the way you'd expect (objects are expandable, numbers and strings are colored, etc).Mccaslin
love this solution. I made a few changes that work better for my application, but the bulk of it is still intact and running beautifully. ThanksChong
I like the "step further" Solution. I'd like to auto prefix the output with a timestamp. Can someone point me in the right direction. I couldn't figure out how to do that (except, outputting a timestamp generated at initialisation time of the function which is not what i want).Keeper
And sometimes, just asking brings you to the solution: jsfiddle.net/2gs3rq4xKeeper
F
27

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/

_log = (function (undefined) {
    var Log = Error; // does this do anything?  proper inheritance...?
    Log.prototype.write = function (args) {
        /// <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>
        /// <remarks>Includes line numbers by calling Error object -- see
        /// * http://paulirish.com/2009/log-a-lightweight-wrapper-for-consolelog/
        /// * https://mcmap.net/q/134641/-a-proper-wrapper-for-console-log-with-correct-line-number
        /// * https://mcmap.net/q/136832/-how-to-get-javascript-caller-function-line-number-and-caller-source-url
        /// </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.log) {
            if (console.log.apply) { console.log.apply(console, args); } else { console.log(args); } // nicer display in some browsers
        }
    };
    var extractLineNumberFromStack = function (stack) {
        /// <summary>
        /// Get the line/filename detail from a Webkit stack trace.  See https://mcmap.net/q/136832/-how-to-get-javascript-caller-function-line-number-and-caller-source-url
        /// </summary>
        /// <param name="stack" type="String">the stack string</param>

        if(!stack) return '?'; // fix undefined issue reported by @sigod

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

    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)); // turn into proper array
    };//--  fn  returned

})();//--- _log

This also works in node, and you can test it with:

// no debug mode
_log('this should not appear');

// turn it on
DEBUGMODE = true;

_log('you should', 'see this', {a:1, b:2, c:3});
console.log('--- regular log ---');
_log('you should', 'also see this', {a:4, b:8, c:16});

// turn it off
DEBUGMODE = false;

_log('disabled, should not appear');
console.log('--- regular log2 ---');
Furlong answered 12/2, 2013 at 20:41 Comment(9)
a slightly more advanced answer to account for the extra console methods like warn, error, etc -- https://mcmap.net/q/136831/-console-log-wrapper-that-keeps-line-numbers-and-supports-most-methods-closedFurlong
var line = stack.split('\n')[2];'undefined' is not an objectPresley
@Presley -- probably either depends on the browser, or that I wrote this 2 years ago and the browser(s) have changed. what's your scenario?Furlong
one of my colleagues copy-pasted your code into our project. It broke site in IE11 and Safari 5. Not sure about other versions of this browsers. Maybe you'll add a check for future copy-pasters?Presley
@Presley what about now? added if(!stack) return '?' to the method that fails, rather than where it's called (so if anyone uses the method itself they're "protected" too)Furlong
Just want to add that this is one of the few solutions that also work in node, because it does not depend on the window entity.Epigoni
The linked fiddle is busted for me, apparently due to line 35 having var line = stack.split('\n')[3]; instead of var line = stack.split('\n')[2];. However, after fixing that bit, the custom logs show properly, but report line 54 (where the wrapper is defined) rather than their correct lines in the source :(Haifa
@JesseDupuy seems like Chrome has changed its stack trace format in the 5+ years since I wrote this. Consider the screenshot from a Google article (date unknown) showing what may be closer to the format I wrote against vs what you're seeing now. Actually, I think it's the 2 vs 3 -- probably depends on how deep the log function was nested?Furlong
Uncaught TypeError: Cannot read property 'indexOf' of undefinedBoreal
E
12

I found a simple solution to combine the accepted answer (binding to console.log/error/etc) with some outside logic to filter what is actually logged.

// or window.log = {...}
var log = {
  ASSERT: 1, ERROR: 2, WARN: 3, INFO: 4, DEBUG: 5, VERBOSE: 6,
  set level(level) {
    if (level >= this.ASSERT) this.a = console.assert.bind(window.console);
    else this.a = function() {};
    if (level >= this.ERROR) this.e = console.error.bind(window.console);
    else this.e = function() {};
    if (level >= this.WARN) this.w = console.warn.bind(window.console);
    else this.w = function() {};
    if (level >= this.INFO) this.i = console.info.bind(window.console);
    else this.i = function() {};
    if (level >= this.DEBUG) this.d = console.debug.bind(window.console);
    else this.d = function() {};
    if (level >= this.VERBOSE) this.v = console.log.bind(window.console);
    else this.v = function() {};
    this.loggingLevel = level;
  },
  get level() { return this.loggingLevel; }
};
log.level = log.DEBUG;

Usage:

log.e('Error doing the thing!', e); // console.error
log.w('Bonus feature failed to load.'); // console.warn
log.i('Signed in.'); // console.info
log.d('Is this working as expected?'); // console.debug
log.v('Old debug messages, output dominating messages'); // console.log; ignored because `log.level` is set to `DEBUG`
log.a(someVar == 2) // console.assert
  • Note that console.assert uses conditional logging.
  • Make sure your browser's dev tools shows all message levels!
Earphone answered 5/2, 2017 at 20:52 Comment(2)
Because it doesn't give any line number nor working examples showing the log level.Epigoni
The line number will be the same as if using the console directly. I updated the answer with usage examples. It doesn't have many votes because I answered it two years later :)Earphone
F
12

Listen McFly, this was the only thing that worked for me:

let debug = true;
Object.defineProperty(this, "log", {get: function () {
  return debug ? console.log.bind(window.console, '['+Date.now()+']', '[DEBUG]') 
               : function(){};}
});

// usage:
log('Back to the future');
// outputs:
[1624398754679] [DEBUG] Back to the future

The beauty is to avoid another function call like log('xyz')() or to create a wrapper object or even class. Its also ES5 safe.

If you don't want a prefix, just delete the param.

update included current timestamp to prefix every log output.

Fariss answered 1/3, 2021 at 1:23 Comment(8)
This is nice. But correct me if I'm wrong: there is no chance to manipulate log's arguments before using it in console.log? I'd like to add timers and the like as in npmjs.com/package/debugPlum
Yes its possible. Updated answer which prefixes the current timestamp.Fariss
It took me a while to understand why you used defineProperty with get, so that the date is always updated, and not static…Huynh
please add howTo delete this PropertyTunicate
@SL5net: what do you mean?Fariss
@Fariss #70952495Tunicate
@SL5net: Ok i see what you mean. As this is not part of the question and there are already answers (like the one you posted) I want to keep this answer focused.Fariss
Nice! However, this does not always exist. Might have to attach to the window global object. Great for logging in a class.Deauville
O
10

Chrome Devtools lets you achieve this with Blackboxing. You can create console.log wrapper that can have side effects, call other functions, etc, and still retain the line number that called the wrapper function.

Just put a small console.log wrapper into a separate file, e.g.

(function() {
    var consolelog = console.log
    console.log = function() {
        // you may do something with side effects here.
        // log to a remote server, whatever you want. here
        // for example we append the log message to the DOM
        var p = document.createElement('p')
        var args = Array.prototype.slice.apply(arguments)
        p.innerText = JSON.stringify(args)
        document.body.appendChild(p)

        // call the original console.log function
        consolelog.apply(console,arguments)
    }
})()

Name it something like log-blackbox.js

Then go to Chrome Devtools settings and find the section "Blackboxing", add a pattern for the filename you want to blackbox, in this case log-blackbox.js

Omnipotent answered 19/2, 2016 at 22:55 Comment(2)
Note: make sure you don't have any code you would want to show up in the stack trace in the same file, as it will also be removed from the trace.Hubert
Is it possible that this is not supported any more? The link doesn't seem to point to correct location.Arri
C
9

From: How to get JavaScript caller function line number? How to get JavaScript caller source URL? the Error object has a line number property(in FF). So something like this should work:

var err = new Error();
Global.console.log(level + ': '+ msg + 'file: ' + err.fileName + ' line:' + err.lineNumber);

In Webkit browser you have err.stack that is a string representing the current call stack. It will display the current line number and more information.

UPDATE

To get the correct linenumber you need to invoke the error on that line. Something like:

var Log = Error;
Log.prototype.write = function () {
    var args = Array.prototype.slice.call(arguments, 0),
        suffix = this.lineNumber ? 'line: '  + this.lineNumber : 'stack: ' + this.stack;

    console.log.apply(console, args.concat([suffix]));
};

var a = Log().write('monkey' + 1, 'test: ' + 2);

var b = Log().write('hello' + 3, 'test: ' + 4);
Cadell answered 11/12, 2012 at 7:44 Comment(3)
new Error(); gives me the context where it executes, if I put it in debug.js, then I will get info: Here is a msg. file: http://localhost/js/debug.js line:7.Covarrubias
What's the point of Log = Error? You're still modifying the Error class, right?Furlong
Combined your answer with a couple others -- see below https://mcmap.net/q/134641/-a-proper-wrapper-for-console-log-with-correct-line-numberFurlong
N
9

A way to keep line number is here: https://gist.github.com/bgrins/5108712. It more or less boils down to this:

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);
    };
}

You could wrap this with isDebug and set window.log to function() { } if you aren't debugging.

Nightmare answered 16/4, 2013 at 20:32 Comment(0)
A
6

You can pass the line number to your debug method, like this :

//main.js
debug('Here is a msg.', (new Error).lineNumber);

Here, (new Error).lineNumber would give you the current line number in your javascript code.

Ay answered 11/12, 2012 at 7:47 Comment(3)
A little bit verbose, isn't it?Covarrubias
I think it is enough to answer your query. :)Ay
the lineNumber property is non-standard, and only works on firefox right now, see hereMistrustful
M
6

Stack trace solutions display the line number but do not allow to click to go to source, which is a major problem. The only solution to keep this behaviour is to bind to the original function.

Binding prevents to include intermediate logic, because this logic would mess with line numbers. However, by redefining bound functions and playing with console string substitution, some additional behaviour is still possible.

This gist shows a minimalistic logging framework that offers modules, log levels, formatting, and proper clickable line numbers in 34 lines. Use it as a basis or inspiration for your own needs.

var log = Logger.get("module").level(Logger.WARN);
log.error("An error has occured", errorObject);
log("Always show this.");

EDIT: gist included below

/*
 * Copyright 2016, Matthieu Dumas
 * This work is licensed under the Creative Commons Attribution 4.0 International License.
 * To view a copy of this license, visit http://creativecommons.org/licenses/by/4.0/
 */

/* Usage : 
 * var log = Logger.get("myModule") // .level(Logger.ALL) implicit
 * log.info("always a string as first argument", then, other, stuff)
 * log.level(Logger.WARN) // or ALL, DEBUG, INFO, WARN, ERROR, OFF
 * log.debug("does not show")
 * log("but this does because direct call on logger is not filtered by level")
 */
var Logger = (function() {
    var levels = {
        ALL:100,
        DEBUG:100,
        INFO:200,
        WARN:300,
        ERROR:400,
        OFF:500
    };
    var loggerCache = {};
    var cons = window.console;
    var noop = function() {};
    var level = function(level) {
        this.error = level<=levels.ERROR ? cons.error.bind(cons, "["+this.id+"] - ERROR - %s") : noop;
        this.warn = level<=levels.WARN ? cons.warn.bind(cons, "["+this.id+"] - WARN - %s") : noop;
        this.info = level<=levels.INFO ? cons.info.bind(cons, "["+this.id+"] - INFO - %s") : noop;
        this.debug = level<=levels.DEBUG ? cons.log.bind(cons, "["+this.id+"] - DEBUG - %s") : noop;
        this.log = cons.log.bind(cons, "["+this.id+"] %s");
        return this;
    };
    levels.get = function(id) {
        var res = loggerCache[id];
        if (!res) {
            var ctx = {id:id,level:level}; // create a context
            ctx.level(Logger.ALL); // apply level
            res = ctx.log; // extract the log function, copy context to it and returns it
            for (var prop in ctx)
                res[prop] = ctx[prop];
            loggerCache[id] = res;
        }
        return res;
    };
    return levels; // return levels augmented with "get"
})();
Maximamaximal answered 31/8, 2016 at 14:46 Comment(2)
This answer only has 3 upvotes but is incredibly more rich and clean than any other in the pageBumper
however, it looks like all the useful parts are on an external gist.Weighbridge
R
5

If you simply want to control whether debug is used and have the correct line number, you can do this instead:

if(isDebug && window.console && console.log && console.warn && console.error){
    window.debug = {
        'log': window.console.log,
        'warn': window.console.warn,
        'error': window.console.error
    };
}else{
    window.debug = {
        'log': function(){},
        'warn': function(){},
        'error': function(){}
    };
}

When you need access to debug, you can do this:

debug.log("log");
debug.warn("warn");
debug.error("error");

If isDebug == true, The line numbers and filenames shown in the console will be correct, because debug.log etc is actually an alias of console.log etc.

If isDebug == false, no debug messages are shown, because debug.log etc simply does nothing (an empty function).

As you already know, a wrapper function will mess up the line numbers and filenames, so it's a good idea to prevent using wrapper functions.

Renee answered 11/12, 2012 at 7:52 Comment(4)
Great, I need to be careful about the order of isDebug = true and debug.js, but this answer does work!Covarrubias
window.debug = window.console would be a bit cleaner.Cadell
@Cadell then I'll need to "implement" all the member functions if isDebug == false. :{Renee
@AlvinWong I just menat for if isDebug===true. Or event to this: jsfiddle.net/fredrik/x6Jw5Cadell
X
4

Here's a way to keep your existing console logging statements while adding a file name and line number or other stack trace info onto the output:

(function () {
  'use strict';
  var isOpera = !!window.opera || navigator.userAgent.indexOf(' OPR/') >= 0;
  var isChrome = !!window.chrome && !!window.chrome.webstore;
  var isIE = /*@cc_on!@*/false || !!document.documentMode;
  var isEdge = !isIE && !!window.StyleMedia;
  var isPhantom = (/PhantomJS/).test(navigator.userAgent);
  Object.defineProperties(console, ['log', 'info', 'warn', 'error'].reduce(function (props, method) {
    var _consoleMethod = console[method].bind(console);
    props[method] = {
      value: function MyError () {
        var stackPos = isOpera || isChrome ? 2 : 1;
        var err = new Error();
        if (isIE || isEdge || isPhantom) { // Untested in Edge
          try { // Stack not yet defined until thrown per https://learn.microsoft.com/en-us/scripting/javascript/reference/stack-property-error-javascript
            throw err;
          } catch (e) {
            err = e;
          }
          stackPos = isPhantom ? 1 : 2;
        }

        var a = arguments;
        if (err.stack) {
          var st = err.stack.split('\n')[stackPos]; // We could utilize the whole stack after the 0th index
          var argEnd = a.length - 1;
          [].slice.call(a).reverse().some(function(arg, i) {
            var pos = argEnd - i;
            if (typeof a[pos] !== 'string') {
              return false;
            }
            if (typeof a[0] === 'string' && a[0].indexOf('%') > -1) { pos = 0 } // If formatting
            a[pos] += ' \u00a0 (' + st.slice(0, st.lastIndexOf(':')) // Strip out character count
              .slice(st.lastIndexOf('/') + 1) + ')'; // Leave only path and line (which also avoids ":" changing Safari console formatting)
            return true;
          });
        }
        return _consoleMethod.apply(null, a);
      }
    };
    return props;
  }, {}));
}());

Then use it like this:

<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8" />
  <script src="console-log.js"></script>
</head>
<body>
  <script>
  function a () {
    console.log('xyz'); // xyz   (console-log.html:10)
  }
  console.info('abc'); // abc   (console-log.html:12)
  console.log('%cdef', "color:red;"); // (IN RED:) // def   (console-log.html:13)
  a();
  console.warn('uuu'); // uuu   (console-log.html:15)
  console.error('yyy'); // yyy   (console-log.html:16)
  </script>
</body>
</html>

This works in Firefox, Opera, Safari, Chrome, and IE 10 (not yet tested on IE11 or Edge).

Xylotomy answered 17/3, 2017 at 15:5 Comment(3)
Nice work, but still not 100% what i need. I would like to have the file-name and line-number information on the right side of the console-view, where it can be clicked to open the source. This solution shows the information as part of the message (like this: my test log message (myscript.js:42) VM167 mypage.html:15), which is not as good to read plus it is not linked. Still good work thus an upvote.Animadversion
Yeah, while that would be ideal, there is no way, AFAIK, to spoof the filename link that shows up in the console...Xylotomy
@BrettZamir posted a question about this code here: #52618868Sacks
T
4

With modern javascript and the use of getters, you could write something like this:

window.Logger = {
    debugMode: true,
    get info() {
        if ( window.Logger.debugMode ) {
            return window.console.info.bind( window.console );
        } else {
            return () => {};
        }
    }
}

The nice part about it is that you can have both static and computed values printed out, together with correct line numbers. You could even define multiple logger with different settings:

class LoggerClz {
    name = null;
    debugMode = true;
    constructor( name ) { this.name = name; }
    get info() {
        if ( this.debugMode ) {
            return window.console.info.bind( window.console, 'INFO', new Date().getTime(), this.name );
        } else {
            return () => {};
        }
    }
}

const Logger1 = new LoggerClz( 'foo' );
const Logger2 = new LoggerClz( 'bar' );

function test() {
    Logger1.info( '123' ); // INFO 1644750929128 foo 123 [script.js:18] 
    Logger2.info( '456' ); // INFO 1644750929128 bar 456 [script.js:19] 
}
test();
Trochanter answered 13/2, 2022 at 11:24 Comment(0)
D
3

The idea with bind Function.prototype.bind is brilliant. You can also use npm library lines-logger. It shows origin source files:

Create logger anyone once in your project:

var LoggerFactory = require('lines-logger').LoggerFactory;
var loggerFactory = new LoggerFactory();
var logger = loggerFactory.getLoggerColor('global', '#753e01');

Print logs:

logger.log('Hello world!')();

enter image description here

Deliberative answered 23/6, 2018 at 23:55 Comment(0)
G
2

A little variation is to to have debug() return a function, which is then executed where you need it - debug(message)(); and so properly shows the correct line number and calling script in the console window, while allowing for variations like redirecting as an alert, or saving to file.

var debugmode='console';
var debugloglevel=3;

function debug(msg, type, level) {

  if(level && level>=debugloglevel) {
    return(function() {});
  }

  switch(debugmode) {
    case 'alert':
      return(alert.bind(window, type+": "+msg));
    break;
    case 'console':
      return(console.log.bind(window.console, type+": "+msg));
    break;
    default:
      return (function() {});
  }

}

Since it returns a function, that function needs to be executed at the debug line with ();. Secondly, the message is sent to the debug function, rather than into the returned function allowing pre-processing or checking that you might need, such as checking log-level state, making the message more readable, skipping different types, or only reporting items meeting the log level criteria;

debug(message, "serious", 1)();
debug(message, "minor", 4)();
Greerson answered 16/1, 2018 at 15:52 Comment(0)
D
2

You can use optional chaining to really simplify this. You get full access to the console object with no hacks and concise syntax.

const debug = (true) ? console : null;

debug?.log('test');
debug?.warn('test');
debug?.error('test')

If debug == null, everything after the ? is ignored with no error thrown about inaccessible properties.

const debug = (false) ? console : null;
debug?.error('not this time');

This also lets you use the debug object directly as a conditional for other debug related processes besides logging.

const debug = (true) ? console : null;

let test = false;

function doSomething() {
  test = true;
  debug?.log('did something');
}

debug && doSomething();

if (debug && test == false) {
  debug?.warn('uh-oh');
} else {
  debug?.info('perfect');
}

if (!debug) {
  // set up production
}

If you want, you can override the various methods with a no-op based on your desired log level.

const debug = (true) ? console : null;
const quiet = true; const noop = ()=>{};

if (debug && quiet) {
  debug.info = noop;
  debug.warn = noop;
}

debug?.log('test');
debug?.info('ignored in quiet mode');
debug?.warn('me too');
Doubleripper answered 12/5, 2022 at 5:6 Comment(0)
C
1
//isDebug controls the entire site.
var isDebug = true;

//debug.js
function debug(msg, level){
    var Global = this;
    if(!(Global.isDebug && Global.console && Global.console.log)){
        return;
    }
    level = level||'info';
    return 'console.log(\'' + level + ': '+ JSON.stringify(msg) + '\')';
}

//main.js
eval(debug('Here is a msg.'));

This will give me info: "Here is a msg." main.js(line:2).

But the extra eval is needed, pity.

Covarrubias answered 11/12, 2012 at 7:56 Comment(1)
eval is Evil! So every evil.Cadell
J
1

I have been looking at this issue myself lately. Needed something very straight forward to control logging, but also to retain line numbers. My solution is not looking as elegant in code, but provides what is needed for me. If one is careful enough with closures and retaining.

I've added a small wrapper to the beginning of the application:

window.log = {
    log_level: 5,
    d: function (level, cb) {
        if (level < this.log_level) {
            cb();
        }
    }
};

So that later I can simply do:

log.d(3, function(){console.log("file loaded: utils.js");});

I've tested it of firefox and crome, and both browsers seem to show console log as intended. If you fill like that, you can always extend the 'd' method and pass other parameters to it, so that it can do some extra logging.

Haven't found any serious drawbacks for my approach yet, except the ugly line in code for logging.

Jochebed answered 24/6, 2016 at 8:47 Comment(0)
E
1

This implementation is based on the selected answer and helps reduce the amount of noise in the error console: https://mcmap.net/q/134641/-a-proper-wrapper-for-console-log-with-correct-line-number

var Logging = Logging || {};

const LOG_LEVEL_ERROR = 0,
    LOG_LEVEL_WARNING = 1,
    LOG_LEVEL_INFO = 2,
    LOG_LEVEL_DEBUG = 3;

Logging.setLogLevel = function (level) {
    const NOOP = function () { }
    Logging.logLevel = level;
    Logging.debug = (Logging.logLevel >= LOG_LEVEL_DEBUG) ? console.log.bind(window.console) : NOOP;
    Logging.info = (Logging.logLevel >= LOG_LEVEL_INFO) ? console.log.bind(window.console) : NOOP;
    Logging.warning = (Logging.logLevel >= LOG_LEVEL_WARNING) ? console.log.bind(window.console) : NOOP;
    Logging.error = (Logging.logLevel >= LOG_LEVEL_ERROR) ? console.log.bind(window.console) : NOOP;

}

Logging.setLogLevel(LOG_LEVEL_INFO);
Englacial answered 3/4, 2020 at 16:59 Comment(0)
L
1

Here's my logger function (based on some of the answers). Hope someone can make use of it:

const DEBUG = true;

let log = function ( lvl, msg, fun ) {};

if ( DEBUG === true ) {
    log = function ( lvl, msg, fun ) {
        const d = new Date();
        const timestamp = '[' + d.getHours() + ':' + d.getMinutes() + ':' +
            d.getSeconds() + '.' + d.getMilliseconds() + ']';
        let stackEntry = new Error().stack.split( '\n' )[2];
        if ( stackEntry === 'undefined' || stackEntry === null ) {
            stackEntry = new Error().stack.split( '\n' )[1];
        }
        if ( typeof fun === 'undefined' || fun === null ) {
            fun = stackEntry.substring( stackEntry.indexOf( 'at' ) + 3,
                stackEntry.lastIndexOf( ' ' ) );
            if ( fun === 'undefined' || fun === null || fun.length <= 1 ) {
                fun = 'anonymous';
            }
        }
        const idx = stackEntry.lastIndexOf( '/' );
        let file;
        if ( idx !== -1 ) {
            file = stackEntry.substring( idx + 1, stackEntry.length - 1 );
        } else {
            file = stackEntry.substring( stackEntry.lastIndexOf( '\\' ) + 1,
                stackEntry.length - 1 );
        }
        if ( file === 'undefined' || file === null ) {
            file = '<>';
        }

        const m = timestamp + ' ' + file + '::' + fun + '(): ' + msg;

        switch ( lvl ) {
        case 'log': console.log( m ); break;
        case 'debug': console.log( m ); break;
        case 'info': console.info( m ); break;
        case 'warn': console.warn( m ); break;
        case 'err': console.error( m ); break;
        default: console.log( m ); break;
        }
    };
}

Examples:

log( 'warn', 'log message', 'my_function' );
log( 'info', 'log message' );
Legofmutton answered 1/7, 2020 at 12:18 Comment(1)
can you please add an example outputTunicate
H
1

For Angular / Typescript console logger with correct line number you can do the following:

example file: console-logger.ts


export class Log {
  static green(title: string): (...args: any) => void {
    return console.log.bind(console, `%c${title}`, `background: #222; color: #31A821`);
  }

  static red(title: string): (...args: any) => void {
    return console.log.bind(console, `%c${title}`, `background: #222; color: #DA5555`);
  }

  static blue(title: string): (...args: any) => void {
    return console.log.bind(console, `%c${title}`, `background: #222; color: #5560DA`);
  }

  static purple(title: string): (...args: any) => void {
    return console.log.bind(console, `%c${title}`, `background: #222; color: #A955DA`);
  }

  static yellow(title: string): (...args: any) => void {
    return console.log.bind(console, `%c${title}`, `background: #222; color: #EFEC47`);
  }
}

Then call it from any other file:

example file: auth.service.ts

import { Log } from 'path to console-logger.ts';

    const user = { user: '123' }; // mock data

    Log.green('EXAMPLE')();
    Log.red('EXAMPLE')(user);
    Log.blue('EXAMPLE')(user);
    Log.purple('EXAMPLE')(user);
    Log.yellow('EXAMPLE')(user);

It will look like this in the console: enter image description here

Stackblitz example

Howells answered 4/10, 2022 at 15:10 Comment(0)
H
0

I found some of the answers to this problem a little too complex for my needs. Here is a simple solution, rendered in Coffeescript. It'a adapted from Brian Grinstead's version here

It assumes the global console object.

# exposes a global 'log' function that preserves line numbering and formatting.
(() ->
    methods = [
      'assert', 'clear', 'count', 'debug', 'dir', 'dirxml', 'error',
      'exception', 'group', 'groupCollapsed', 'groupEnd', 'info', 'log',
      'markTimeline', 'profile', 'profileEnd', 'table', 'time', 'timeEnd',
      'timeStamp', 'trace', 'warn']
    noop = () ->
    # stub undefined methods.
    for m in methods  when  !console[m]
        console[m] = noop

    if Function.prototype.bind?
        window.log = Function.prototype.bind.call(console.log, console);
    else
        window.log = () ->
            Function.prototype.apply.call(console.log, console, arguments)
)()
Hamburg answered 12/2, 2014 at 23:23 Comment(0)
D
0

Code from http://www.briangrinstead.com/blog/console-log-helper-function:

// 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);
    };
  }
})();

var a = {b:1};
var d = "test";
log(a, d);
Dopey answered 25/11, 2014 at 7:57 Comment(3)
This does not appear to show the original line number where log is called fromLuik
I'm nearly sure that it worked when I tested, but I replaced the code with the "full" version from the same page. Worked at least in Chrome 45.Ambie
Understood. With the changes you have now it's essentially the same as a few of the other answers and works. I was just curious about your previous code because you had an apply at the end that raised interesting possibilities for me to use this for more but since it didn't show the line number I was back to square one. Thanks though!Luik
H
0

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 line;
}
window.log = function () {
    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);
}
log('hello');

here was my solution about this question. when you call the method: log, it will print the line number where you print your log

Hooves answered 18/12, 2016 at 17:50 Comment(0)
I
0

The way I solved it was to create an object, then create a new property on the object using Object.defineProperty() and return the console property, which was then used as the normal function, but now with the extended abilty.

var c = {};
var debugMode = true;

var createConsoleFunction = function(property) {
    Object.defineProperty(c, property, {
        get: function() {
            if(debugMode)
                return console[property];
            else
                return function() {};
        }
    });
};

Then, to define a property you just do...

createConsoleFunction("warn");
createConsoleFunction("log");
createConsoleFunction("trace");
createConsoleFunction("clear");
createConsoleFunction("error");
createConsoleFunction("info");

And now you can use your function just like

c.error("Error!");
Impower answered 20/4, 2017 at 2:23 Comment(0)
G
0

Based on other answers (mainly @arctelix one) I created this for Node ES6, but a quick test showed good results in the browser as well. I'm just passing the other function as a reference.

let debug = () => {};
if (process.argv.includes('-v')) {
    debug = console.log;
    // debug = console; // For full object access
}
Gamecock answered 15/8, 2019 at 16:26 Comment(0)
M
0

Nothing here really had what I needed so I add my own approach: Overriding the console and reading the original error line from a synthetic Error. The example stores the console warns and errors to console.appTrace, having errors very detailed and verbose; in such way that simple (console.appTrace.join("") tells me all I need from the user session.

Note that this works as of now only on Chrome

(function () {
    window.console.appTrace = [];

    const defaultError = console.error;
    const timestamp = () => {
        let ts = new Date(), pad = "000", ms = ts.getMilliseconds().toString();
        return ts.toLocaleTimeString("cs-CZ") + "." + pad.substring(0, pad.length - ms.length) + ms + " ";
    };
    window.console.error = function () {
        window.console.appTrace.push("ERROR ",
            (new Error().stack.split("at ")[1]).trim(), " ",
            timestamp(), ...arguments, "\n");
        defaultError.apply(window.console, arguments);
    };

    const defaultWarn = console.warn;
    window.console.warn = function () {
        window.console.appTrace.push("WARN  ", ...arguments, "\n");
        defaultWarn.apply(window.console, arguments);
    };
})();

inspired by Get name and line of calling function in node.js and date formatting approach in this thread.

Mielke answered 5/10, 2022 at 13:23 Comment(0)
A
0

This is what worked for me. It creates a new object with all the functionality of the original console object and retains the console object.

let debugOn=true; // we can set this from the console or from a query parameter, for example (&debugOn=true)
const noop = () => { }; // dummy function. 
const debug = Object.create(console); // creates a deep copy of the console object. This retains the console object so we can use it when we need to and our new debug object has all the properties and methods of the console object.
let quiet = false;
debug.log = (debugOn) ? debug.log : noop;
if (debugOn&&quiet) {
    debug.info = noop;
    debug.warn = noop;
    debug.assert = noop;
    debug.error = noop;
    debug.debug = noop;
}

console.log(`we should see this with debug off or on`);
debug.log(`we should not see this with debugOn=false`);
Affect answered 18/2, 2023 at 16:39 Comment(0)
S
-1

All the solutions here dance around the real problem -- the debugger should be able to ignore part of the stack to give meaningful lines. VSCode's js debugger can now do this. At the time of this edit, the feature is available via the nightly build of the js-debug extension. See the link in the following paragraph.

I proposed a feature for VSCode's debugger here that ignores the top of the stack that resides in any of the skipFiles file paths of the launch configuration. The property is in the launch.json config of your vscode workspace. So you can create a file/module responsible for wrapping console.log, add it to the skipFiles and the debugger will show the line that called into your skipped file rather than console.log itself.

It is in the nightly build of the js-debug extension. It looks like it could be in the next minor release of visual studio code. I've verified it works using the nightly build. No more hacky workarounds, yay!

Semiconscious answered 8/12, 2020 at 18:7 Comment(0)
T
-1
let debug = console.log.bind(console);
let error = console.error.bind(console);
debug('debug msg');
error('more important message');

Reading for noobs:

Town answered 11/12, 2022 at 15:11 Comment(1)
Please explain why/how this answer works; code-only answers have limited value, especially for users looking to learn how solutions solve their problem.Manymanya

© 2022 - 2024 — McMap. All rights reserved.