The JavaScript console is a very useful debugging tool, but it can also introduce a very simple security risk into your application: 'console' method calls that aren't removed before code is deployed can leak sensitive information to the browser. That's why many developers use a linter to warn about console method calls and other issues. (On RedBit's web team, we use husky to run the linter on a pre-commit hook so that 'console' method calls are never committed by accident. We've found that many small bugs can be avoided by making linting mandatory before committing code.)
The problem is that there are times when you want to leave 'console' method calls in place. You can use 'eslint-disable' comments to bypass the linter, but you still run the risk of leaking sensitive information if you forget to clean up before you deploy. You could also wrap your 'console' method calls in a condition that checks a debug or environment flag and only executes the calls in development, but that's a bit awkward. It's also easy to forget so your users' security will depend on your memory. That certainly isn't a good strategy given that we all make mistakes from time to time.
A better solution would be to integrate the debug or environment condition into the console itself. We can do this by replacing the browser's 'window.console' object with a modified version of the same. Example 1 shows the function that does this.
Notice that the 'createDebugConsole' accepts the console object as an argument, rather than assuming it to be in scope. The function is designed this way for two reasons. First, it eliminates a dependency on the global scope which makes the function easier to unit test. Second, it makes it possible to use the function in non-browser environments that use a browser-like 'console' object that may not be attached to the 'window' global.
Let's examine this function line by line:
(Depending on your linter rules, you may or may not need the '/* eslint-disable no-prototype-builtins */' and '/* eslint-enable no-prototype-builtins */' comments.)
Example 2 shows how to use the function to replace the browser's default console with our new debug console. In this case, we're using 'process.env.NODE_ENV' to enable logging in non-production builds. (This assumes that your build process replaces 'process.env.NODE_ENV' with the value of the 'NODE_ENV' environment variable.) You could also set the 'enabled' option based on a value from a dotenv file or any other source of configuration.
Call 'createDebugConsole' once in your application's main 'index.js' file or wherever you do other setup work. You can now use the global 'console' object normally. If the condition that sets the 'enabled' value is 'true', the 'console' methods will behave normally. It it's 'false' the methods will return without doing anything. You can now be more confident that your production applications won't leak data to the console.
You could also assign the return value of 'createDebugConsole' to a new constant in case you need to leave the 'window.console' global untouched, or if you just don't want to use the global console, as shown in Example 3:
The approach you take should depend on your application and personal preferences. I prefer to use the 'window.console' global for two reasons:
The console created by 'createDebugConsole' will not be active in unit tests, so you won't be able to use it to prevent logging in your test environment. If you use jest you can [silence 'console' methods with the jest CLI.
Here is a short 'jest' suite that you can use to test 'createDebugConsole':