Concat runAsynchronously stacktraces

This commit is contained in:
Konstantin Wohlwend 2024-11-19 03:53:48 +01:00
parent 010656e586
commit 539e10981e
3 changed files with 29 additions and 16 deletions

View File

@ -15,7 +15,7 @@ if (process.env.NODE_ENV === 'development' && isBrowserLike()) {
for (const prop of ["warn", "error"] as const) {
const original = console[prop];
console[prop] = (...args) => {
original(...args, new Error("This error was caught by DevErrorNotifier, and the original stacktrace is below."));
original(...args, { devErrorNotifierNote: new Error("The error above was caught by DevErrorNotifier, and the original stacktrace is below.") });
if (!neverNotify.some((msg) => args.some((arg) => `${arg}`.includes(msg)))) {
callbacks.forEach((cb) => cb(prop, args));
}

View File

@ -16,6 +16,29 @@ export function throwErr(...args: any[]): never {
}
}
function removeStacktraceNameLine(stack: string): string {
// some browsers (eg. Chrome) prepend the stack with an extra line with the error name
const addsNameLine = new Error().stack?.startsWith("Error\n");
return stack.split("\n").slice(addsNameLine ? 1 : 0).join("\n");
}
export function concatStacktraces(first: Error, ...errors: Error[]): void {
// some browsers (eg. Firefox) add an extra empty line at the end
const addsEmptyLineAtEnd = first.stack?.endsWith("\n");
// Add a reference to this function itself so that we know that stacktraces were concatenated
// If you are coming here from a stacktrace, please know that the two parts before and after this line are different
// stacktraces that were concatenated with concatStacktraces
const separator = removeStacktraceNameLine(new Error().stack ?? "").split("\n")[0];
for (const error of errors) {
const toAppend = removeStacktraceNameLine(error.stack ?? "");
first.stack += (addsEmptyLineAtEnd ? "" : "\n") + separator + "\n" + toAppend;
}
}
export class StackAssertionError extends Error implements ErrorWithCustomCapture {
constructor(message: string, public readonly extraData?: Record<string, any>, options?: ErrorOptions) {

View File

@ -1,4 +1,4 @@
import { StackAssertionError, captureError } from "./errors";
import { StackAssertionError, captureError, concatStacktraces } from "./errors";
import { DependenciesMap } from "./maps";
import { Result } from "./results";
import { generateUuid } from "./uuids";
@ -122,13 +122,6 @@ export async function waitUntil(date: Date) {
return await wait(date.getTime() - Date.now());
}
class ErrorDuringRunAsynchronously extends Error {
constructor() {
super("The error above originated in a runAsynchronously() call. Here is the stacktrace associated with it.");
this.name = "ErrorDuringRunAsynchronously";
}
}
export function runAsynchronouslyWithAlert(...args: Parameters<typeof runAsynchronously>) {
return runAsynchronously(
args[0],
@ -153,17 +146,13 @@ export function runAsynchronously(
if (typeof promiseOrFunc === "function") {
promiseOrFunc = promiseOrFunc();
}
const duringError = new ErrorDuringRunAsynchronously();
const duringError = new Error();
promiseOrFunc?.catch(error => {
const newError = new StackAssertionError(
"Uncaught error in asynchronous function: " + error.toString(),
{
duringError,
},
{
cause: error,
}
{ cause: error },
);
concatStacktraces(newError, duringError);
options.onError?.(newError);
if (!options.noErrorLogging) {
captureError("runAsynchronously", newError);
@ -264,6 +253,7 @@ export function rateLimited<T>(
runAsynchronously(async () => {
while (true) {
await next();
throw new Error("test");
}
});