feat(fe): enable error logger and reporting

This commit is contained in:
hexxa 2021-12-28 23:18:08 +08:00 committed by Hexxa
parent 3cb711b37e
commit 9a7cfcb097
10 changed files with 153 additions and 35 deletions

View file

@ -270,12 +270,33 @@
padding: 1rem 0 1rem 0;
}
.theme-default .upload-item .error {
.theme-default .error {
font-size: 1.4rem;
padding: 1rem;
color: #f1c40f;
margin: 1rem 0 0 0;
background-color: #2c3e50;
overflow-wrap: break-word;
}
.theme-default .error-inline {
font-size: 1.4rem;
padding: 1rem;
color: #f1c40f;
margin: 1rem 0 0 0;
background-color: #2c3e50;
white-space: nowrap;
overflow: auto;
}
.theme-default .error {
font-size: 1.4rem;
padding: 1rem;
color: #f1c40f;
margin: 1rem 0 0 0;
background-color: #2c3e50;
overflow: hidden;
}
.theme-default #sharing-list .info {

View file

@ -6,6 +6,7 @@ import { ISettingsClient } from "../client";
import { SettingsClient } from "../client/settings";
import { ICoreState } from "../components/core_state";
import { updater } from "../components/state_updater";
import { alertMsg } from "./env";
const errorVer = "0.0.1";
const cookieKeyClErrs = "qs_cli_errs";
@ -13,6 +14,7 @@ const cookieKeyClErrs = "qs_cli_errs";
export interface ClientErrorV001 {
version: string;
error: string;
timestamp: string;
state: ICoreState;
}
@ -22,9 +24,10 @@ export interface IErrorLogger {
error: (msg: string) => null | Error;
report: () => Promise<null | Error>;
readErrs: () => Map<string, ClientErrorV001>;
truncate: () => void;
}
export class ErrorLog {
export class SimpleErrorLogger {
private client: ISettingsClient;
private storage: ILocalStorage = Storage();
@ -45,9 +48,18 @@ export class ErrorLog {
};
readErrs = (): Map<string, ClientErrorV001> => {
const errsStr = this.storage.get(cookieKeyClErrs);
const errsObj = JSON.parse(errsStr);
return Map(errsObj);
try {
const errsStr = this.storage.get(cookieKeyClErrs);
if (errsStr === "") {
return Map();
}
const errsObj = JSON.parse(errsStr);
return Map(errsObj);
} catch (e: any) {
this.truncate(); // reset
}
return Map();
};
private writeErrs = (errs: Map<string, ClientErrorV001>) => {
@ -62,6 +74,7 @@ export class ErrorLog {
const clientErr: ClientErrorV001 = {
version: errorVer,
error: msg,
timestamp: `${Date.now()}`,
state: updater().props,
};
let errs = this.readErrs();
@ -88,17 +101,20 @@ export class ErrorLog {
}
}
// truncate errors
this.writeErrs(Map());
this.truncate();
} catch (e: any) {
return Error(e);
}
return null;
};
truncate = () => {
this.writeErrs(Map());
};
}
const errorLogger = new ErrorLog(new SettingsClient(""));
const errorLogger = new SimpleErrorLogger(new SettingsClient(""));
export const ErrorLogger = (): IErrorLogger => {
return errorLogger;
};

View file

@ -98,12 +98,8 @@ export class Rows extends React.Component<Props, State, {}> {
}
);
return (
<div
id={this.props.id}
style={this.props.style}
className={this.props.className}
>
const orderByList =
sortBtns.size > 0 ? (
<div className="margin-b-l">
<Flexbox
children={List([
@ -116,6 +112,15 @@ export class Rows extends React.Component<Props, State, {}> {
childrenStyles={List([{ flex: "0 0 auto" }, { flex: "0 0 auto" }])}
/>
</div>
) : null;
return (
<div
id={this.props.id}
style={this.props.style}
className={this.props.className}
>
{orderByList}
{bodyRows}
</div>
);

View file

@ -6,9 +6,11 @@ import { ICoreState, UIProps, MsgProps } from "./core_state";
import { LoginProps } from "./pane_login";
import { Flexbox } from "./layout/flexbox";
import { updater } from "./state_updater";
import { alertMsg } from "../common/env";
import { alertMsg, confirmMsg } from "../common/env";
import { Container } from "./layout/container";
import { Card } from "./layout/card";
import { Rows, Row } from "./layout/rows";
import { ClientErrorV001, ErrorLogger } from "../common/log_error";
export interface Props {
login: LoginProps;
msg: MsgProps;
@ -138,12 +140,53 @@ export class PaneSettings extends React.Component<Props, State, {}> {
});
};
truncateErrors = () => {
if (confirmMsg(this.props.msg.pkg.get("op.confirm"))) {
ErrorLogger().truncate();
}
};
reportErrors = () => {
if (confirmMsg(this.props.msg.pkg.get("op.confirm"))) {
ErrorLogger().report();
}
};
prepareErrorRows = (): List<Row> => {
let errRows = List<Row>();
ErrorLogger()
.readErrs()
.forEach((clientErr: ClientErrorV001, sign: string) => {
const elem = (
<div>
<div className="error-inline">{JSON.stringify(clientErr)}</div>
<div className="hr"></div>
</div>
);
const val = clientErr;
const sortVals = List<string>([]);
errRows = errRows.push({
elem,
val,
sortVals,
});
});
return errRows;
};
render() {
const errRows = this.prepareErrorRows();
return (
<div id="pane-settings">
<Container>
<div id="profile">
<h5 className="pane-title">{this.props.msg.pkg.get("user.profile")}</h5>
<h5 className="pane-title">
{this.props.msg.pkg.get("user.profile")}
</h5>
<div className="hr"></div>
@ -372,6 +415,30 @@ export class PaneSettings extends React.Component<Props, State, {}> {
</div>
</Container>
<Container>
<Flexbox
children={List([
<h5 className="pane-title">
{this.props.msg.pkg.get("error.report.title")}
</h5>,
<span>
<button className="margin-r-m" onClick={this.reportErrors}>
{this.props.msg.pkg.get("op.submit")}
</button>
<button onClick={this.truncateErrors}>
{this.props.msg.pkg.get("op.truncate")}
</button>
</span>,
])}
childrenStyles={List([{}, { justifyContent: "flex-end" }])}
/>
<div className="hr"></div>
<Rows rows={errRows} sortKeys={List([])} />
</Container>
{/* <div className="hr"></div>
<div>
<Flexbox

View file

@ -8,11 +8,11 @@ import { RiArchiveDrawerFill } from "@react-icons/all-files/ri/RiArchiveDrawerFi
import { RiFile2Fill } from "@react-icons/all-files/ri/RiFile2Fill";
import { RiFileList2Fill } from "@react-icons/all-files/ri/RiFileList2Fill";
import { RiCheckboxFill } from "@react-icons/all-files/ri/RiCheckboxFill";
import { RiCheckboxBlankFill } from "@react-icons/all-files/ri/RiCheckboxBlankFill";
import { RiInformationFill } from "@react-icons/all-files/ri/RiInformationFill";
import { BiTable } from "@react-icons/all-files/bi/BiTable";
import { BiListUl } from "@react-icons/all-files/bi/BiListUl";
import { ErrorLogger } from "../common/log_error";
import { alertMsg, confirmMsg } from "../common/env";
import { getErrMsg } from "../common/utils";
import { updater } from "./state_updater";
@ -699,12 +699,12 @@ export class FilesPanel extends React.Component<Props, State, {}> {
};
setView = (opt: string) => {
if (opt === "rows" || opt === "table") {
updater().setControlOption(filesViewCtrl, opt);
this.props.update(updater().updateUI);
if (opt !== "rows" && opt !== "table") {
ErrorLogger().error(`FilesPanel:setView: unknown view ${opt}`);
return;
}
// TODO: log error
updater().setControlOption(filesViewCtrl, opt);
this.props.update(updater().updateUI);
};
render() {

View file

@ -35,6 +35,7 @@ import { controlName as panelTabs } from "./root_frame";
import { settingsTabsCtrl } from "./dialog_settings";
import { settingsDialogCtrl } from "./layers";
import { errUpdater, errServer } from "../common/errors";
import { ErrorLogger } from "../common/log_error";
import { MsgPackage, isValidLanPack } from "../i18n/msger";
@ -135,8 +136,11 @@ export class Updater {
refreshUploadings = async (): Promise<string> => {
const luResp = await this.filesClient.listUploadings();
if (luResp.status !== 200) {
// TODO: log error
console.error(luResp.data);
// this method is called for authed users
// other status codes are unexpected, including 401
ErrorLogger().error(
`refreshUploadings: unexpected response ${luResp.status} ${luResp.data}`
);
return errServer;
}
@ -767,16 +771,15 @@ export class Updater {
const url = this.props.login.preferences.lanPackURL;
if (url === "") {
const lan = this.props.login.preferences.lan;
if (lan == "en_US" || lan == "zh_CN") {
if (lan === "en_US" || lan === "zh_CN") {
// fallback to build-in language pack
this.props.msg.lan = lan;
this.props.msg.pkg = MsgPackage.get(lan);
} else {
// fallback to english
ErrorLogger().error(`syncLan: unexpected lan ${lan}`);
this.props.msg.lan = "en_US";
this.props.msg.pkg = MsgPackage.get("en_US");
}
// TODO: should warning here
return "";
}

View file

@ -126,4 +126,8 @@ export const msgs: Map<string, string> = Map({
"item.modTime": "Mod Time",
"item.size": "Size",
"item.progress": "Progress",
"error.report.title": "Error Report",
"op.truncate": "Truncate",
"op.submit": "Submit",
"term.time": "Time",
});

View file

@ -123,4 +123,8 @@ export const msgs: Map<string, string> = Map({
"item.modTime": "修改时间",
"item.size": "大小",
"item.progress": "进度",
"error.report.title": "报告错误",
"op.truncate": "清空",
"op.submit": "提交",
"term.time": "时间",
});

View file

@ -12,6 +12,7 @@ import {
UploadState,
} from "./interface";
import { errUploadMgr } from "../common/errors";
import { ErrorLogger } from "../common/log_error";
const win: Window = self as any;
@ -197,8 +198,7 @@ export class UploadMgr {
err: errResp.err,
});
} else {
// TODO: refine this
console.error(`uploading ${errResp.filePath} may already be deleted`);
ErrorLogger().error(`respHandler: entry not found ${errResp.err}`);
}
this.statusCb(this.infos.toMap(), false);
@ -225,17 +225,16 @@ export class UploadMgr {
this.statusCb(this.infos.toMap(), false);
}
} else {
// TODO: refine this
console.error(
`respHandler: may already be deleted: file(${
infoResp.filePath
}) infos(${this.infos.toObject()})`
ErrorLogger().error(
`respHandler: entry(uploadInfoKind) not found ${infoResp.err}`
);
}
break;
default:
console.error(`respHandler: response kind not found: ${resp}`);
ErrorLogger().error(
`respHandler: unknown kind: ${JSON.stringify(resp)}`
);
}
};
}

View file

@ -2,7 +2,6 @@ import { FilesClient } from "../client/files";
import { IFilesClient, Response, isFatalErr } from "../client";
// TODO: get settings from server
// TODO: move chunk copying to worker
const defaultChunkLen = 1024 * 1024 * 1;
const speedDownRatio = 0.5;
const speedUpRatio = 1.05;