10

I am working on project that uses quite a few js libraries and one of them is outputting awful lot into console, it is polluting the airwaves so bad that it makes it hard to debug....

I know how to disable logging completely by overriding console.log with this,

(function (original) {
    console.enableLogging = function () {
        console.log = original;
    };
    console.disableLogging = function () {
        console.log = function () {};
    };
})(console.log);

but how do it do that per source(file/url) of where message originated?

Community
  • 1
  • 1
Matas Vaitkevicius
  • 49,230
  • 25
  • 212
  • 228
  • I guess you need a solution, where the JS file does not define a module of some standard or has an IIFE to protect the global namespace? – Sirko Sep 22 '16 at 09:20
  • How to determine which `console.log` shoud be repaced ? – passion Sep 22 '16 at 09:26
  • Do you want externally controlled (e.g., using some sort of configuration) or literally per-file controlled (e.g., change that in each file)? – VLAZ Sep 22 '16 at 10:28
  • @vlaz I can't really change all files they get loaded from third party, but I could do tweaks after loading I guess, as this is only for while I am debugging... If I would have access then just replace `console.log` -> `\\console.log` :) – Matas Vaitkevicius Sep 22 '16 at 10:32
  • OK, so I am guessing you have third party stuff that logs information you are not interested in and you want to disable that. Would you like to disable _all_ third party logging or only some of them? – VLAZ Sep 22 '16 at 10:38
  • @vlaz Ideally only some of it would be great, however I would be more than satisficed by shutting everything 3rd party, as long as I don't have to scroll all the time or have to enable/disable console before every output.... – Matas Vaitkevicius Sep 22 '16 at 10:41
  • A very simple solution to disable logging for all third party tools is to just disable logging everywhere by overwriting the global `console.log` function. In your own files, you will need to use the original logging function, though, but overall it's not going to be that big of a change. Selectively controlling arbitrary file's logging is a more...let's say _interesting_ challenge. Not sure how exactly that could be done but I think it should be possible. I'll think about it. – VLAZ Sep 22 '16 at 10:46
  • @vlaz I would appreciate that Vlaz. Thanks. – Matas Vaitkevicius Sep 22 '16 at 10:48
  • @MatasVaitkevicius - I've been thinking about this and I think I have a way forward. Although I'm at work now, so no time to detail it, I'll write up an answer when I get off. Just to confirm, the goal here is to allow/disallow logging on a per-file basis _without_ modifying the file in question. – VLAZ Sep 22 '16 at 14:05
  • @vlaz It would be ideal - yes. – Matas Vaitkevicius Sep 22 '16 at 14:11
  • Cool, I'll write it up after work then. It's actually not _that_ hard a concept. I believe you'll be able to just create your own logger that acts based on what calls it. It's not going to be pretty but should work for development/debugging purposes. – VLAZ Sep 22 '16 at 14:25
  • Sorry, but what is so bad with my practical (It works ) solution ? Is it the "eval" or the need to change the script tag ? something else ? – O_Z Sep 22 '16 at 15:56

5 Answers5

8

Preamble

The beginning discusses how stuff works in general. If you just care for the code, skip Introduction and scroll to the Solution heading.

Introduction

Problem:

there is a lot of console noise in a web application. A significant amount of that noise is coming from third party code which we do not have access to. Some of the log noise might be coming from our code, as well.

Requirement:

reduce the noise by stopping the log. Some logs should still be kept and the decision about those should be decoupled from the code that is doing the logging. The granularity needed is "per-file". We should be able to choose which files do or do not add log messages. Finally, this will not be used in production code.

Assumption: this will be ran in a developer controlled browser. In that case, I will not focus on backwards compatibility.

Prior work:

First off logging can be enabled/disabled globally using this

(function (original) {
    console.enableLogging = function () {
        console.log = original;
    };
    console.disableLogging = function () {
        console.log = function () {};
    };
})(console.log);

(code posted in the question but also here for reference)

  • However, that does not allow for any granularity.
  • This could be modified to work on only specific modules but that cannot be done for third party code.
  • A mixed approach would be to disable logging globally but enable it in each of our modules. Problem there is that we have to modify each of our files and we will not get some potentially useful external messages.

A logging framework can be used but it might be an overkill. Although, to be honest, that's what I'd go for, I think, but it may need some integration into the product.

So, we need something light-weight-ish that has some configuration and does not need to be pretty.

Proposal:

The Loginator (title subject to change)

Let's start with the basics - we already know we can override the global log function. We'll take that and work with it. But first, let's recognise that the console object supports more than just .log. There could be various logging functions used. So-o-o, let's disable all of them.

Silence everything

//shorthand for further code. 
function noop() {}

const savedFunctions = Object.keys(console)
  .reduce((memo, key) => {
    if(typeof console[key] == "function") {
      //keep a copy just in case we need it
      memo[key] = console[key];
      //de-fang any functions 
      console[key] = noop;
    }
    
    return memo;
  }, 
  {});

console.log("Hello?");
console.info("Hello-o-o-o?");
console.warn("Can anybody hear me?");
console.error("I guess there is nobody there...");

savedFunctions.log("MUAHAHAHA!")

This can obviously be improved but it showcases how any and ll logging can be stopped. In reality, console.error should probably be left and console.warn might be also useful. But this is not the be-all-and-end-all solution.

Next, since we can override console functionality...why not supply our own?

Custom logging

const originalLog = console.log;
console.log = function selectiveHearing() {
  if (arguments[0].indexOf("die") !== -1) {
    arguments[0] = "Have a nice day!";
    }
  return originalLog.apply(console, arguments)
}

console.log("Hello.");
console.log("My name is Inigo Montoya.");
console.log("You killed my father.");
console.log("Prepare to die.");

That is all the tools we need to roll our own mini-logging framework.

How to do selective logging

The only thing missing is to determine which file something is coming from. We just need a stack trace.

// The magic
console.log(new Error().stack);

/* SAMPLE:

Error
    at Object.module.exports.request (/home/vagrant/src/kumascript/lib/kumascript/caching.js:366:17)
    at attempt (/home/vagrant/src/kumascript/lib/kumascript/loaders.js:180:24)
    at ks_utils.Class.get (/home/vagrant/src/kumascript/lib/kumascript/loaders.js:194:9)
    at /home/vagrant/src/kumascript/lib/kumascript/macros.js:282:24
    at /home/vagrant/src/kumascript/node_modules/async/lib/async.js:118:13
    at Array.forEach (native)
    at _each (/home/vagrant/src/kumascript/node_modules/async/lib/async.js:39:24)
    at Object.async.each (/home/vagrant/src/kumascript/node_modules/async/lib/async.js:117:9)
    at ks_utils.Class.reloadTemplates (/home/vagrant/src/kumascript/lib/kumascript/macros.js:281:19)
    at ks_utils.Class.process (/home/vagrant/src/kumascript/lib/kumascript/macros.js:217:15)
*/

(Relevant bit copied here.)

True, there are some better ways to do it but not a lot. It would either require a framework or it's browser specific - error stacks are not officially supported but they work in Chrome, Edge, and Firefox. Also, come on - it's literally one line - we want simple and don't mind dirty, so I'm happy for the tradeoff.

Solution

Putting it all together. Warning: Do NOT use this in production

(function(whitelist = [], functionsToPreserve = ["error"]) {
  function noop() {}

  //ensure we KNOW that there is a log function here, just in case
  const savedFunctions = { log: console.log }
        
  //proceed with nuking the rest of the chattiness away
  Object.keys(console)
    .reduce((memo, key) => {
      if(typeof console[key] == "function" && functionsToPreserve.indexOf(key) != -1 ) {
        memo[key] = console[key];
        console[key] = noop;
      }
    
      return memo;
    }, 
    savedFunctions); //<- it's a const so we can't re-assign it. Besides, we don't need to, if we use it as a seed for reduce()
  
  console.log = function customLog() {
    //index 0 - the error message
    //index 1 - this function
    //index 2 - the calling function, i.e., the actual one that did console.log()
    const callingFile = new Error().stack.split("\n")[2];
    
    if (whitelist.some(entry => callingFile.includes(entry))) {
      savedFunctions.log.apply(console, arguments)
    }
  }

})(["myFile.js"]) //hey, it's SOMEWHAT configurable

