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") {
|
if (typeof text === "string") {
|
||||||
return cyrb53(text);
|
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 {
|
function close(): void {
|
||||||
|
@ -54,6 +54,33 @@ export interface IErrorData {
|
|||||||
|
|
||||||
export const newIssueUrl = `https://github.com/bitburner-official/bitburner-src/issues/new`;
|
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 {
|
export function getErrorMetadata(error: unknown, errorInfo?: React.ErrorInfo, page?: Page): IErrorMetadata {
|
||||||
const isElectron = navigator.userAgent.toLowerCase().includes(" electron/");
|
const isElectron = navigator.userAgent.toLowerCase().includes(" electron/");
|
||||||
const env = process.env.NODE_ENV === "development" ? GameEnv.Development : GameEnv.Production;
|
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 {
|
export function getErrorForDisplay(error: unknown, errorInfo?: React.ErrorInfo, page?: Page): IErrorData {
|
||||||
const metadata = getErrorMetadata(error, errorInfo, page);
|
const metadata = getErrorMetadata(error, errorInfo, page);
|
||||||
|
const errorData = parseUnknownError(error);
|
||||||
const fileName = String(metadata.error.fileName);
|
const fileName = String(metadata.error.fileName);
|
||||||
const features =
|
const features =
|
||||||
`lang=${metadata.features.language} cookiesEnabled=${metadata.features.cookiesEnabled.toString()}` +
|
`lang=${metadata.features.language} cookiesEnabled=${metadata.features.cookiesEnabled.toString()}` +
|
||||||
` doNotTrack=${metadata.features.doNotTrack ?? "null"} indexedDb=${metadata.features.indexedDb.toString()}`;
|
` doNotTrack=${metadata.features.doNotTrack ?? "null"} indexedDb=${metadata.features.indexedDb.toString()}`;
|
||||||
|
|
||||||
const title = `${metadata.error.name}: ${metadata.error.message} (at "${metadata.page}")`;
|
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 = `
|
const body = `
|
||||||
## ${title}
|
## ${title}
|
||||||
|
|
||||||
@ -104,7 +144,7 @@ Please fill this information with details if relevant.
|
|||||||
|
|
||||||
### Environment
|
### Environment
|
||||||
|
|
||||||
* Error: ${String(metadata.error) ?? "n/a"}
|
* Error: ${errorData.errorAsString ?? "n/a"}
|
||||||
* Page: ${metadata.page ?? "n/a"}
|
* Page: ${metadata.page ?? "n/a"}
|
||||||
* Version: ${metadata.version.toDisplay()}
|
* Version: ${metadata.version.toDisplay()}
|
||||||
* Environment: ${GameEnv[metadata.environment]}
|
* Environment: ${GameEnv[metadata.environment]}
|
||||||
@ -115,9 +155,9 @@ Please fill this information with details if relevant.
|
|||||||
|
|
||||||
### Stack Trace
|
### Stack Trace
|
||||||
\`\`\`
|
\`\`\`
|
||||||
${metadata.error.stack}
|
${errorData.stack}
|
||||||
\`\`\`
|
\`\`\`
|
||||||
|
${causeAndCauseStack}
|
||||||
### React Component Stack
|
### React Component Stack
|
||||||
\`\`\`
|
\`\`\`
|
||||||
${metadata.errorInfo?.componentStack}
|
${metadata.errorInfo?.componentStack}
|
||||||
|
@ -1,8 +1,9 @@
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
import { dialogBoxCreate } from "../../ui/React/DialogBox";
|
import { dialogBoxCreate } from "../../ui/React/DialogBox";
|
||||||
import Typography from "@mui/material/Typography";
|
import Typography from "@mui/material/Typography";
|
||||||
import { getErrorMetadata } from "../ErrorHelper";
|
import { parseUnknownError } from "../ErrorHelper";
|
||||||
import { cyrb53 } from "../StringHelperFunctions";
|
import { cyrb53 } from "../StringHelperFunctions";
|
||||||
|
import { commitHash } from "./commitHash";
|
||||||
|
|
||||||
const errorSet = new Set<string>();
|
const errorSet = new Set<string>();
|
||||||
|
|
||||||
@ -17,31 +18,43 @@ const errorSet = new Set<string>();
|
|||||||
*/
|
*/
|
||||||
export function exceptionAlert(error: unknown, showOnlyOnce = false): void {
|
export function exceptionAlert(error: unknown, showOnlyOnce = false): void {
|
||||||
console.error(error);
|
console.error(error);
|
||||||
const errorAsString = String(error);
|
const errorData = parseUnknownError(error);
|
||||||
const errorStackTrace = error instanceof Error ? error.stack : undefined;
|
|
||||||
if (showOnlyOnce) {
|
if (showOnlyOnce) {
|
||||||
// Calculate the "id" of the error.
|
// Calculate the "id" of the error.
|
||||||
const errorId = cyrb53(errorAsString + errorStackTrace);
|
const errorId = cyrb53(errorData.errorAsString + errorData.stack);
|
||||||
// Check if we showed it
|
// Check if we showed it
|
||||||
if (errorSet.has(errorId)) {
|
if (errorSet.has(errorId)) {
|
||||||
return;
|
return;
|
||||||
} else {
|
}
|
||||||
errorSet.add(errorId);
|
errorSet.add(errorId);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
const errorMetadata = getErrorMetadata(error);
|
|
||||||
|
|
||||||
dialogBoxCreate(
|
dialogBoxCreate(
|
||||||
<>
|
<>
|
||||||
Caught an exception: {errorAsString}
|
Caught an exception: {errorData.errorAsString}
|
||||||
<br />
|
<br />
|
||||||
<br />
|
<br />
|
||||||
{errorStackTrace && (
|
{errorData.stack && (
|
||||||
<Typography component="div" style={{ whiteSpace: "pre-wrap" }}>
|
<Typography component="div" style={{ whiteSpace: "pre-wrap" }}>
|
||||||
Stack: {errorStackTrace}
|
Stack: {errorData.stack}
|
||||||
</Typography>
|
</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 />
|
<br />
|
||||||
UserAgent: {navigator.userAgent}
|
UserAgent: {navigator.userAgent}
|
||||||
<br />
|
<br />
|
||||||
|
Loading…
Reference in New Issue
Block a user