Often when an assertion fails I want to see what kind of data caused the issue. That's very easy with the browser's developer tools, since I can see the values on the stack. But it's harder for node.js.
Is there any simple way to show the stack trace as well as the faulty values when an assertion fails, in a short-living node.js script?
This is problem that almost every node developer faces, right? I'm surprised that there's no obvious solution then.
How do you handle this?
Example
Here is a minimal example to show the problem:
import {strict as assert} from 'assert';
function sameX(a, b){
return a.x==b.x;
}
const a={x:1}; a.self=a;
const b={x:2}; b.self=b;
Let's say I expect sameX(a,b) to return true. If it doesn't, then I wish to print an error message, show the stack trace and show the current value of those variables.
How can I do it nicely in node?
❌ Failed attempt: extra arguments to node's assert
assert(sameX(a,b), "Objects with unexpected x", a, b);
This of course only prints:
Objects with unexpected x
since node's assert only takes two parameters.
❌ Failed attempt: stringify the objects
assert(sameX(a,b), `Objects with unexpected x ${a} ${b}`);
This prints:
Objects with unexpected x [object Object] [object Object]
I wish to see the content of a and b though.
❌ Failed attempt: JSON.stringify the objects
Something similar to:
assert(sameX(a,b), `Objects with unexpected x ${JSON.stringify(a)} ${JSON.stringify(b)}`);
Not only is a pain to type, but it may fail in some cases (like in this example where a and b have circular references).
❌ Failed attempt: console.assert
console.assert(sameX(a,b), "Objects with unexpected x", a, b);
This prints an error message I like:
Objects with unexpected x <ref *1> { x: 1, self: [Circular *1] } <ref *1> { x: 2, self: [Circular *1] }
However it doesn't throw an exception and thus doesn't even show the stack trace. I want to see the stack trace.
⚠️ Uncomfortable solution: connecting to the browser's developer tools
It's possible to debug Node using a browser's developer tools.
This works, but it's not a good option for a short-living/one-shot script that terminates in milliseconds.
Now, if there was something like a terminal that embeds the developer tools and connects them to the Node instance when one is in execution, that would be perfect. But I don't know any such terminal.
⚠️ Uncomfortable solution: custom assert function
Of course I can define my own assert function:
function assert(cond, ...msg){
if(!cond){
console.error("Assertion Failed!", ...msg);
throw new Error(msg[0]);
}
}
But I would have to copy and paste it in every project of mine and it would lack all the features of node's assert (for instance I would need to implement assert.deepEqual separately, if I need it).
A better alternative could be using an existing library. What library should I use for this though? After some searching I couldn't find any widespread library for this use case. Most assertion libraries are meant for test suites and are too heavy for production.
Related
The following error is killing my application in production:
/usr/src/app/node_modules/#nestjs/common/services/console-logger.service.js:137
? `${this.colorize('Object:', logLevel)}\n${JSON.stringify(message, (key, value) => typeof value === 'bigint' ? value.toString() : value, 2)}\n`
^
TypeError: Converting circular structure to JSON
--> starting at object with constructor 'ClientRequest'
| property 'socket' -> object with constructor 'Socket'
--- property '_httpMessage' closes the circle
at JSON.stringify (<anonymous>)
at ConsoleLogger.stringifyMessage (/usr/src/app/node_modules/#nestjs/common/services/console-logger.service.js:137:62)
at ConsoleLogger.formatMessage (/usr/src/app/node_modules/#nestjs/common/services/console-logger.service.js:130:29)
at /usr/src/app/node_modules/#nestjs/common/services/console-logger.service.js:122:43
at Array.forEach (<anonymous>)
at ConsoleLogger.printMessages (/usr/src/app/node_modules/#nestjs/common/services/console-logger.service.js:117:18)
at ConsoleLogger.error (/usr/src/app/node_modules/#nestjs/common/services/console-logger.service.js:44:14)
at Logger.error (/usr/src/app/node_modules/#nestjs/common/services/logger.service.js:34:75)
at Logger.descriptor.value (/usr/src/app/node_modules/#nestjs/common/services/logger.service.js:163:27)
at ExceptionsHandler.handleUnknownError (/usr/src/app/node_modules/#nestjs/core/exceptions/base-exception-filter.js:54:43)
I can't reproduce it on development and I don't know what is causing this error. Is there any way to make the stack trace include the source of this error?
I already ran it with DEBUG=*, but it doesn't give me a conclusive answer.
I think the error is happening close to these lines (based on logs), but I can't tell for sure:
this.logger.error(error.toString())
throw new InternalServerErrorException(error.toString())
It doesn't seems to be related, because error.toString() evaluates to [object Object] (useless, but not wrong).
Based on the properties, it looks like probably an AxiosError or some other HTTP client error was thrown directly without being converted into an HttpException that Nest would know how to handle and Nest then tried to log out the error. I would check your uses of HttpService (if you use it) or any other HTTP clients you use. If you still can't find anything, I'd suggest using a different logger than Nest's that can handle circular JSON. My preference is ogma, but I'm also the author of it. pino is another good choice
I created some end-2-end with cypress. Locally tests work fine, but when these tests run on CircleCI it shows errors from cypress
CypressError: Timed out retrying: You attempted to make a chai-jQuery assertion on an object that is neither a DOM object or a jQuery object.
The chai-jQuery assertion you used was:
> css
The invalid subject you asserted on was:
> 250px
To use chai-jQuery assertions your subject must be valid.
This can sometimes happen if a previous assertion changed the subject.
And this is the code responsible for this error.
cy.get('.vce-single-image')
.should('have.css', 'width')
.and('have.css', 'height')
The same error for this one.
cy.window()
.then((win) => {
cy.get('.vce-row')
.should('have.css', 'width')
.and('have.css', 'height')
})
I tried to add first() after get() but it didn't help. I tried it locally on different devices and there're no problems with that.
For CircleCi docker image I use own image, which is based on circleci/php:7.3.2-apache. Here is the link https://github.com/wpbakery/ci-wordpress/blob/master/circleci/Dockerfile.
Normally, cy.should() yields the value that it was chained off of. However, some assertions, including have.css, change the subject yielded by cy.should(). (here is the relevant chai-jquery documentation)
So, this should work:
cy.get('.vce-single-image').should('have.css', 'width', '250px')
cy.get('.vce-single-image').should('have.css', 'height', '250px')
Relevant reading: How do I know which assertions change the subject and which keep it the same?
Anyone knows a trick for filtering out React warnings in the browser console caused by some node module? They are making things harder to debug and at the moment I can't do anything about it.
Whilst not being specific to React:
If you are using Chrome there is an option in the console to filter out which messages are shown. It's the funnel like icon under the Console tab. Then you can choose to filter by Err, Warning, Info, etc...
One option is to overwrite the logging functions of the console object. For example, to filter out "warnings" about the invalid prop type notation that is raised from a package in node_modules, you can add this on top of your main index.jsx file:
const originalConsoleError = console.error;
console.error = (message, ...messageArgs) => {
// console.log({message, messageArgs}); // uncomment only to see what arguments you receive
if (
messageArgs[1] &&
messageArgs[1].includes("has invalid PropType notation inside")
) {
return;
}
originalConsoleError(message, ...messageArgs);
};
You can of course perform more complicated checks - uncomment the console.log to see what message format arguments you receive.
This is something that just started happening yesterday, and it's causing me a big headache.
As part of my build for arcade.ly I have a check-scripts task that strips debugging code and runs jshint over all my JavaScript:
gulp.task('check-scripts', function () {
return gulp.src([
'src/static/scripts/common/pinjector.js',
'src/static/scripts/common/service.*.js',
'src/static/scripts/asteroids-starcastle/*.js',
'src/static/scripts/asteroids/asteroids.js',
'src/static/scripts/asteroids/app.js',
'src/static/scripts/starcastle/actor.*.js',
'src/static/scripts/starcastle/service.*.js',
'src/static/scripts/starcastle/starcastle.js',
'src/static/scripts/starcastle/app.js',
'src/static/scripts/space-invaders/actor.*.js',
'src/static/scripts/space-invaders/service.*.js',
'src/static/scripts/space-invaders/space-invaders.js',
'src/static/scripts/space-invaders/app.js',
'src/server/*.js',
])
.pipe(stripDebug())
.pipe(jshint({
laxbreak: true,
multistr: true
}))
.pipe(jshint.reporter('default'))
.pipe(jshint.reporter('fail'));
});
Up until yesterday this was working absolutely fine and, if there was a problem, I'd get a helpful jshint error telling me exactly what's wrong. Now, when I run gulp check-scripts, if there's a problem I often get this kind of error:
11:47:00] Starting 'check-scripts'...
events.js:154
throw er; // Unhandled 'error' event
^
Error: Line 30: Unexpected identifier
at constructError (/Library/WebServer/Documents/arcade/node_modules/esprima/esprima.js:2406:21)
at createError (/Library/WebServer/Documents/arcade/node_modules/esprima/esprima.js:2425:17)
at unexpectedTokenError (/Library/WebServer/Documents/arcade/node_modules/esprima/esprima.js:2499:13)
at throwUnexpectedToken (/Library/WebServer/Documents/arcade/node_modules/esprima/esprima.js:2504:15)
at expect (/Library/WebServer/Documents/arcade/node_modules/esprima/esprima.js:2522:13)
at expectCommaSeparator (/Library/WebServer/Documents/arcade/node_modules/esprima/esprima.js:2546:13)
at parseObjectInitializer (/Library/WebServer/Documents/arcade/node_modules/esprima/esprima.js:3052:17)
at inheritCoverGrammar (/Library/WebServer/Documents/arcade/node_modules/esprima/esprima.js:2680:18)
at parsePrimaryExpression (/Library/WebServer/Documents/arcade/node_modules/esprima/esprima.js:3246:20)
at inheritCoverGrammar (/Library/WebServer/Documents/arcade/node_modules/esprima/esprima.js:2680:18)
As you can see, there's no useful information in there. At the moment I'm refactoring so my changesets often cover a number of files. The issue here is I can see esprima's parser is unhappy but I have no idea which file is breaking it, nor which line of code within that file.
I can figure it out by iteratively commenting and uncommenting lines in check-scripts and rerunning gulp-checkscripts, and eventually by commenting and uncommenting code, but it's obviously a bit of a chore.
In this case this is the code that caused the error:
function masterSoundConfiguration() {
var masterConfig = {
elementCountForRapidRepeat: 6,
sfxMasterVolume: 0.4,
musicMasterVolume: 0.6,
sounds: [],
music: [] // <-- Missing comma
mergeIntoMaster: mergeIntoMaster
};
...
}
As you can see, there's a missing comma, which is causing the error.
The problem seems to be caused by gulp-strip-debug, but if I simply disable this jshint will complain because of debugger statements. Since I need to build to run the site even in dev, this is a hassle.
I suppose I could disable gulp-strip-debug and configure jshint to ignore debugger statements, for dev builds only, but is there a way to configure gulp-strip-debug to report errors better, or is this a bug/design flaw/oversight?
Any insight/suggestions would be gratefully received.
Thanks,
Bart
The gulp-strip-debug library is a very thin wrapper around strip-debug so I'm tempted to conclude this is a bug (or deficiency) in strip-debug, although it might also be in the way gulp-strip-debug reports errors from strip-debug.
I need to investigate a little further and possibly put in a PR. It would certainly be easy enough to get it to report which file the error is in, even without the line number, which would be a big improvement in itself.
I have reviewed how to create custom errors in JS, and wrote following code:
function AssertException(message)
{
"use strict";
Error.captureStackTrace(this, AssertException);
Object.assign(this, {name: 'AssertException', message: message});
}
AssertException.prototype = Object.create(Error.prototype);
AssertException.prototype.name = 'AssertException'
Now, when I try to create instance and output it in console (in Chrome), it just shows it as object instead of error syntax (with stack in ellipsis block).
See screenshot for details.
Is there way to define an error class that its output will be shown as for standard error classes (with stack in ellipsis)?
PS. console.log(new AssertException('abc').stack) shows expanded stack as string.
Google Chrome Version 50.0.2661.94 m
Console output screenshot for details
Although I do not have any code to prove the point, I believe the difference is to due to different "handlers" used by Chrome.
The "Error" object and all its six subtypes are native objects. Therefore, for printing on console, Chrome uses a native handler. Perhaps, someone with more visibility to the chrome v8 source code can point to the exact handler.
The "AssertException" object created by your code is non-native. Hence, the use of "Object Handler" to print its contents.
FYI..you can check if a function is native or not using the following:
https://gist.github.com/hilbix/a298f4c969593fabb08074628dc304b8