From a2ba744c8dfb132ae1f178bcd23012ca41f8c908 Mon Sep 17 00:00:00 2001 From: Eric Doughty-Papassideris Date: Mon, 11 Nov 2024 01:21:32 +0100 Subject: [PATCH] =?UTF-8?q?=E2=9C=A8=20back,front:=20report=20client=20err?= =?UTF-8?q?ors=20to=20the=20server=20(#714)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../node/src/services/user/web/controller.ts | 25 +++++++++++++++ .../node/src/services/user/web/routes.ts | 10 ++++++ .../node/src/services/user/web/schemas.ts | 13 ++++++++ .../app/features/users/api/user-api-client.ts | 31 +++++++++++++++++++ 4 files changed, 79 insertions(+) diff --git a/tdrive/backend/node/src/services/user/web/controller.ts b/tdrive/backend/node/src/services/user/web/controller.ts index b350df56f..d13283aa2 100644 --- a/tdrive/backend/node/src/services/user/web/controller.ts +++ b/tdrive/backend/node/src/services/user/web/controller.ts @@ -36,6 +36,7 @@ import { formatCompany, getCompanyStats } from "../utils"; import { formatUser } from "../../../utils/users"; import gr from "../../global-resolver"; import config from "config"; +import { getLogger } from "../../../core/platform/framework"; export class UsersCrudController implements @@ -93,6 +94,30 @@ export class UsersCrudController }; } + /** This allows a logged in user to upload log entries */ + async reportClientLog( + request: FastifyRequest<{ Body: { [key: string]: string } }>, + ): Promise { + const headers = { ...request.headers }; + const boringOrSecretHeaders = + "cookie authorization cache-control connection pragma content-length content-type accept accept-encoding accept-language".split( + /\s+/g, + ); + boringOrSecretHeaders.forEach(header => delete headers[header]); + const message = + request.body.message || + "(missing message property in UsersCrudController.reportClientLog request body)"; + delete request.body.message; + getLogger("FromBrowser").error( + { + headers, + ...request.body, + }, + message, + ); + return {}; + } + async setPreferences( request: FastifyRequest<{ Body: User["preferences"] }>, ): Promise { diff --git a/tdrive/backend/node/src/services/user/web/routes.ts b/tdrive/backend/node/src/services/user/web/routes.ts index beb3d659e..6937e8169 100644 --- a/tdrive/backend/node/src/services/user/web/routes.ts +++ b/tdrive/backend/node/src/services/user/web/routes.ts @@ -9,6 +9,7 @@ import { getUserSchema, getUsersSchema, postDevicesSchema, + sendUserClientReportSchema, setUserPreferencesSchema, } from "./schemas"; @@ -52,6 +53,15 @@ const routes: FastifyPluginCallback = (fastify: FastifyInstance, options, next) handler: usersController.setPreferences.bind(usersController), }); + fastify.route({ + method: "POST", + url: `${usersUrl}/me/reportLog`, + preHandler: accessControl, + preValidation: [fastify.authenticate], + schema: sendUserClientReportSchema, + handler: usersController.reportClientLog.bind(usersController), + }); + fastify.route({ method: "POST", url: `${usersUrl}/me`, diff --git a/tdrive/backend/node/src/services/user/web/schemas.ts b/tdrive/backend/node/src/services/user/web/schemas.ts index 9ee0fe4fc..690e654a9 100644 --- a/tdrive/backend/node/src/services/user/web/schemas.ts +++ b/tdrive/backend/node/src/services/user/web/schemas.ts @@ -190,6 +190,19 @@ export const setUserPreferencesSchema = { }, }; +export const sendUserClientReportSchema = { + request: { + type: "object", + properties: { + message: { type: "string" }, + }, + required: ["message"], + }, + response: { + "2xx": {}, + }, +}; + export const getUsersSchema = { type: "object", properties: { diff --git a/tdrive/frontend/src/app/features/users/api/user-api-client.ts b/tdrive/frontend/src/app/features/users/api/user-api-client.ts index 12a51bfd0..335851c63 100644 --- a/tdrive/frontend/src/app/features/users/api/user-api-client.ts +++ b/tdrive/frontend/src/app/features/users/api/user-api-client.ts @@ -8,6 +8,7 @@ import CurrentUser from '../../../deprecated/user/CurrentUser'; import { setUserList } from '../hooks/use-user-list'; import Logger from 'features/global/framework/logger-service'; import { UserQuota } from "features/users/types/user-quota"; +import version from '../../../environment/version'; export type SearchContextType = { scope: 'company' | 'workspace' | 'all'; @@ -142,6 +143,30 @@ class UserAPIClientService { }); } + /** Send log entry to the server's logs. Try to include a `message` property please */ + async reportLog(body: any | { message: string, error?: Error, err?: Error, e?: Error, }): Promise { + const actualBody = { + version: version.version_detail || version.version, + ...body, + } as any; + for (const [key, val] of Object.entries(actualBody)) { + const error = val as Error; + if ((error as Error).stack) { + actualBody[key] = { + message: error.message, + stack: error.stack, + error, + }; + if (!actualBody.message) actualBody.message = "Error: " + error.message; + } + } + if (!actualBody.message) actualBody.message = "(missing message property in UserAPIClient.reportLog)"; + return Api.post( + '/internal/services/users/v1/users/me/reportLog', + actualBody, + ).then(result => undefined); + } + async getQuota(companyId: string, userId: string): Promise { return Api.get( `/internal/services/users/v1/users/${userId}/quota?companyId=${companyId}`, @@ -255,4 +280,10 @@ class UserAPIClientService { } } const UserAPIClient = new UserAPIClientService(); + +window.onerror = (message, url, line, col, error) => { + UserAPIClient.reportLog({ message, url, line, col, error }); + return false; // don't suppress normal alert +} + export default UserAPIClient;