From 3cb711b37ee9c2fc4621d216454da7ff81d46f41 Mon Sep 17 00:00:00 2001 From: hexxa Date: Mon, 27 Dec 2021 21:54:16 +0800 Subject: [PATCH] feat(fe/log_error): add error reporter --- src/client/web/package.json | 2 + src/client/web/src/app.tsx | 4 +- src/client/web/src/client/index.ts | 1 + src/client/web/src/client/settings.ts | 11 +++ src/client/web/src/client/settings_mock.ts | 12 +++- src/client/web/src/common/log_error.ts | 82 ++++++++++++++++++---- yarn.lock | 10 +++ 7 files changed, 107 insertions(+), 15 deletions(-) diff --git a/src/client/web/package.json b/src/client/web/package.json index 2a339f0..6596c19 100644 --- a/src/client/web/package.json +++ b/src/client/web/package.json @@ -19,6 +19,7 @@ "@types/assert": "^1.4.2", "@types/deep-diff": "^1.0.0", "@types/jest": "^27.0.1", + "@types/object-hash": "^2.2.1", "assert": "^2.0.0", "babel-loader": "^8.2.2", "deep-diff": "^1.0.2", @@ -52,6 +53,7 @@ "css-loader": "^5.0.0", "filesize": "^6.1.0", "immutable": "^4.0.0-rc.12", + "object-hash": "^2.2.0", "react": "^16.8.6", "react-copy-to-clipboard": "^5.0.1", "react-dom": "^16.8.6", diff --git a/src/client/web/src/app.tsx b/src/client/web/src/app.tsx index 82177c0..d447c3d 100644 --- a/src/client/web/src/app.tsx +++ b/src/client/web/src/app.tsx @@ -14,9 +14,9 @@ window.onerror = ( ) => { const lowerMsg = msg.toLowerCase(); if (lowerMsg.indexOf("script error") > -1) { - ErrorLogger().error(errCorsScript, "Check Browser Console for Detail"); + ErrorLogger().error("Check Browser Console for Detail"); } - ErrorLogger().error(`${source}:${lineno}:${colno}: ${error.toString()}`, ""); + ErrorLogger().error(`${source}:${lineno}:${colno}: ${error.toString()}`); }; ReactDOM.render(, document.getElementById("mount")); diff --git a/src/client/web/src/client/index.ts b/src/client/web/src/client/index.ts index cf72436..aba783a 100644 --- a/src/client/web/src/client/index.ts +++ b/src/client/web/src/client/index.ts @@ -139,6 +139,7 @@ export interface ISettingsClient { health: () => Promise; getClientCfg: () => Promise; setClientCfg: (cfg: ClientConfig) => Promise; + reportError: (content: string, version: string) => Promise; } export interface Response { diff --git a/src/client/web/src/client/settings.ts b/src/client/web/src/client/settings.ts index 6286bf4..079c193 100644 --- a/src/client/web/src/client/settings.ts +++ b/src/client/web/src/client/settings.ts @@ -30,4 +30,15 @@ export class SettingsClient extends BaseClient { }, }); }; + + reportError = (content: string, version: string): Promise => { + return this.do({ + method: "post", + url: `${this.url}/v1/settings/errors`, + data: { + content, + version, + }, + }); + }; } diff --git a/src/client/web/src/client/settings_mock.ts b/src/client/web/src/client/settings_mock.ts index 99625b1..91bb9a0 100644 --- a/src/client/web/src/client/settings_mock.ts +++ b/src/client/web/src/client/settings_mock.ts @@ -7,6 +7,7 @@ export interface SettingsClientResps { healthMockResp?: Response; setClientCfgMockResp?: Response; getClientCfgMockResp?: Response; + reportErrorResp?: Response; } export const resps = { @@ -25,9 +26,14 @@ export const resps = { position: "clientCfg_bg_position", align: "clientCfg_bg_align", }, - } + }, }, }, + reportErrorResp: { + status: 200, + statusText: "", + data: {}, + }, }; export class MockSettingsClient { private url: string; @@ -59,4 +65,8 @@ export class MockSettingsClient { getClientCfg = (): Promise => { return this.wrapPromise(this.resps.getClientCfgMockResp); }; + + reportError = (): Promise => { + return this.wrapPromise(this.resps.reportErrorResp); + }; } diff --git a/src/client/web/src/common/log_error.ts b/src/client/web/src/common/log_error.ts index d4a3bd2..1fac2e8 100644 --- a/src/client/web/src/common/log_error.ts +++ b/src/client/web/src/common/log_error.ts @@ -1,12 +1,27 @@ +import { Map } from "immutable"; +import { sha1 } from "object-hash"; + import { ILocalStorage, Storage } from "./localstorage"; import { ISettingsClient } from "../client"; import { SettingsClient } from "../client/settings"; +import { ICoreState } from "../components/core_state"; +import { updater } from "../components/state_updater"; + +const errorVer = "0.0.1"; +const cookieKeyClErrs = "qs_cli_errs"; + +export interface ClientErrorV001 { + version: string; + error: string; + state: ICoreState; +} export interface IErrorLogger { setClient: (client: ISettingsClient) => void; setStorage: (storage: ILocalStorage) => void; - error: (key: string, msg: string) => void; - report: () => void; + error: (msg: string) => null | Error; + report: () => Promise; + readErrs: () => Map; } export class ErrorLog { @@ -25,18 +40,61 @@ export class ErrorLog { this.storage = storage; } - error = (key: string, msg: string) => { - const existKey = this.storage.get(key); - if (existKey === "") { - this.storage.set(key, msg); - } + private getErrorSign = (errMsg: string): string => { + return `e:${sha1(errMsg)}`; }; - report = () => { - // TODO: - // check last submitting, and set submit time - // report all errors to backend - // clean storage + readErrs = (): Map => { + const errsStr = this.storage.get(cookieKeyClErrs); + const errsObj = JSON.parse(errsStr); + return Map(errsObj); + }; + + private writeErrs = (errs: Map) => { + const errsObj = errs.toObject(); + const errsStr = JSON.stringify(errsObj); + this.storage.set(cookieKeyClErrs, errsStr); + }; + + error = (msg: string): null | Error => { + try { + const sign = this.getErrorSign(msg); + const clientErr: ClientErrorV001 = { + version: errorVer, + error: msg, + state: updater().props, + }; + let errs = this.readErrs(); + if (!errs.has(sign)) { + errs = errs.set(sign, clientErr); + this.writeErrs(errs); + } + } catch (err: any) { + return Error(`failed to save err log: ${err}`); + } + + return null; + }; + + report = async (): Promise => { + try { + const errs = this.readErrs(); + + for (let sign of errs.keySeq().toArray()) { + const err = errs.get(sign); + const resp = await this.client.reportError(sign, JSON.stringify(err)); + if (resp.status !== 200) { + return Error(`failed to report error: ${resp.data}`); + } + } + + // truncate errors + this.writeErrs(Map()); + } catch (e: any) { + return Error(e); + } + + return null; }; } diff --git a/yarn.lock b/yarn.lock index a2a6a5c..3c5a17c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1320,6 +1320,11 @@ resolved "https://registry.yarnpkg.com/@types/node/-/node-16.9.2.tgz#81f5a039d6ed1941f8cc57506c74e7c2b8fc64b9" integrity sha512-ZHty/hKoOLZvSz6BtP1g7tc7nUeJhoCf3flLjh8ZEv1vFKBWHXcnMbJMyN/pftSljNyy0kNW/UqI3DccnBnZ8w== +"@types/object-hash@^2.2.1": + version "2.2.1" + resolved "https://registry.yarnpkg.com/@types/object-hash/-/object-hash-2.2.1.tgz#67c169f8f033e0b62abbf81df2d00f4598d540b9" + integrity sha512-i/rtaJFCsPljrZvP/akBqEwUP2y5cZLOmvO+JaYnz01aPknrQ+hB5MRcO7iqCUsFaYfTG8kGfKUyboA07xeDHQ== + "@types/prettier@^2.1.5": version "2.3.2" resolved "https://registry.yarnpkg.com/@types/prettier/-/prettier-2.3.2.tgz#fc8c2825e4ed2142473b4a81064e6e081463d1b3" @@ -3768,6 +3773,11 @@ object-assign@^4.1.1: resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" integrity sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM= +object-hash@^2.2.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/object-hash/-/object-hash-2.2.0.tgz#5ad518581eefc443bd763472b8ff2e9c2c0d54a5" + integrity sha512-gScRMn0bS5fH+IuwyIFgnh9zBdo4DV+6GhygmWM9HyNJSgS0hScp1f5vjtm7oIIOiT9trXrShAkLFSc2IqKNgw== + object-inspect@^1.11.0, object-inspect@^1.9.0: version "1.11.0" resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.11.0.tgz#9dceb146cedd4148a0d9e51ab88d34cf509922b1"