How can I override/extend ReferenceError in Chrome's JavaScript? - javascript

To make debugging easier, I'm capturing all of the console logs in Chrome so that users who submit a feedback entry will also submit all of the logs to our server. When someone encounters a problem in production, I can first and foremost get them back to work so that I can then sit down and more thoroughly go through all of the logs to determine the root cause of whatever issue the user encountered in production.
The technique I use to capture the logs involves overriding console.log so that all text entered in the first argument gets stored in an array while simultaneously invoking the legacy function so that I can still see the logs in the console too.
The problem is when there's the occasional uncaught exception. These aren't included in the uploaded logs, so it's not always clear what caused the problem. So I tried overriding ReferenceError by writing a JavaScript function that takes a function as an argument, then returns a new function that does stuff with it, like storing data in a variable, and then invoking the legacy function as the last step:
function overrideException(legacyFn) {
/** arguments for original fn **/
return function() {
var args = [];
args[0] = arguments[0];
// pass in as arguments to original function and store result to
// prove we overrode the ReferenceError
output = ">> " + legacyFn.apply(this, args).stack;
return legacyFn.apply(this, arguments);
}
}
To test the overrideException function, I ran the following code on the console:
ReferenceError = overrideException(ReferenceError);
Afterwards, I tested the returned function, the new ReferenceError, by manually throwing a ReferenceError:
throw new ReferenceError("YES!! IT WORKS! HAHAHA!");
The resulting output on the console is:
ReferenceError: YES!! IT WORKS! HAHAHA!
And checking the global variable output from the overrideException function shows that it did indeed run:
output
">> ReferenceError: YES!! IT WORKS! HAHAHA!
at ReferenceError (<anonymous>)
at new <anonymous> (<anonymous>:18:35)
at <anonymous>:2:7
at Object.InjectedScript._evaluateOn (<anonymous>:562:39)
at Object.InjectedScript._evaluateAndWrap (<anonymous>:521:52)
at Object.InjectedScript.evaluate (<anonymous>:440:21)"
Now, here's where things start to fall apart. In our code, we're not going to know when an uncaught exception occurs, so I tested it by attempting to run a function that doesn't exist:
ttt();
Which results in:
ReferenceError: ttt is not defined
However, unlike the case where we explicitly throw an error, in this case, the function doesn't fire, and we're left with only the legacy functionality. The contents of the variable output is the same as in the first test.
So the question seems to be this: How do we override the ReferenceError functionality that the JavaScript engine uses to throw errors so that it's the same one we use when we throw a ReferenceError?
Keep in mind that my problem is limited only to Chrome at this time; I'm building a Chrome Packaged app.

I have done quite a bit of research for the same reason: I wanted to log errors and report them.
"Overriding" a native type (whether ReferenceError, String, or Array) is not possible.
Chrome binds these before any Javascript is run, so redefining window.ReferenceError has no effect.
You can extend ReferenceError with something like ReferenceError.prototype.extension = function() { return 0; }, or even override toString (for consistency, try it on the page, not the Dev Tools).
That doesn't help you much.
But not to worry....
(1) Use window.onerror to get file name, 1-indexed line number, and 0-indexed position of uncaught errors, as well as the error itself.
var errorData = [];
onerror = function(message, file, line, position, error) {
errorData.push({message:message, file:file, line:line, position:position, error:error});
};
See the fiddle for an example. Since the OP was Chrome-specific, this has only been tested to work in Chrome.
(2) Because of improvements to (1), this is no longer necessary, but I leave this second technique here for completeness, and since onerror is not guaranteed to work for all errors on all browsers. You will also sometimes see the following:
var errors = [];
function protectedFunction(f) {
return function() {
try {
f.apply(this, arguments);
} catch(e) {
errors.push(e);
throw e;
}
};
}
setTimeout = protectedFunction(setTimeout);
setInterval = protectedFunction(setInterval);
etc...
FYI, all this is very similar to what has been done in the Google Closure Compiler library, in goog.debug, created during Gmail development with the intent of doing exactly this. Of particular interest is goog.debug.ErrorHandler and goog.debug.ErrorReporter.

Related

How to apply expect over softAssertAll or show explicity in the test result that the softAssertAll passed (Error: Expected Object to be a function)

I'm using 'soft-assert' library (soft-assert library) to apply assertion on my test steps without stopping the test if any of them fail.
According to the documentation, all soft-assert is verified at the end of the test by using softAssertAll() command. And this works very well. However, if nothing fails, I don't see any explicit message in my test result as when I use expect command.
So, I'm trying to apply expect over the softAssertAll() command, as it's seen below, but I'm getting the error message:
"expected { Object (userInvocationStack, specWindow, ...) } to be a function"
What I'm trying to do:
expect(cy.softAssertAll()).not.throw(Error)
Does anyone know how can I do this or solve the error in the image below?
Thanks in advance.
See the Chai example for throw
var badFn = function () { throw new TypeError('Illegal salmon!'); };
expect(badFn).to.throw();
Note you pass in the function name without invoking it. I think this allows chai to wrap the function invocation in a try-catch and gracefully report the failure.
You can't do the same with a Cypress custom command as it will not raise errors in the same way as badFn above. Internally it swallows any error and sets the state of the test to "failed".
You could reasonably expect this to work
expect(jsonAssertion.softAssertAll).not.throw()
however there's an internal error in jsonAssertion that seems to be related to the this reference inside it's class.
TypeError: Cannot read properties of undefined (reading '_softThrowJsonDiffArray')
To fix, use an arrow function
const testSoftAssertAll = () => jsonAssertion.softAssertAll();
expect(testSoftAssertAll).not.throw()
or shorter
expect(() => jsonAssertion.softAssertAll()).not.throw()
Check the diff array
This is cleaner and clearer
// expect all soft-assert to be passing
expect(jsonAssertion.jsonDiffArray).to.eq(undefined)
// expect first soft-assert to fail with a certain message
expect(jsonAssertion.jsonDiffArray[0].error.message).to.contain('Illegal salmon')

Throw new Error doesn't throw and node.js don't complain about nonexistent variables

I'm believe i'm having some problem with node.js, for at least 4 months, but just now i want fix. I already try to re-install, currently i'm using 6.0.0.
My throw new Error('message'); are being ignored by the engine, when i try to do:
console.log('test');
throw new Error('error');
console.log('test');
I see only:
test
test
Also, when i try to console.log in nonexistent variables, nothing happens.
What could be?
Thanks.
It seems likely there is something about the context of your code that is confusing you. We could help you figure that out if you showed us the context of this code and if you change what the second console.log('test'); outputs so you could tell the difference between the second log statement executing vs. your code just getting called twice (which is my guess).
And, as a vanilla test of your node installation, I'd suggest that you take this set of code and put it in a file named throw-test.js and run just that file:
// throw-test.js
try {
console.log('test1');
throw new Error('error');
console.log('test2');
} catch(e) {
console.log('log exception');
}
You should see this in the console when you run node throw-test.js:
test1
log exception
That's exactly what I see in my running node.js and is what would be expected.
Other things that could be confusing the situation:
Your code might be getting called twice and that's why you get the output you get.
Your code might be inside an async callback that eats exceptions (that's why you don't see any exception logged).
You have a console.log('test') somewhere else in your code and that's why you see two of them.
As for your other comment, the code:
console.log(nonExistent);
will throw a ReferenceError exception. Whether you see that exception or not in your logs depends upon where this code is. Again, if it's inside an async callback, the exception may be eaten (depends upon the specific callback).

Node.js: calling an undefined function as object property goes unnoticed

I did notice a worrysome behaviour with node.js (v4.0.0).
I have an object with functions, something like this:
var local = {};
local.myMethod = function() {
console.log('here I am');
};
At a certain point in time I did comment out myMethod() for debugging purposes.
Some time later, I did call it again in my code, forgetting to remove comments from function definition:
...
var result = local.myMethod();
...
/*
local.myMethod = function() {
console.log('here I am');
};
*/
When running that code, I got no compile nor runtime error, so I got mad catching the silly problem...
What I ask is: Is this behaviour intended?
Should I use some pragma enforcing strictness, to avoid such problems?
UPDATE
Sorry, this code does not reproduce the problem, I did shrink it too much...
I'm quite sure on some condition the problem shows, however: I have a warning set just before the call to the undefined function... The warning prints, and then the code continues without a notice...
The undefined function is called inside an async.each() function, but I can't yet simplify my code enough to post it here... Keep trying...

