fix stack traces of query() to include the async context (#1762) #2983
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
This PR replaces the stack trace of errors in pool.query() and client.query() with a more useful stack trace including the callsite:
The following code:
Throws this with the current version of pg:
With this PR, it throws this much more useful error:
Why
For larger code-bases it has been a long-standing issue of not being able to find out which query or code a PG error comes from. I have years of logs with errors like
error: invalid input syntax for type bigint: "NaN"
orerror: could not serialize access due to concurrent update
with no way to know where they are coming from. This should fix that by directly linking the calling functions in the stack trace.How it works
Since Node 16, Node has included stack tracing for async functions. The reason these don't work out of the box in node-pg is that the response to a query comes from a different stack resulting from the message received via TCP, and this stack is not linked anywhere to the calling stack. As far as I know there's no way to directly do this, there's an API to do this for the async_context API (https://nodejs.org/api/async_context.html#static-method-asyncresourcebindfn-type-thisarg) that AFAIK uses the same technology as the zero-cost async stack traces, but it doesn't work for the integrated Error stack tracing.
Instead, the solution is to simply replace the Error stack at the first point where the async execution context is the "correct" one by catching and rethrowing the error. This should come at no cost for the happy path.
Limitations
.then(), .catch()
style calls but it definitely doesn't work for purely callback-based code. (this doesn't break anything, worst case you just won't get useful error traces as before)...catch(e => {e.cause = Object.assign(Object.create(Error.prototype), { stack: e.stack }); Error.captureStackTrace(e); throw e});
Compatibility
This code really only uses the Error.captureStackTrace function which has been there since Node 8. So this should be compatible starting with node 8+ and useful starting with Node 14 (with
--async-stack-traces
) and Node 16 (out of the box).References