MISC: Always include stack trace in Recovery Mode (#1487)

We are getting some more error reports coming in that don't have enough
info in them. It turns out that populating the stack trace was gated
behind the dev flag; in reality, production builds are where we need it
most. Even if it ends up being obfuscated (source maps should prevent
this), we can figure out the actual source lines with enough effort if
need be.

This also changes to using the actual stack trace, rather than the
"component" trace (the tree of JSX objects), since knowing where the
code failed is far more valuable. Also, it ensures we get the full error
details when things go wrong in savefile loading.
This commit is contained in:
David Walker 2024-07-14 22:51:18 -07:00 committed by GitHub
parent 864613c616
commit abe7a43eec
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 15 additions and 12 deletions

@ -5,7 +5,7 @@ import { Settings } from "../../Settings/Settings";
import { load } from "../../db"; import { load } from "../../db";
import { Router } from "../GameRoot"; import { Router } from "../GameRoot";
import { Page } from "../Router"; import { Page } from "../Router";
import { IErrorData, newIssueUrl } from "../../utils/ErrorHelper"; import { IErrorData, newIssueUrl, getErrorForDisplay } from "../../utils/ErrorHelper";
import { DeleteGameButton } from "./DeleteGameButton"; import { DeleteGameButton } from "./DeleteGameButton";
import { SoftResetButton } from "./SoftResetButton"; import { SoftResetButton } from "./SoftResetButton";
@ -38,6 +38,13 @@ export function RecoveryRoot({ softReset, errorData, resetError }: IProps): Reac
} }
Settings.AutosaveInterval = 0; Settings.AutosaveInterval = 0;
// The architecture around RecoveryRoot is awkward, and it can be invoked in
// a number of ways. If we are invoked via a save error, sourceError will be set
// and we won't have decoded the information into errorData.
if (errorData == null && sourceError) {
errorData = getErrorForDisplay(sourceError, undefined, Page.LoadingScreen);
}
useEffect(() => { useEffect(() => {
load() load()
.then((content) => { .then((content) => {

@ -30,7 +30,7 @@ interface BrowserFeatures {
} }
interface IErrorMetadata { interface IErrorMetadata {
error: Error; error: Record<string, unknown>;
errorInfo?: React.ErrorInfo; errorInfo?: React.ErrorInfo;
page?: Page; page?: Page;
@ -54,7 +54,7 @@ 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`;
function getErrorMetadata(error: Error, errorInfo?: React.ErrorInfo, page?: Page): IErrorMetadata { 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;
const version: GameVersion = { const version: GameVersion = {
@ -70,19 +70,20 @@ function getErrorMetadata(error: Error, errorInfo?: React.ErrorInfo, page?: Page
doNotTrack: navigator.doNotTrack, doNotTrack: navigator.doNotTrack,
indexedDb: !!window.indexedDB, indexedDb: !!window.indexedDB,
}; };
const errorObj = typeof error === "object" && error !== null ? (error as Record<string, unknown>) : {};
const metadata: IErrorMetadata = { const metadata: IErrorMetadata = {
platform: isElectron ? Platform.Steam : Platform.Browser, platform: isElectron ? Platform.Steam : Platform.Browser,
environment: env, environment: env,
version, version,
features, features,
error, error: errorObj,
errorInfo, errorInfo,
page, page,
}; };
return metadata; return metadata;
} }
export function getErrorForDisplay(error: Error, 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 fileName = (metadata.error as any).fileName; const fileName = (metadata.error as any).fileName;
const features = const features =
@ -112,16 +113,11 @@ Please fill this information with details if relevant.
* Features: ${features} * Features: ${features}
* Source: ${fileName ?? "n/a"} * Source: ${fileName ?? "n/a"}
${
metadata.environment === GameEnv.Development
? `
### Stack Trace ### Stack Trace
\`\`\` \`\`\`
${metadata.errorInfo?.componentStack.toString().trim()} ${metadata.error.stack}
\`\`\` \`\`\`
`
: ""
}
### Save ### Save
\`\`\` \`\`\`
Copy your save here if possible Copy your save here if possible