Does JavaScript automatically skip empty/bodiless functions? - javascript

"tl;dr" included
When trying to disable Logging so as to avoid spam once deploying, I used to do something like
if (isDebug)
console.log(...);
but I felt like (or read online) this would slow the code overall because the condition would be evaluated each time (and I usually include a lot of these, and the functions are called often, either via loops, setIntervals or w/e).
As such, I now switched to simply "emptying" the functions using a custom logger like
function LOGGER_MODULE_FACTORY() {
let current_log_level = "log";
return {
log: console.log,
info: console.info,
warn: console.warn,
error: console.error,
setDebug: function(logLevel) {
current_log_level = logLevel;
this.log = (logLevel === true || logLevel === "log") ? console.log : function () {};
this.info = (logLevel === true || logLevel === "log" || logLevel === "info") ? console.info : function () {};
this.warn = (logLevel === true || logLevel === "log" || logLevel === "info" || logLevel === "warn") ? console.warn : function () {};
this.error = (!logLevel) ? function () {} : console.error;
return logLevel;
},
getCurrent_log_level: function () { return current_log_level; }
}
}
Thinking that it would probably be quicker to run an empty function than evaluating an expression and it felt cleaner to write.
I tried creating a fiddle to test my theory and compare performance but the values are often pretty random...
Edit: jsperf test
Interesting results. "Chrome 64" 's results are from running in on Edge.
My reasoning relied on the fact that I read few things about CPUs and, apparently, they do things on their own in order to optimize the general run-time like, for instance, skipping useless operations: Say we have :
a = x
b = y
c = a + 3
The CPU would actually ignore the second line (or something... I am far from being an expert ahah, I just curiously read that fact).
Now, since we, on our browser, may access any globally declared variable at some point using the console, this cannot happen and so I felt I should simply ask online :
tl;dr:
Is calling an empty/bodiless function repeatedly (the pages I create basically run 24/7 on a dashboard) better, performance-wise than placing a condition in front of the original function (console.log / info / warn / error) ?
Basically, you are asking which is faster f = () => {}; f() or flag = false; if (flag) f()

isDebug won't be evaluated at every execution, but the stored variable will just be read.
It would be different if it was if (isDebug()), in this case, every time the parser meets the statement, the function will be evaluated.
Obviously, reading the isDebug boolean variable will add some overhead, but this is not perceptible since reading and handling variable is the main purpose of a programming language, so the time required for reading a boolean will always be lower than any other statement in the code.
Reassigning the console.log function is not a bad idea, it could be useful for enabling or disabling the logs all in one point. But this is a solution for a different problem

Related

Can you trap/identify if an object is being "invoked" as a function in runtime?

Given some basic class, such as a logical predicate class here:
const isACat = new Predicate(obj => obj.cat === true);
Is there a way to determine/trap/identify (perhaps via Reflection?) the context under which isACat is being "invoked/evaluated"? By "invoke/evaluate"--because I can't really think of a better word to use right now--I mean something like this:
console.log(isACat(object1)); // being "invoked/evaluated" as a function
console.log(isACat); // being "invoked/evaluated" as a non-function
I specifically mean this in the "runtime" sense, not in the typeof/instanceof sense.
For the ultimate goal of performing contingent behavior, such as (perhaps via a Proxy) returning a default function if that instance is being "invoked/evaluated" as a function.
Edit: Maybe in more precise terms, is there such a thing as a "default getter" when no further child prop is passed (i.e. isACat, but not isACat[ prop ])?
I am not seriously suggesting that you do any of the things presented bellow and you will spot their limitation immediately but I thought it was kind of fun to demonstrate.
[{dog: true}, {cat: true}].filter(isACat);// (referenced(), [{cat: true}])
isACat({dog: true}); // (referenced(), false)
let lives = 0;
lives += isACat; // (referenced(), 7)
`Felix ${isACat}` // (referenced(), "Felix is a cat")
The above requires the following, which you could probably generate with a Babel plugin or something (I mean: don't, obviously)
const referenced = (obj) => () => {
console.log(obj, 'was referenced');
return obj;
}
const _isACat = obj => obj.cat === true;
Object.defineProperty(_isACat, 'toString', {
value: () => 'is a cat'
});
Object.defineProperty(_isACat, 'valueOf', {
value: () => 7
});
Object.defineProperty(window, 'isACat', {
get: referenced(_isACat)
});
I don't know what I like the most about it: deceiving expectations thanks to getters, magical type coercion, or local variables leaking to the global scope. It is pure poetry.
More seriously, I don't think Javascript is the language for this but if for some reason you need meta-programming power, maybe give Clojure a go. You can also use macros with ClojureScript, which compiles to Javascript and has Javascript interop, but there is a runtime/compile time distinction which will limit what you can do.
No, there is no such thing. You're just accessing a variable, or whatever the reference to your object is stored in, and you get back a reference value. The object itself has no say about this process - it isn't even looked at yet. This is not "invocation" or "evaluation".
Really, if you need something like this, make the invocation explicit, and write isACat() (vs isACat(object) vs isACat.prop()).

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

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?
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.
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
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.
I've found these settings in the latest (July 2020) Chrome DevTools console to be helpful:
DevTools | Console | (sidebar icon) | user messages
DevTools | Console | (gear icon) | Select context only
DevTools | Console | (gear icon) | Hide network
I like (1) most, I only see the messages from "my" code. (2) hides messages from my iframe.
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 = () => {};
}
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.