Or a blacklist

(function(blacklist = [], functionsToPreserve = ["error"]) {
    function noop() {}

    //ensure we KNOW that there is a log function here, just in case
    const savedFunctions = {
        log: console.log
    }

    //proceed with nuking the rest of the chattiness away
    Object.keys(console)
        .reduce((memo, key) => {
                if (typeof console[key] == "function" && functionsToPreserve.indexOf(key) != -1) {
                    memo[key] = console[key];
                    console[key] = noop;
                }

                return memo;
            },
            savedFunctions); //<- it's a const so we can't re-assign it. Besides, we don't need to, if we use it as a seed for reduce()

    console.log = function customLog() {
        //index 0 - the error message
        //index 1 - this function
        //index 2 - the calling function, i.e., the actual one that did console.log()
        const callingFile = new Error().stack.split("\n")[2];

        if (blacklist.some(entry => callingFile.includes(entry))) {
            return;
        } else {
            savedFunctions.log.apply(console, arguments);
        }
    }

})(["myFile.js"])

So, this is a custom logger. Sure, it's not perfect but it will do the job. And, hey, since the whitelisting is a bit loose, it could be turned to an advantage:

  • to whitelist a bunch of files that share a substring, say, all myApp can include myApp1.js, myApp2.js, and myApp3.js.
  • although if you want specific files, you can just pass the full name, including extension. I doubt there would be a bunch of duplicate filenames.
  • finally, the stack trace will include the name of the calling function, if any, so you can actually just pass that and that will whitelist on per-function basis. However, it relies on the function having a name and it's more likely for function names to clash, so use with care

Other than that, there can certainly be improvements but that is the basis of it. The info/warn methods can also be overriden, for example.

So, this, if used, should only be in dev builds. There are a lot of ways to make it not go into production, so I won't discuss them but here is one thing I can mention: you can also use this anywhere if you save it as a bookmarklet

javascript:!function(){function c(){}var a=arguments.length<=0||void 0===arguments[0]?[]:arguments[0],b=arguments.length<=1||void 0===arguments[1]?["error"]:arguments[1],d={log:console.log};Object.keys(console).reduce(function(a,d){return"function"==typeof console[d]&&b.indexOf(d)!=-1&&(a[d]=console[d],console[d]=c),a},d),console.log=function(){var c=(new Error).stack.split("\n")[2];a.some(function(a){return c.includes(a)})&&d.log.apply(console,arguments)}}(["myFile.js"]);

This is it minified (although I passed it through Babel first, to use ES5 minification) and still configurable, to an extent, as you can change the very end where you can pass the whitelist. But other than that, it will work the same and is completely decoupled from the codebase. It will not run at pageload but if that's needed you can either use this as a userscript (still decoupled) or include it before other JS files in dev/debug builds only.

A note here - this will work in Chrome, Edge and Firefox. It's all the latest browsers, so I assume a developer will use at least one of them. The question is tagged as Chrome but I decided to widen the support. A Chrome only solution could work slightly better but it's not really a big loss of functionality.

VLAZ
  • 18,437
  • 8
  • 35
  • 54
  • Good morning vlaz, the last one (with whitelist filename) didn't fly for me, however all pollution messages have 'Received' word in my case so I modified your selective hearing example `const originalLog = console.log; console.log = function selectiveHearing() { if (arguments[0].indexOf("RECEIVED:") !== -1) { return; } return originalLog.apply(console, arguments) }` and this did it. Could I suggest instead of whitelisting files to do blacklisting instead to shut them up. Anyway thanks this is a great answer... – Matas Vaitkevicius Sep 26 '16 at 08:37
  • My bad... It does work, had to change this bit `if (whitelist.some(entry => callingFile.includes(entry))) { return; }else{savedFunctions.log.apply(console, arguments); }` to use it as blacklisting.... – Matas Vaitkevicius Sep 26 '16 at 08:44
  • Yes, my reason for whitelisting is that you generally won't know which files produce noise and which don't, so if you're interested in only some, you can just add those. Of course, you can do blacklisting as well, if that makes more sense. – VLAZ Sep 26 '16 at 10:43
  • Ofc you do :) http://i.stack.imgur.com/H5lfW.png I am now waiting to award you with bounty for such a great answer.... – Matas Vaitkevicius Sep 26 '16 at 10:47
0

It work in chrome: ...index.html

<html>
<body>
<script>
    (function(){
        var original = console.log;
        console.log = function(){
            var script = document.currentScript;
            alert(script.src);
            if(script.src === 'file:///C:/Users/degr/Desktop/script.js') {
                original.apply(console, arguments)
            }
        }
    })();
    console.log('this will be hidden');
</script>
<script src="script.js"></script>
</body>
</html>

...script.js

console.log('this will work');

Console.log does not work from index.html, but work from script.js. Both files situated on my desctop.

degr
  • 1,301
  • 1
  • 13
  • 33
  • Hi degr, thanks for your answer, unfortunately `document.currentScript;` returns `null` and then it fails with `VM119:5 Uncaught TypeError: Cannot read property 'src' of null` on `alert(script.src);` – Matas Vaitkevicius Sep 22 '16 at 11:07
  • from [here](https://developer.mozilla.org/en-US/docs/Web/API/Document/currentScript) It's important to note that this will not reference the – Max Koretskyi Sep 22 '16 at 11:18
  • according to @Maximus note, think this this is impossible. If currentScrip available only on initial process, you can't get unique identifier to differentiate which content you should display. – degr Sep 22 '16 at 11:57
  • @degr Nothing is impossible... :) I am now trying to play with `arguments.callee`.... http://stackoverflow.com/questions/280389/how-do-you-find-out-the-caller-function-in-javascript – Matas Vaitkevicius Sep 22 '16 at 13:35
  • Yes, it have sense, you can go up on arguments.callee.caller.caller.caller.... chain to find top-level component, but it depend of your project structure. – degr Sep 22 '16 at 14:20
  • Sorry, but what is so bad with my practical (and not so beautiful) solution ? – O_Z Sep 22 '16 at 15:48
  • Think for this issue it was correct answer, but somebody dislike 'eval' constructions. Also, it will no work in case when your js framework will take care about script loading (such as extJS). – degr Sep 23 '16 at 06:45
0

I've found these settings in the latest (July 2020) Chrome DevTools console to be helpful:

  1. DevTools | Console | (sidebar icon) | user messages
  2. DevTools | Console | (gear icon) | Select context only
  3. DevTools | Console | (gear icon) | Hide network

I like (1) most, I only see the messages from "my" code. (2) hides messages from my iframe.

Bob Stein
  • 12,526
  • 8
  • 72
  • 91
0

If it's an option to modify file, you can set a flag at top of file for disabling logs for that:

var DEBUG = false;
DEBUG && console.log("cyberpunk 2077");

To disable logs for all js files, put it once at top of any js file:

var DEBUG = false;
if (!DEBUG) {
   console.log = () => {};
 }
GorvGoyl
  • 27,835
  • 20
  • 141
  • 143
-1

This is not pretty but will work.
Put something like this in your file before the <script> tag of the "bad" library :

<script>function GetFile(JSFile) {      
    var MReq = new XMLHttpRequest();        
    MReq.open('GET', JSFile, false);    
    MReq.send();
    eval(MReq.responseText.replace(/console.log\(/g,"(function(){})("));            
}</script>

Then replace the tag

<script src="badLib.js">

With:

GetFile("badLib.js")

Only for short time debugging.

Mr Lister
  • 42,557
  • 14
  • 95
  • 136
O_Z
  • 1,449
  • 9
  • 10