Getting the stack trace of a console message

Sometimes I see stray log statements when I load up my application, and I don't remember how the log statement was called. Is there a way to find the stack trace of a console log message from the Chrome developer tools?
Right now, I wrap console.log with a function that prints the stack trace:
console.log = (function () {
var oldLog = console.log.bind(console);
return function () {
try { throw new Error(); } catch (e) {
oldLog(
e.stack.split('\n')
.slice(2)
.map((line) => line.trim().replace('at', 'from'))
.join('\n')
);
}
oldLog.apply(null, Array.prototype.slice.call(arguments));
};
}());
This will print the stack trace in addition to the log message. For this to be useful, I have to insert this code just before any of my code runs. Is there a way to find out this information from just the console, without having to add this wrap?
I primarily use Chrome developer tools, but I'd be interested if this was possible in any other browser.
Have you tried console.trace()? It is available in Chrome and Firefox. For your use-case, you could alias log to trace.
That said, I usually wrap logger statements in a custom logger function that itself wraps whatever I need to use, but which can be programmatically disabled in production to prevent leaking logs.
EDIT:
There aren't currently any chrome://flags that would let you set that behaviour, but you could do this before your console gets invoked (such as at the top of your minified js): console.log=console.trace;
Which will have this effect, (example in Chrome Canary's console):
> console.log("Hello, Robz");
VM929:2 Hello, Robz
undefined
> console.log = console.trace;
function trace() { [native code] }
> console.log("Hello, Robz");
VM935:2 Hello, Robz
VM935:2 (anonymous function)
VM468:777 InjectedScript._evaluateOn
VM468:710 InjectedScript._evaluateAndWrap
VM468:626 InjectedScript.evaluate
Basically. To show the source of a logging-message was a very bad idea. It leads you to write logging messages that neither belongs to the code nor can be localized without a stack-trace.
How do you work in a Team with it?
hey, i got a `ohoh!`-logging in file abc.js in line 3487!
To understand the problem by inspecting the call-stack means: Hey i do not need a text-message, i do not need a message what exactly is going on, just where.

Is it possible to override console logs

If I receive an error from a framework or an error from the browser. Basically a runtime error of any kind. Without modifying the framework, is it possible for me to override the console logs that these frameworks make and the browser's errors. I want to use my own framework with own error handling system when informing the user of errors of practically anything runtime (not syntax errors). I don't know if you would class it all as runtime errors because of the way javascript is executed in the browser but hopefully you will get me?
Is this possible if all the frameworks are written in Javascript?
How is this achieved?
What considerations do I have to make between different browsers?
Thanks
You are probably looking for a try-catch block:
try {
alert(foo);
} catch(e) {
alert('The code got the following error: '+e.message);
}
Whenever the code between the try {} receives an error, the catch(e) {} block will execute, with the argument e being the error object for the error that occurred. In this case, the variable foo is not defined, so executing this code will result in an alert message saying "The code got the following error: foo is not defined"
While not over-riding console.log, you may be achieve the same effect by overriding window.onerror.
From the MDN documentation
window.onerror = function myErrorHandler(errorMsg, url, lineNumber) {
// Log the error here -- perhaps using an AJAX call
}
You could try overriding the console.log() function.
//Save original reference
var originalConsole = console;
//Override
console = {};
console.log = function()
{
//According to MDN the console.log function can receive a variable number of params
for(var i=0; i<arguments.length; i++)
{
//Make your changes here, then call the original console.log function
originalConsole.log("Change something: "+arguments[i]);
}
//Or maybe do something here after parsing all the arguments
//...
}
console.log("one", "two");
JSFiddle here.
You can override the console logs by creating a "console" object and overriding it's .log() function:
var console = {};
console.log = function(){};
Some browsers require this to be added to the window object directly; so, for browser compatibility, also add:
window.console = console;
Additionally, you can override other console functions (e.g. console.info, console.warn and console.error) if you're using those too.
Also, consider reading this blog post from Udi Talias on overriding the console functions. Good and quick read!
You can custom your console.log here
// copy the original
let originalConsole = Object.assign({}, console);
// do something with your log.
console.log = (value) => {
//some cool condition
if (true) {
value = "new_log : " + value
}
originalConsole.log(value);
};

Categories