Debug code removal using Google Closure Compiler

If I run the following code through advanced optimization, I can still see the debug statements in the code.
var log = console.info.bind(console);
(function() {
/** #const */
var DEBUG = false;
log('Brady', createRank({
max: 100,
debug: DEBUG
}));
})();
function createRank(options) {
if (options.debug) {
log('This should be in debug mode only');
}
if(typeof alert == 'function'){
alert(options);
}
return (Math.random() * options.max) | 0;
}
output after Advanced mode compilation
(function() {
var a = console.info.bind(console),
b = {
max: 100,
debug: !1
};
b.debug && a("This should be in debug mode only");
"function" == typeof alert && alert(b);
a("Brady", Math.random() * b.max | 0);
})();
How can we get rid of debug message with advanced mode?
if the DEBUG variable is defined as global, and logging statements are enclosed like
if (DEBUG) {
log('debug message');
}
then it would work but is there a way to make it work if we don't not want it as a global variable, and rather pass the value around to individual modules/functions via parameters.
This is a limitation of the current set of optimizations and when they are run. The optimizations are a trade off between compilation time and optimization and the choices made are not necessarily ideal for every code pattern.
In this particular case, the issue is that "property collapsing" only happens once for global scope, and that is before function inlining occurs ("property collapsing" for objects local to a function occurs during the main optimization loop). For the code in your example to be removed, "collapse properties" would need to run at least once more, or the function local version (which is more conservative) would need to be enhanced to run in global scope.
This is also discussed here: https://github.com/google/closure-compiler/issues/891

How to make console.log configurable

In my web-app( heavy on client side, light on server side), while in development, I need to do a lot of debugging, console.log is very helpful, but ideally in production, there must not be any debug message shown, so I am planning to add the below code:
window.production = false; // set to true when in production mode.
if(window.production){
window.log = function(){};
}else{
window.log = function(){
console.log.apply(console, arguments);
};
}
//then replace all console.log(a, b, c, ...) into window.log(a, b, c, ...) in my code.
is this a good way to make debugging configurable, or must I just make a grunt that removes all console.log lines for production?
So if your only end goal is to not show debug messages in production you have a huge amount of options to choose from! You should also decide if the following are important to you:
Being able to perform other work outside of your log statement that also should not run in production.
Minimizing file size in production and not shipping logging code.
Multiple log levels, or different logging groups.
The ability to turn these logging levels or groups on/off in production.
Minimizing the amount that you have to type for each log statement.
At a very basic level simply calling
if (window.console && window.console.log)
{
window.log = console.log.bind(console); // function devnull() { };
}
else
{
window.log = function() { };
}
log('This is a log!');
will be enough to allow you to turn logging on/off. This would fulfill goal (5) in the list above and works quite well.
An alternate solution which works well with minifiers like uglify which can remove dead code would be to surround your logging statements with something like (you may not want to pollute the global namespace however):
window.LogLevels =
{
Off: 0x00000000,
Error: 0x00000001,
Warning: 0x00000002,
Timing: 0x00000004,
Data: 0x00000008,
Status: 0x00000010,
...
Verbose: 0x04000000,
};
window.LogLevel = LogLevels.Error | LogLevels.Warning;
window.ShouldLog = function(mask)
{
return ((window.LogLevel & mask) === mask);
};
if (ShouldLog(LogLEvels.Error)) { log('This is an error!'); }
This would satisfy condition (1), (3), and (4) and sets you up to solve (2) as well at the cost of (5).
Coupled with a pre-defined DEBUG constant (or similar), in your build step you can replace the log statements with a regex:
productionCode = debugCode.replace(/ShouldLog\(((?!LogLevels\.Error|LogLevels\.Warning)[^)]*)\)/g, 'DEBUG');
This would completely remove non-error and non-warning level logging in your code and satisfy (2). You don't really want people peeking at your logs right.. plus better perf! :)
Bonus
If you want to get extra funky, you can use the following (at least in Chrome) to get a stack trace for each logging statement in your console. No more 'why did this log get hit'!
window.log = function ()
{
console.groupCollapsed.apply(console, arguments);
var stack = new Error().stack.split('\n');
for(var i = 2; i < stack.length; i ++)
{
// Trim and remove 'at ';
console.log('%c' + stack[i].trim().substring(3), 'padding-left: 10px; color: #777');
}
console.groupEnd();
};
If you are using build tools like Gulp.js or Grunt.js, then there are modules to do that :
gulp strip debug
grunt strip debug

