feat(error_reporting): integrate error reporting
This commit is contained in:
parent
9a7cfcb097
commit
711a3a874f
9 changed files with 86 additions and 57 deletions
|
@ -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()
|
||||
}
|
||||
|
|
|
@ -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<Response>;
|
||||
getClientCfg: () => Promise<Response>;
|
||||
setClientCfg: (cfg: ClientConfig) => Promise<Response>;
|
||||
reportError: (content: string, version: string) => Promise<Response>;
|
||||
reportErrors: (reports: List<ClientErrorReport>) => Promise<Response>;
|
||||
}
|
||||
|
||||
export interface Response<T = any> {
|
||||
|
|
|
@ -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<Response> => {
|
||||
reportErrors = (reports: List<ClientErrorReport>): Promise<Response> => {
|
||||
return this.do({
|
||||
method: "post",
|
||||
url: `${this.url}/v1/settings/errors`,
|
||||
data: {
|
||||
content,
|
||||
version,
|
||||
reports: reports.toArray(),
|
||||
},
|
||||
});
|
||||
};
|
||||
|
|
|
@ -7,7 +7,7 @@ export interface SettingsClientResps {
|
|||
healthMockResp?: Response;
|
||||
setClientCfgMockResp?: Response;
|
||||
getClientCfgMockResp?: Response<ClientConfigMsg>;
|
||||
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<Response> => {
|
||||
return this.wrapPromise(this.resps.reportErrorResp);
|
||||
reportErrors = (): Promise<Response> => {
|
||||
return this.wrapPromise(this.resps.reportErrorsResp);
|
||||
};
|
||||
}
|
||||
|
|
|
@ -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<null | Error> => {
|
||||
try {
|
||||
const errs = this.readErrs();
|
||||
|
||||
let reports = List<ClientErrorReport>();
|
||||
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);
|
||||
}
|
||||
|
|
|
@ -179,6 +179,32 @@ export class PaneSettings extends React.Component<Props, State, {}> {
|
|||
|
||||
render() {
|
||||
const errRows = this.prepareErrorRows();
|
||||
const errorReportPane =
|
||||
errRows.size > 0 ? (
|
||||
<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>
|
||||
) : null;
|
||||
|
||||
return (
|
||||
<div id="pane-settings">
|
||||
|
@ -415,29 +441,7 @@ 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>
|
||||
{errorReportPane}
|
||||
|
||||
{/* <div className="hr"></div>
|
||||
<div>
|
||||
|
|
|
@ -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))
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
})
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue