How to disable console.log messages based on criteria from specific javascript source (method, file) or message contents
Asked Answered
F

6

12

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?

Faustena answered 22/9, 2016 at 9:14 Comment(12)
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?Wuhan
How to determine which console.log shoud be repaced ?Monoatomic
Do you want externally controlled (e.g., using some sort of configuration) or literally per-file controlled (e.g., change that in each file)?Unappealable
@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 :)Faustena
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?Unappealable
@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....Faustena
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.Unappealable
@vlaz I would appreciate that Vlaz. Thanks.Faustena
@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.Unappealable
@vlaz It would be ideal - yes.Faustena
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.Unappealable
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 ?Magic
U
10

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.

Unappealable answered 22/9, 2016 at 21:14 Comment(4)
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...Faustena
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....Faustena
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.Unappealable
Ofc you do :) i.sstatic.net/H5lfW.png I am now waiting to award you with bounty for such a great answer....Faustena
B
1

I've found these settings in recent (2020-2023) Chrome DevTools console to be helpful:

  1. sidebar icon: user messages. I like this option. Only see the messages from "my" code.
    devtools console sidebar menu

  2. gear icon: Select context only. "Selected context" refers to iframes versus the parent document.
    devtools selected context only

  3. gear icon: Hide network. "Network" refers to HTTP traffic.
    devtools hide network

Bury answered 1/7, 2020 at 16:42 Comment(1)
I'm sure the accepted answer gives you more flexibility, but this is just what I was after to ignore logging from 3rd party scripts and just focus on mine. I never noticed some of these options!Freemon
L
0

I was as troubled as you. This is my approach. https://github.com/jchnxu/guard-with-debug

Simple usage:

localStorage.debug = [
    'enable/console/log/in/this/file.ts',
    'enable/console/log/in/this/folder/*',
    '-disable/console/log/in/this/file.ts',
    '-disable/console/log/in/this/folder/*',
    
    // enable all
    '*',
].join(',');

The benefit: it's zero-runtime.

Disclaimer: I am the author of this tiny utility

Landmark answered 6/1, 2023 at 13:39 Comment(0)
D
-1

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.

Dowson answered 22/9, 2016 at 9:30 Comment(7)
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);Faustena
from here It's important to note that this will not reference the <script> element if the code in the script is being called as a callback or event handler; it will only reference the element while it's initially being processed.Mark
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.Dowson
@Dowson Nothing is impossible... :) I am now trying to play with arguments.callee.... #280889Faustena
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.Dowson
Sorry, but what is so bad with my practical (and not so beautiful) solution ?Magic
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).Dowson
A
-1

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 = () => {};
 }
Alumna answered 26/11, 2020 at 4:32 Comment(0)
M
-2

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.

Magic answered 22/9, 2016 at 10:6 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.