diff --git a/app/client/src/instrumentation/index.ts b/app/client/src/instrumentation/index.ts index fb0d7629cf..8363bcf048 100644 --- a/app/client/src/instrumentation/index.ts +++ b/app/client/src/instrumentation/index.ts @@ -15,6 +15,7 @@ import { getWebInstrumentations, type Faro, InternalLoggerLevel, + LogLevel, } from "@grafana/faro-react"; import { FaroTraceExporter, @@ -57,7 +58,12 @@ if (isTracingEnabled()) { ], ignoreUrls, consoleInstrumentation: { - consoleErrorAsLog: false, + disabledLevels: [ + LogLevel.DEBUG, + LogLevel.TRACE, + LogLevel.INFO, + LogLevel.LOG, + ], }, trackResources: true, trackWebVitalsAttribution: true, diff --git a/app/client/src/instrumentation/sendFaroErrors.ts b/app/client/src/instrumentation/sendFaroErrors.ts index 13e4677641..66adfe7a07 100644 --- a/app/client/src/instrumentation/sendFaroErrors.ts +++ b/app/client/src/instrumentation/sendFaroErrors.ts @@ -1,4 +1,5 @@ import { v4 as uuidv4 } from "uuid"; +import { error as errorLogger } from "loglevel"; // eslint-disable-next-line @typescript-eslint/no-explicit-any export function captureException(exception: any, hint?: any): string { @@ -11,8 +12,7 @@ export function captureException(exception: any, hint?: any): string { { type: "error", context: context }, ); } catch (error) { - // eslint-disable-next-line - console.error(error); + errorLogger(error); } return eventId; diff --git a/app/client/src/sagas/EvalErrorHandler.ts b/app/client/src/sagas/EvalErrorHandler.ts index 2e3725107d..4a6093d39d 100644 --- a/app/client/src/sagas/EvalErrorHandler.ts +++ b/app/client/src/sagas/EvalErrorHandler.ts @@ -30,6 +30,7 @@ import type { AppState } from "ee/reducers"; import { toast } from "@appsmith/ads"; import { isDynamicEntity } from "ee/entities/DataTree/isDynamicEntity"; import { getEntityPayloadInfo } from "ee/utils/getEntityPayloadInfo"; +import { reconstructErrorFromEvalError } from "./helper"; const getDebuggerErrors = (state: AppState) => state.ui.debugger.errors; @@ -217,6 +218,8 @@ export function* evalErrorHandler( } errors.forEach((error) => { + const reconstructedError = reconstructErrorFromEvalError(error); + switch (error.type) { case EvalErrorTypes.CYCLICAL_DEPENDENCY_ERROR: { if (error.context) { @@ -232,7 +235,7 @@ export function* evalErrorHandler( if (error.context.logToSentry) { // Send the generic error message to sentry for better grouping - captureException(new Error(error.message), { + captureException(reconstructedError, { errorName: "CyclicalDependencyError", tags: { node, @@ -265,22 +268,26 @@ export function* evalErrorHandler( kind: "error", }); log.error(error); - captureException(error, { errorName: "EvalTreeError" }); + captureException(reconstructedError, { errorName: "EvalTreeError" }); break; } case EvalErrorTypes.BAD_UNEVAL_TREE_ERROR: { log.error(error); - captureException(error, { errorName: "BadUnevalTreeError" }); + captureException(reconstructedError, { + errorName: "BadUnevalTreeError", + }); break; } case EvalErrorTypes.EVAL_PROPERTY_ERROR: { - captureException(error, { errorName: "EvalPropertyError" }); + captureException(reconstructedError, { + errorName: "EvalPropertyError", + }); log.error(error); break; } case EvalErrorTypes.CLONE_ERROR: { log.debug(error); - captureException(new Error(error.message), { + captureException(reconstructedError, { errorName: "CloneError", extra: { request: error.context, @@ -296,14 +303,14 @@ export function* evalErrorHandler( text: `${error.message} at: ${error.context?.propertyPath}`, }); log.error(error); - captureException(error, { + captureException(reconstructedError, { errorName: "ParseJSError", entity: error.context, }); break; } case EvalErrorTypes.EXTRACT_DEPENDENCY_ERROR: { - captureException(new Error(error.message), { + captureException(reconstructedError, { errorName: "ExtractDependencyError", extra: error.context, }); @@ -311,7 +318,9 @@ export function* evalErrorHandler( } case EvalErrorTypes.UPDATE_DATA_TREE_ERROR: { // Log to Sentry with additional context - captureException(error, { errorName: "UpdateDataTreeError" }); + captureException(reconstructedError, { + errorName: "UpdateDataTreeError", + }); // Log locally with error details log.error(`Evaluation Error: ${error.message}`, { type: error.type, @@ -320,7 +329,7 @@ export function* evalErrorHandler( } default: { log.error(error); - captureException(error, { errorName: "UnknownEvalError" }); + captureException(reconstructedError, { errorName: "UnknownEvalError" }); } } }); diff --git a/app/client/src/sagas/EvalWorkerActionSagas.ts b/app/client/src/sagas/EvalWorkerActionSagas.ts index 6da3b1364a..88b281f38c 100644 --- a/app/client/src/sagas/EvalWorkerActionSagas.ts +++ b/app/client/src/sagas/EvalWorkerActionSagas.ts @@ -24,10 +24,7 @@ import type { LintTreeSagaRequestData } from "plugins/Linting/types"; import { evalErrorHandler } from "./EvalErrorHandler"; import { getUnevaluatedDataTree } from "selectors/dataTreeSelectors"; import { endSpan, startRootSpan } from "instrumentation/generateTraces"; - -export interface UpdateDataTreeMessageData { - workerResponse: EvalTreeResponseData; -} +import type { UpdateDataTreeMessageData } from "./types"; // TODO: Fix this the next time the file is edited // eslint-disable-next-line @typescript-eslint/no-explicit-any diff --git a/app/client/src/sagas/helper.ts b/app/client/src/sagas/helper.ts index 7839552f9d..b354ecec9b 100644 --- a/app/client/src/sagas/helper.ts +++ b/app/client/src/sagas/helper.ts @@ -37,6 +37,7 @@ import { } from "../constants/Datasource"; import { type Datasource, ToastMessageType } from "../entities/Datasource"; import { getNextEntityName } from "utils/AppsmithUtils"; +import type { EvalError } from "utils/DynamicBindingUtils"; // function to extract all objects that have dynamic values export const extractFetchDynamicValueFormConfigs = ( @@ -53,6 +54,29 @@ export const extractFetchDynamicValueFormConfigs = ( return output; }; +/** + * Reconstructs an EvalError from a serialized error object. This attaches the correct stack trace and error type to the error. + * this is used to send the error to faro. + * + * @param serializedError - The serialized error object to reconstruct. + * @returns A reconstructed Error object. + */ +export function reconstructErrorFromEvalError(serializedError: EvalError) { + const error = new Error(serializedError.message); + + if (serializedError.stack) { + error.stack = serializedError.stack; + } + + if (serializedError.context) { + Object.assign(error, { + context: serializedError.context, + }); + } + + return error; +} + // Function to extract all the objects that have to fetch dynamic values export const extractQueueOfValuesToBeFetched = (evalOutput: FormEvalOutput) => { let output: Record = {}; diff --git a/app/client/src/sagas/types.ts b/app/client/src/sagas/types.ts new file mode 100644 index 0000000000..2912d73a42 --- /dev/null +++ b/app/client/src/sagas/types.ts @@ -0,0 +1,5 @@ +import type { EvalTreeResponseData } from "workers/Evaluation/types"; + +export interface UpdateDataTreeMessageData { + workerResponse: EvalTreeResponseData; +} diff --git a/app/client/src/utils/DynamicBindingUtils.ts b/app/client/src/utils/DynamicBindingUtils.ts index 2256ae178e..19532a7b06 100644 --- a/app/client/src/utils/DynamicBindingUtils.ts +++ b/app/client/src/utils/DynamicBindingUtils.ts @@ -427,6 +427,7 @@ export interface EvaluationError extends DataTreeError { | PropertyEvaluationErrorType.VALIDATION; originalBinding?: string; kind?: Partial; + stack?: string; } export interface LintError extends DataTreeError { diff --git a/app/client/src/workers/Evaluation/JSObject/index.ts b/app/client/src/workers/Evaluation/JSObject/index.ts index 315eacb6f1..f7f31ca0c0 100644 --- a/app/client/src/workers/Evaluation/JSObject/index.ts +++ b/app/client/src/workers/Evaluation/JSObject/index.ts @@ -210,7 +210,15 @@ export function saveResolvedFunctionsAndJSUpdates( } } } catch (e) { - //if we need to push error as popup in case + dataTreeEvalRef.errors.push({ + type: EvalErrorTypes.PARSE_JS_ERROR, + message: (e as Error).message, + stack: (e as Error).stack, + context: { + entityType: entity.ENTITY_TYPE, + propertyPath: entityName + ".body", + }, + }); } } else { const parsedBody = { diff --git a/app/client/src/workers/Evaluation/evalTreeWithChanges.test.ts b/app/client/src/workers/Evaluation/evalTreeWithChanges.test.ts index 8053d86fac..29ccef4f28 100644 --- a/app/client/src/workers/Evaluation/evalTreeWithChanges.test.ts +++ b/app/client/src/workers/Evaluation/evalTreeWithChanges.test.ts @@ -7,7 +7,7 @@ import { generateDataTreeWidget } from "entities/DataTree/dataTreeWidget"; import { create } from "mutative"; import { klona } from "klona/json"; import type { WidgetEntity } from "plugins/Linting/lib/entity/WidgetEntity"; -import type { UpdateDataTreeMessageData } from "sagas/EvalWorkerActionSagas"; +import type { UpdateDataTreeMessageData } from "sagas/types"; import DataTreeEvaluator from "workers/common/DataTreeEvaluator"; import * as evalTreeWithChanges from "./evalTreeWithChanges"; import { APP_MODE } from "entities/App"; diff --git a/app/client/src/workers/Evaluation/evalTreeWithChanges.ts b/app/client/src/workers/Evaluation/evalTreeWithChanges.ts index 4d4c3f1f4f..89b4c819d2 100644 --- a/app/client/src/workers/Evaluation/evalTreeWithChanges.ts +++ b/app/client/src/workers/Evaluation/evalTreeWithChanges.ts @@ -7,7 +7,7 @@ import type { } from "./types"; import { MessageType, sendMessage } from "utils/MessageUtil"; import { MAIN_THREAD_ACTION } from "ee/workers/Evaluation/evalWorkerActions"; -import type { UpdateDataTreeMessageData } from "sagas/EvalWorkerActionSagas"; +import type { UpdateDataTreeMessageData } from "sagas/types"; import { generateOptimisedUpdatesAndSetPrevState, getNewDataTreeUpdates, diff --git a/app/client/src/workers/Evaluation/handlers/evalTree.ts b/app/client/src/workers/Evaluation/handlers/evalTree.ts index 5c0d73e213..cf37832c95 100644 --- a/app/client/src/workers/Evaluation/handlers/evalTree.ts +++ b/app/client/src/workers/Evaluation/handlers/evalTree.ts @@ -307,6 +307,12 @@ export async function evalTree( dataTreeEvaluator?.setPrevState(parsedUpdates[0].rhs); } catch (e) { updates = "[]"; + + errors.push({ + type: EvalErrorTypes.EVAL_TREE_ERROR, + message: (e as Error).message, + stack: (e as Error).stack, + }); } isNewTree = false; } else { diff --git a/app/client/src/workers/Evaluation/helpers.ts b/app/client/src/workers/Evaluation/helpers.ts index 62ffffdd97..bc2518d38e 100644 --- a/app/client/src/workers/Evaluation/helpers.ts +++ b/app/client/src/workers/Evaluation/helpers.ts @@ -6,7 +6,7 @@ import type { DataTree } from "entities/DataTree/dataTreeTypes"; import equal from "fast-deep-equal"; import { get, isObject, set } from "lodash"; import { isMoment } from "moment"; -import { EvalErrorTypes } from "utils/DynamicBindingUtils"; +import { EvalErrorTypes, type EvalError } from "utils/DynamicBindingUtils"; import { create } from "mutative"; export const fn_keys: string = "__fn_keys__"; import { klona } from "klona/json"; @@ -465,7 +465,7 @@ export const generateSerialisedUpdates = ( ): { serialisedUpdates: string; updates: Diff[]; - error?: { type: string; message: string }; + error?: EvalError; } => { const updates = generateOptimisedUpdates( prevState, @@ -491,6 +491,7 @@ export const generateSerialisedUpdates = ( error: { type: EvalErrorTypes.SERIALIZATION_ERROR, message: (error as Error).message, + stack: (error as Error).stack, }, }; } @@ -547,6 +548,7 @@ export function updatePrevState( dataTreeEvaluator.errors.push({ type: EvalErrorTypes.UPDATE_DATA_TREE_ERROR, message: error.message, + stack: error.stack, }); } } diff --git a/app/client/src/workers/common/DataTreeEvaluator/index.ts b/app/client/src/workers/common/DataTreeEvaluator/index.ts index 2df85afa25..99c2da53a7 100644 --- a/app/client/src/workers/common/DataTreeEvaluator/index.ts +++ b/app/client/src/workers/common/DataTreeEvaluator/index.ts @@ -1205,6 +1205,7 @@ export default class DataTreeEvaluator { context: { propertyPath: fullPropertyPath, }, + stack: (error as Error).stack, }); evalPropertyValue = undefined; } @@ -1408,6 +1409,7 @@ export default class DataTreeEvaluator { this.errors.push({ type: EvalErrorTypes.EVAL_TREE_ERROR, message: (error as Error).message, + stack: (error as Error).stack, }); } finally { // Restore the dataStore since it was a part of contextTree and prone to mutation. diff --git a/app/client/src/workers/common/DependencyMap/utils.ts b/app/client/src/workers/common/DependencyMap/utils.ts index 6fe5760263..73a42d560b 100644 --- a/app/client/src/workers/common/DependencyMap/utils.ts +++ b/app/client/src/workers/common/DependencyMap/utils.ts @@ -109,6 +109,7 @@ export const extractInfoFromBindings = ( context: { script: binding, }, + stack: (error as Error).stack, }; return {