Posted on
console.log
If you've tried to do this sort of thing before, you'll understand that it's a pain to keep the correct line number and source mapping in the output if you are using a higher-order function or closure. This problem arises when you'd like to run conditionals or hooks before console.log
executes.
If you don't know what I mean - that's okay - just know that the function needs to be called at the actual line number you want. In short, you cannot mess with any of the parameters before they enter console.log
. The function needs to be called directly.
Here's a short example:
JSfunction log(message, ...args) { console.log("I am messing with console.log...", message, ...args); } log("hello!", {}); // logs with line number = 2, in function log
The output will be as you expect, but it will show the line number from where it is called, not where log("hello!", {})
is called.
If we want the line number, we have to do something like this:
JSvar log = console.log; log("hello!", {}); // logs with line number = 3, where we want it
There is another route we can take if we desire to mess with the logging parameters yet keep the line number, but it's a bit verbose. It is the best (and probably only) approach I have found that enables this sort of capability.
JSfunction log(message, ...args) { return Function.prototype.bind.apply(console.log, console, ["hi!", message, ...args]); } log("hello!", {})(); // logs "hi!" and "hello!" with line number = 4, where we want it // NOTE THE EXTRA PARENTHESES!
Essentially, we bind the function console.log
to the console
object (to make sure this = console
) and return a pre-baked function with the desired parameters. However, we do have to add an extra set of parentheses to make sure that returned function is called.
So, we've gone over the limitations of console.log
and ways around that. If you desire to just turn off logging based on a global parameter, you'd be fine with the first approach, like so:
JSconst debug = true; const log = Function.prototype; // no-op function by default if (debug && window.console) { log = window.console.log; } log("hello!"); // works if debug == true, else does nothing
If you want to add conditionals, format the output, or add hooks that execute before the log (like logging to a remote server) then you'll need the second approach. Here's a quick example:
JSconst debug = true; const log = Function.prototype; // no-op function by default if (debug && window.console) { log = window.console.log; } function log(debug, hook, format) { return (message, ...args) => { if (debug) { hook(message, args); // call our hook return Function.prototype.bind.apply(console.log, console, format(message, args)); // return our binded function with a formatted output } return Function.prototype; // else return no-op function! } } log("hello!")(); // works if debug == true, else does nothing
This is a powerful approach, albeit a bit verbose. You can add pretty much anything you need to execute as long as you return the bound, applicated function at the end. You could use this to "turn off" parts of your application, resolve stack traces to a remote server, etc.
Keep in mind the no-op function has minimal to no performance impact (see here for a great article on the subject). Therefore, you could leave all your logging statements in production code, although it's best to (a) leave a cleanup script that will remove these statements or (b) make sure you redefine the console with no-op functions when building to production.
Thanks for reading!