mirror of
https://github.com/bitburner-official/bitburner-src.git
synced 2024-11-17 13:13:49 +01:00
MISC: Add error cause to exceptionAlert and Recovery mode (#1772)
This commit is contained in:
parent
246d668951
commit
4f84a894eb
@ -50,7 +50,22 @@ export function AlertManager({ hidden }: { hidden: boolean }): React.ReactElemen
|
||||
if (typeof text === "string") {
|
||||
return cyrb53(text);
|
||||
}
|
||||
return cyrb53(JSON.stringify(text.props));
|
||||
/**
|
||||
* JSON.stringify may throw an error in edge cases. One possible error is "TypeError: Converting circular structure
|
||||
* to JSON". It may happen in very special cases. This is the flow of one of them:
|
||||
* - An error occurred in GameRoot.tsx and we show a warning popup by calling "exceptionAlert" without delaying.
|
||||
* - "exceptionAlert" constructs a React element and passes it via "dialogBoxCreate" -> "AlertEvents.emit".
|
||||
* - When we receive the final React element here, the element's "props" property may contain a circular structure.
|
||||
*/
|
||||
let textPropsAsString;
|
||||
try {
|
||||
textPropsAsString = JSON.stringify(text.props);
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
// Use the current timestamp as the fallback value.
|
||||
textPropsAsString = Date.now().toString();
|
||||
}
|
||||
return cyrb53(textPropsAsString);
|
||||
}
|
||||
|
||||
function close(): void {
|
||||
|
@ -54,6 +54,33 @@ export interface IErrorData {
|
||||
|
||||
export const newIssueUrl = `https://github.com/bitburner-official/bitburner-src/issues/new`;
|
||||
|
||||
export function parseUnknownError(error: unknown): {
|
||||
errorAsString: string;
|
||||
stack?: string;
|
||||
causeAsString?: string;
|
||||
causeStack?: string;
|
||||
} {
|
||||
const errorAsString = String(error);
|
||||
let stack: string | undefined = undefined;
|
||||
let causeAsString: string | undefined = undefined;
|
||||
let causeStack: string | undefined = undefined;
|
||||
if (error instanceof Error) {
|
||||
stack = error.stack;
|
||||
if (error.cause != null) {
|
||||
causeAsString = String(error.cause);
|
||||
if (error.cause instanceof Error) {
|
||||
causeStack = error.cause.stack;
|
||||
}
|
||||
}
|
||||
}
|
||||
return {
|
||||
errorAsString,
|
||||
stack,
|
||||
causeAsString,
|
||||
causeStack,
|
||||
};
|
||||
}
|
||||
|
||||
export function getErrorMetadata(error: unknown, errorInfo?: React.ErrorInfo, page?: Page): IErrorMetadata {
|
||||
const isElectron = navigator.userAgent.toLowerCase().includes(" electron/");
|
||||
const env = process.env.NODE_ENV === "development" ? GameEnv.Development : GameEnv.Production;
|
||||
@ -85,12 +112,25 @@ export function getErrorMetadata(error: unknown, errorInfo?: React.ErrorInfo, pa
|
||||
|
||||
export function getErrorForDisplay(error: unknown, errorInfo?: React.ErrorInfo, page?: Page): IErrorData {
|
||||
const metadata = getErrorMetadata(error, errorInfo, page);
|
||||
const errorData = parseUnknownError(error);
|
||||
const fileName = String(metadata.error.fileName);
|
||||
const features =
|
||||
`lang=${metadata.features.language} cookiesEnabled=${metadata.features.cookiesEnabled.toString()}` +
|
||||
` doNotTrack=${metadata.features.doNotTrack ?? "null"} indexedDb=${metadata.features.indexedDb.toString()}`;
|
||||
|
||||
const title = `${metadata.error.name}: ${metadata.error.message} (at "${metadata.page}")`;
|
||||
let causeAndCauseStack = errorData.causeAsString
|
||||
? `
|
||||
### Error cause: ${errorData.causeAsString}
|
||||
`
|
||||
: "";
|
||||
if (errorData.causeStack) {
|
||||
causeAndCauseStack += `Cause stack:
|
||||
\`\`\`
|
||||
${errorData.causeStack}
|
||||
\`\`\`
|
||||
`;
|
||||
}
|
||||
const body = `
|
||||
## ${title}
|
||||
|
||||
@ -104,7 +144,7 @@ Please fill this information with details if relevant.
|
||||
|
||||
### Environment
|
||||
|
||||
* Error: ${String(metadata.error) ?? "n/a"}
|
||||
* Error: ${errorData.errorAsString ?? "n/a"}
|
||||
* Page: ${metadata.page ?? "n/a"}
|
||||
* Version: ${metadata.version.toDisplay()}
|
||||
* Environment: ${GameEnv[metadata.environment]}
|
||||
@ -115,9 +155,9 @@ Please fill this information with details if relevant.
|
||||
|
||||
### Stack Trace
|
||||
\`\`\`
|
||||
${metadata.error.stack}
|
||||
${errorData.stack}
|
||||
\`\`\`
|
||||
|
||||
${causeAndCauseStack}
|
||||
### React Component Stack
|
||||
\`\`\`
|
||||
${metadata.errorInfo?.componentStack}
|
||||
|
@ -1,8 +1,9 @@
|
||||
import React from "react";
|
||||
import { dialogBoxCreate } from "../../ui/React/DialogBox";
|
||||
import Typography from "@mui/material/Typography";
|
||||
import { getErrorMetadata } from "../ErrorHelper";
|
||||
import { parseUnknownError } from "../ErrorHelper";
|
||||
import { cyrb53 } from "../StringHelperFunctions";
|
||||
import { commitHash } from "./commitHash";
|
||||
|
||||
const errorSet = new Set<string>();
|
||||
|
||||
@ -17,31 +18,43 @@ const errorSet = new Set<string>();
|
||||
*/
|
||||
export function exceptionAlert(error: unknown, showOnlyOnce = false): void {
|
||||
console.error(error);
|
||||
const errorAsString = String(error);
|
||||
const errorStackTrace = error instanceof Error ? error.stack : undefined;
|
||||
const errorData = parseUnknownError(error);
|
||||
if (showOnlyOnce) {
|
||||
// Calculate the "id" of the error.
|
||||
const errorId = cyrb53(errorAsString + errorStackTrace);
|
||||
const errorId = cyrb53(errorData.errorAsString + errorData.stack);
|
||||
// Check if we showed it
|
||||
if (errorSet.has(errorId)) {
|
||||
return;
|
||||
} else {
|
||||
errorSet.add(errorId);
|
||||
}
|
||||
errorSet.add(errorId);
|
||||
}
|
||||
const errorMetadata = getErrorMetadata(error);
|
||||
|
||||
dialogBoxCreate(
|
||||
<>
|
||||
Caught an exception: {errorAsString}
|
||||
Caught an exception: {errorData.errorAsString}
|
||||
<br />
|
||||
<br />
|
||||
{errorStackTrace && (
|
||||
{errorData.stack && (
|
||||
<Typography component="div" style={{ whiteSpace: "pre-wrap" }}>
|
||||
Stack: {errorStackTrace}
|
||||
Stack: {errorData.stack}
|
||||
</Typography>
|
||||
)}
|
||||
Commit: {errorMetadata.version.commitHash}
|
||||
{errorData.causeAsString && (
|
||||
<>
|
||||
<br />
|
||||
<Typography component="div" style={{ whiteSpace: "pre-wrap" }}>
|
||||
Error cause: {errorData.causeAsString}
|
||||
{errorData.causeStack && (
|
||||
<>
|
||||
<br />
|
||||
Cause stack: {errorData.causeStack}
|
||||
</>
|
||||
)}
|
||||
</Typography>
|
||||
</>
|
||||
)}
|
||||
<br />
|
||||
Commit: {commitHash()}
|
||||
<br />
|
||||
UserAgent: {navigator.userAgent}
|
||||
<br />
|
||||
|
Loading…
Reference in New Issue
Block a user