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; padding: 1rem 0 1rem 0;
} }
.theme-default .upload-item .error { .theme-default .error {
font-size: 1.4rem; font-size: 1.4rem;
padding: 1rem; padding: 1rem;
color: #f1c40f; color: #f1c40f;
margin: 1rem 0 0 0; margin: 1rem 0 0 0;
background-color: #2c3e50; 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 { .theme-default #sharing-list .info {

View file

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

View file

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

View file

@ -6,9 +6,11 @@ import { ICoreState, UIProps, MsgProps } from "./core_state";
import { LoginProps } from "./pane_login"; import { LoginProps } from "./pane_login";
import { Flexbox } from "./layout/flexbox"; import { Flexbox } from "./layout/flexbox";
import { updater } from "./state_updater"; import { updater } from "./state_updater";
import { alertMsg } from "../common/env"; import { alertMsg, confirmMsg } from "../common/env";
import { Container } from "./layout/container"; import { Container } from "./layout/container";
import { Card } from "./layout/card"; import { Card } from "./layout/card";
import { Rows, Row } from "./layout/rows";
import { ClientErrorV001, ErrorLogger } from "../common/log_error";
export interface Props { export interface Props {
login: LoginProps; login: LoginProps;
msg: MsgProps; 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() { render() {
const errRows = this.prepareErrorRows();
return ( return (
<div id="pane-settings"> <div id="pane-settings">
<Container> <Container>
<div id="profile"> <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> <div className="hr"></div>
@ -372,6 +415,30 @@ export class PaneSettings extends React.Component<Props, State, {}> {
</div> </div>
</Container> </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 className="hr"></div>
<div> <div>
<Flexbox <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 { RiFile2Fill } from "@react-icons/all-files/ri/RiFile2Fill";
import { RiFileList2Fill } from "@react-icons/all-files/ri/RiFileList2Fill"; import { RiFileList2Fill } from "@react-icons/all-files/ri/RiFileList2Fill";
import { RiCheckboxFill } from "@react-icons/all-files/ri/RiCheckboxFill"; 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 { RiInformationFill } from "@react-icons/all-files/ri/RiInformationFill";
import { BiTable } from "@react-icons/all-files/bi/BiTable"; import { BiTable } from "@react-icons/all-files/bi/BiTable";
import { BiListUl } from "@react-icons/all-files/bi/BiListUl"; import { BiListUl } from "@react-icons/all-files/bi/BiListUl";
import { ErrorLogger } from "../common/log_error";
import { alertMsg, confirmMsg } from "../common/env"; import { alertMsg, confirmMsg } from "../common/env";
import { getErrMsg } from "../common/utils"; import { getErrMsg } from "../common/utils";
import { updater } from "./state_updater"; import { updater } from "./state_updater";
@ -699,12 +699,12 @@ export class FilesPanel extends React.Component<Props, State, {}> {
}; };
setView = (opt: string) => { setView = (opt: string) => {
if (opt === "rows" || opt === "table") { if (opt !== "rows" && opt !== "table") {
updater().setControlOption(filesViewCtrl, opt); ErrorLogger().error(`FilesPanel:setView: unknown view ${opt}`);
this.props.update(updater().updateUI);
return; return;
} }
// TODO: log error updater().setControlOption(filesViewCtrl, opt);
this.props.update(updater().updateUI);
}; };
render() { render() {

View file

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

View file

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

View file

@ -12,6 +12,7 @@ import {
UploadState, UploadState,
} from "./interface"; } from "./interface";
import { errUploadMgr } from "../common/errors"; import { errUploadMgr } from "../common/errors";
import { ErrorLogger } from "../common/log_error";
const win: Window = self as any; const win: Window = self as any;
@ -197,8 +198,7 @@ export class UploadMgr {
err: errResp.err, err: errResp.err,
}); });
} else { } else {
// TODO: refine this ErrorLogger().error(`respHandler: entry not found ${errResp.err}`);
console.error(`uploading ${errResp.filePath} may already be deleted`);
} }
this.statusCb(this.infos.toMap(), false); this.statusCb(this.infos.toMap(), false);
@ -225,17 +225,16 @@ export class UploadMgr {
this.statusCb(this.infos.toMap(), false); this.statusCb(this.infos.toMap(), false);
} }
} else { } else {
// TODO: refine this ErrorLogger().error(
console.error( `respHandler: entry(uploadInfoKind) not found ${infoResp.err}`
`respHandler: may already be deleted: file(${
infoResp.filePath
}) infos(${this.infos.toObject()})`
); );
} }
break; break;
default: 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"; import { IFilesClient, Response, isFatalErr } from "../client";
// TODO: get settings from server // TODO: get settings from server
// TODO: move chunk copying to worker
const defaultChunkLen = 1024 * 1024 * 1; const defaultChunkLen = 1024 * 1024 * 1;
const speedDownRatio = 0.5; const speedDownRatio = 0.5;
const speedUpRatio = 1.05; const speedUpRatio = 1.05;