From 711a3a874ff0733754ddef9aebe42df21a8ce45a Mon Sep 17 00:00:00 2001 From: hexxa Date: Thu, 30 Dec 2021 10:33:13 +0800 Subject: [PATCH] feat(error_reporting): integrate error reporting --- src/client/settings.go | 4 +- src/client/web/src/client/index.ts | 8 ++- src/client/web/src/client/settings.ts | 10 ++-- src/client/web/src/client/settings_mock.ts | 8 +-- src/client/web/src/common/log_error.ts | 25 ++++++---- .../web/src/components/pane_settings.tsx | 50 ++++++++++--------- src/handlers/settings/handlers.go | 12 +++-- src/server/server.go | 2 +- src/server/server_settings_test.go | 24 ++++++--- 9 files changed, 86 insertions(+), 57 deletions(-) diff --git a/src/client/settings.go b/src/client/settings.go index be22442..d54701f 100644 --- a/src/client/settings.go +++ b/src/client/settings.go @@ -55,9 +55,9 @@ func (cl *SettingsClient) SetClientCfg(cfg *sitestore.ClientConfig, token *http. End() } -func (cl *SettingsClient) ReportError(report *settings.ClientErrorReport, token *http.Cookie) (*http.Response, string, []error) { +func (cl *SettingsClient) ReportErrors(reports *settings.ClientErrorReports, token *http.Cookie) (*http.Response, string, []error) { return cl.r.Post(cl.url("/v1/settings/errors")). AddCookie(token). - Send(report). + Send(reports). End() } diff --git a/src/client/web/src/client/index.ts b/src/client/web/src/client/index.ts index aba783a..4724827 100644 --- a/src/client/web/src/client/index.ts +++ b/src/client/web/src/client/index.ts @@ -1,4 +1,5 @@ import axios, { AxiosRequestConfig } from "axios"; +import { List } from "immutable"; export const defaultTimeout = 10000; export const userIDParam = "uid"; @@ -88,6 +89,11 @@ export interface ClientConfig { bg: BgConfig; } +export interface ClientErrorReport { + report: string; + version: string; +} + export interface IUsersClient { login: ( user: string, @@ -139,7 +145,7 @@ export interface ISettingsClient { health: () => Promise; getClientCfg: () => Promise; setClientCfg: (cfg: ClientConfig) => Promise; - reportError: (content: string, version: string) => Promise; + reportErrors: (reports: List) => Promise; } export interface Response { diff --git a/src/client/web/src/client/settings.ts b/src/client/web/src/client/settings.ts index 079c193..847bca1 100644 --- a/src/client/web/src/client/settings.ts +++ b/src/client/web/src/client/settings.ts @@ -1,6 +1,7 @@ -import { BaseClient, Response, userIDParam, Quota } from "."; +import { List } from "immutable"; -import { ClientConfig } from "./"; +import { BaseClient, Response, userIDParam, Quota } from "."; +import { ClientConfig, ClientErrorReport } from "./"; export class SettingsClient extends BaseClient { constructor(url: string) { @@ -31,13 +32,12 @@ export class SettingsClient extends BaseClient { }); }; - reportError = (content: string, version: string): Promise => { + reportErrors = (reports: List): Promise => { return this.do({ method: "post", url: `${this.url}/v1/settings/errors`, data: { - content, - version, + reports: reports.toArray(), }, }); }; diff --git a/src/client/web/src/client/settings_mock.ts b/src/client/web/src/client/settings_mock.ts index 91bb9a0..46431ae 100644 --- a/src/client/web/src/client/settings_mock.ts +++ b/src/client/web/src/client/settings_mock.ts @@ -7,7 +7,7 @@ export interface SettingsClientResps { healthMockResp?: Response; setClientCfgMockResp?: Response; getClientCfgMockResp?: Response; - reportErrorResp?: Response; + reportErrorsResp?: Response; } export const resps = { @@ -29,7 +29,7 @@ export const resps = { }, }, }, - reportErrorResp: { + reportErrorsResp: { status: 200, statusText: "", data: {}, @@ -66,7 +66,7 @@ export class MockSettingsClient { return this.wrapPromise(this.resps.getClientCfgMockResp); }; - reportError = (): Promise => { - return this.wrapPromise(this.resps.reportErrorResp); + reportErrors = (): Promise => { + return this.wrapPromise(this.resps.reportErrorsResp); }; } diff --git a/src/client/web/src/common/log_error.ts b/src/client/web/src/common/log_error.ts index 55ec1e4..359f54a 100644 --- a/src/client/web/src/common/log_error.ts +++ b/src/client/web/src/common/log_error.ts @@ -1,4 +1,4 @@ -import { Map } from "immutable"; +import { Map, List } from "immutable"; import { sha1 } from "object-hash"; import { ILocalStorage, Storage } from "./localstorage"; @@ -6,13 +6,12 @@ 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"; +import { ClientErrorReport } from "../client"; const errorVer = "0.0.1"; const cookieKeyClErrs = "qs_cli_errs"; export interface ClientErrorV001 { - version: string; error: string; timestamp: string; state: ICoreState; @@ -72,7 +71,6 @@ export class SimpleErrorLogger { try { const sign = this.getErrorSign(msg); const clientErr: ClientErrorV001 = { - version: errorVer, error: msg, timestamp: `${Date.now()}`, state: updater().props, @@ -92,16 +90,21 @@ export class SimpleErrorLogger { report = async (): Promise => { try { const errs = this.readErrs(); - + let reports = List(); 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}`); - } + const errObj = errs.get(sign); + reports = reports.push({ + report: JSON.stringify(errObj), + version: errorVer, + }); } - this.truncate(); + const resp = await this.client.reportErrors(reports); + if (resp.status !== 200) { + return Error(`failed to report error: ${resp.data}`); + } else { + this.truncate(); + } } catch (e: any) { return Error(e); } diff --git a/src/client/web/src/components/pane_settings.tsx b/src/client/web/src/components/pane_settings.tsx index e81c1c6..b9f6f33 100644 --- a/src/client/web/src/components/pane_settings.tsx +++ b/src/client/web/src/components/pane_settings.tsx @@ -179,6 +179,32 @@ export class PaneSettings extends React.Component { render() { const errRows = this.prepareErrorRows(); + const errorReportPane = + errRows.size > 0 ? ( + + + {this.props.msg.pkg.get("error.report.title")} + , + + + + + , + ])} + childrenStyles={List([{}, { justifyContent: "flex-end" }])} + /> + +
+ + +
+ ) : null; return (
@@ -415,29 +441,7 @@ export class PaneSettings extends React.Component {
- - - {this.props.msg.pkg.get("error.report.title")} - , - - - - - , - ])} - childrenStyles={List([{}, { justifyContent: "flex-end" }])} - /> - -
- - -
+ {errorReportPane} {/*
diff --git a/src/handlers/settings/handlers.go b/src/handlers/settings/handlers.go index c7bb434..657240f 100644 --- a/src/handlers/settings/handlers.go +++ b/src/handlers/settings/handlers.go @@ -83,14 +83,20 @@ type ClientErrorReport struct { Version string `json:"version"` } -func (h *SettingsSvc) ReportError(c *gin.Context) { +type ClientErrorReports struct { + Reports []*ClientErrorReport `json:"reports"` +} + +func (h *SettingsSvc) ReportErrors(c *gin.Context) { var err error - req := &ClientErrorReport{} + req := &ClientErrorReports{} if err = c.ShouldBindJSON(&req); err != nil { c.JSON(q.ErrResp(c, 400, err)) return } - h.deps.Log().Errorf("version:%s,error:%s", req.Version, req.Report) + for _, report := range req.Reports { + h.deps.Log().Errorf("version:%s,error:%s", report.Version, report.Report) + } c.JSON(q.Resp(200)) } diff --git a/src/server/server.go b/src/server/server.go index bbd7841..145e4f7 100644 --- a/src/server/server.go +++ b/src/server/server.go @@ -304,7 +304,7 @@ func initHandlers(router *gin.Engine, cfg gocfg.ICfg, deps *depidx.Deps) (*gin.E settingsAPI.OPTIONS("/health", settingsSvc.Health) settingsAPI.GET("/client", settingsSvc.GetClientCfg) settingsAPI.PATCH("/client", settingsSvc.SetClientCfg) - settingsAPI.POST("/errors", settingsSvc.ReportError) + settingsAPI.POST("/errors", settingsSvc.ReportErrors) return router, nil } diff --git a/src/server/server_settings_test.go b/src/server/server_settings_test.go index b758053..5dff066 100644 --- a/src/server/server_settings_test.go +++ b/src/server/server_settings_test.go @@ -126,15 +126,22 @@ func TestSettingsHandlers(t *testing.T) { } }) - t.Run("ReportError", func(t *testing.T) { + t.Run("ReportErrors", func(t *testing.T) { settingsCl := client.NewSettingsClient(addr) - reportContent := `{state: "{}", error: "empty state"}` - report := &settings.ClientErrorReport{ - Report: reportContent, - Version: "0.0.1", + reports := &settings.ClientErrorReports{ + Reports: []*settings.ClientErrorReport{ + &settings.ClientErrorReport{ + Report: `{state: "{}", error: "empty state1"}`, + Version: "0.0.1", + }, + &settings.ClientErrorReport{ + Report: `{state: "{}", error: "empty state2"}`, + Version: "0.0.1", + }, + }, } - reportResp, _, errs := settingsCl.ReportError(report, adminToken) + reportResp, _, errs := settingsCl.ReportErrors(reports, adminToken) if len(errs) > 0 { t.Fatal(errs) } else if reportResp.StatusCode != 200 { @@ -154,7 +161,10 @@ func TestSettingsHandlers(t *testing.T) { if err != nil { t.Fatal(err) } - if !strings.Contains(string(content), `"msg":"version:0.0.1,error:{state: \"{}\", error: \"empty state\"}"`) { + if !strings.Contains(string(content), `"msg":"version:0.0.1,error:{state: \"{}\", error: \"empty state1\"}"`) { + t.Fatalf("log does not contain error: %s", content) + } + if !strings.Contains(string(content), `"msg":"version:0.0.1,error:{state: \"{}\", error: \"empty state2\"}"`) { t.Fatalf("log does not contain error: %s", content) } })