How to Test an optional member of an object?

What is the best technique to test an optional object member. Right now we are prefacing the expect statements with an if:
if(object.member) expect(object).to.have.a.property('member').that.is.a('string');
but there must be a method that is more inline stylistically. E.g.
expect(object).to.have.an.optional.property('member').that.is.a('string');
or (adding would as an empty chain, for readability):
expect(object).to.have.an.optional.property('member').that.would.be.a('string');
or (moving the optional to provide an alternative version of expect):
optionally.expect(object).to.have.a.property('member').that.is.a('string');
update - I started to write this code (new to chai) to see if I could accomplish what I was targeting, so I added a small plugin:
module.exports = function(chai, utils) {
var Assertion = chai.Assertion
, i = utils.inspect
, flag = utils.flag;
var OPTIONAL_FLAG = 'chai-optional/option'
Assertion.addProperty('optional', function() {
flag(this, OPTIONAL_FLAG, true)
return this;
})
Assertion.overwriteMethod('property', function (_super) {
return function assertProperty (propertyName) {
if (flag(this, OPTIONAL_FLAG)) {
flag(this, OPTIONAL_FLAG, false) ;
var obj = this._obj;
var isPropertyPresent = (obj[propertyName]) ? true : false ;
if(isPropertyPresent) {
return _super.apply(this, arguments);
}
} else {
_super.apply(this, arguments);
}
};
});
Assertion.addProperty('would', function () {
return this;
});
};
WIth usage:
it('could be null or have a value', function(done){
var objWithout = {}
var objWith = {}
objWith.someProperty = 'blah'
expect(objWith).to.have.optional.property('someProperty').that.would.be.a('string');
expect(objWithout).to.have.optional.property('someProperty').that.would.be.a('string');
return done();
})
The current problem even when the property isn't present, the control of the function ends - but the evaluation chain continues. I need to end the evaluation with out a failing assertion - is this possible?
update either solution (simplistic solution):
var either = function(firstCondition){
var returnObject = {}
try{
firstCondition()
returnObject.or = function(secondCondition){ return }
} catch(e) {
returnObject.or = function(secondCondition){ return secondCondition() }
}
return returnObject ;
}
module.exports = either
I think the implementation is a little clunky - but fat arrow functions will hap thin out some of the syntax. So here is waiting on that!
The current problem even when the property isn't present, the control of the function ends - but the evaluation chain continues. I need to end the evaluation with out a failing assertion - is this possible?
After having read about chai's plugin guide I would have used a similar approach with a flag. However, I have reached the same conclusion - you cannot simply stop a chain.
A possibility I though of would be not only to implement new properties and a new flag, but to overwrite the assert method itself - to not throw when the OPTIONAL_FLAG flag on the current Assertion object is set. However, the chance to destroy everything or to miss an edge case is too hight.
After all, I don't think it's a good idea. Citing from this "confusing syntax" issue:
I think the misunderstanding comes from your expectation that Chai
follows most/all English grammar rules. Unfortunately, English grammar
has way too many rules (and exceptions to those rules) for it to be a
reasonable undertaking to implement.
The challenge with designing Chai's chain-able assertions is finding
the balance between being expressive and concise. Even if full grammar
wasn't a daunting task to implement and document, it would make the
API less concise, which is not good for a testing environment.
RULE: Any "flag" which modifies the behavior of an assertion (negation
not or inclusion include/contain , etc...), once set to true
should remain true until the end of the chain.
This means it is impossible to implement something like an .or operator as well.
What is possible though would be to implement something like
either(function(){
expect(object).not.to.have.a.property('member');
}).or(function(){
expect(object).to.have.a.property('member').that.is.a('string');
});
Maybe one can build a more appealing syntax on that.

Categories

Resources