chore: fix issues in eval error logs sent to faro (#40285)
## Description Errors sent from evaluation worker shows up as following in faro. This entry is illegible and doesn't have the correct stack trace. ``` console.error: [object Object] [object Object] at ? (https://app.appsmith.com/src/instrumentations/console/instrumentation.ts:28:33) at log (https://app.appsmith.com/static/js/sagas/ErrorSagas.tsx:322:22) ``` This pr sends a reconstructed error object for eval errors and retains the original stack trace of the error ``` error: Fetch computation cache failed!! at AppComputationCache.getCachedComputationResult (https://dev.appsmith.com/static/js/src_workers_Evaluation_handlers_evalTree_ts-node_modules_blueprintjs_core_lib_esm_components_-d37317.chunk.js:48831:11) at createDependencyMap (https://dev.appsmith.com/static/js/src_workers_Evaluation_handlers_evalTree_ts-node_modules_blueprintjs_core_lib_esm_components_-d37317.chunk.js:50756:98) at ? (https://dev.appsmith.com/static/js/src_workers_Evaluation_handlers_evalTree_ts-node_modules_blueprintjs_core_lib_esm_components_-d37317.chunk.js:49278:221) at profileAsyncFn (https://dev.appsmith.com/static/js/src_workers_Evaluation_handlers_evalTree_ts-node_modules_blueprintjs_core_lib_esm_components_-d37317.chunk.js:37451:21) at DataTreeEvaluator.setupFirstTree (https://dev.appsmith.com/static/js/src_workers_Evaluation_handlers_evalTree_ts-node_modules_blueprintjs_core_lib_esm_components_-d37317.chunk.js:49278:103) at async profileAsyncFn (https://dev.appsmith.com/static/js/src_workers_Evaluation_handlers_evalTree_ts-node_modules_blueprintjs_core_lib_esm_components_-d37317.chunk.js:37451:15) at async evalTree (https://dev.appsmith.com/static/js/src_workers_Evaluation_handlers_evalTree_ts-node_modules_blueprintjs_core_lib_esm_components_-d37317.chunk.js:46384:38) at async asyncRequestMessageListener (https://dev.appsmith.com/static/js/evalWorker.chunk.js:236:16) ``` Fixes #`Issue Number` _or_ Fixes `Issue URL` > [!WARNING] > _If no issue exists, please create an issue first, and check with the maintainers if the issue is valid._ ## Automation /ok-to-test tags="@tag.All" ### 🔍 Cypress test results <!-- This is an auto-generated comment: Cypress test results --> > [!TIP] > 🟢 🟢 🟢 All cypress tests have passed! 🎉 🎉 🎉 > Workflow run: <https://github.com/appsmithorg/appsmith/actions/runs/14512303434> > Commit: 3bbb8c1df4a731e63c2a0b3805d28ad96696ffd0 > <a href="https://internal.appsmith.com/app/cypress-dashboard/rundetails-65890b3c81d7400d08fa9ee5?branch=master&workflowId=14512303434&attempt=1" target="_blank">Cypress dashboard</a>. > Tags: `@tag.All` > Spec: > <hr>Thu, 17 Apr 2025 10:31:54 UTC <!-- end of auto-generated comment: Cypress test results --> ## Communication Should the DevRel and Marketing teams inform users about this change? - [ ] Yes - [ ] No <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit - **New Features** - Enhanced error reporting across the application by including stack traces with error messages, providing more detailed information for debugging. - **Bug Fixes** - Improved error handling in various areas to ensure errors are consistently captured and reported with additional context. - **Chores** - Updated internal logging mechanisms for better error tracking and maintainability. - Refined error reconstruction and logging processes for more accurate error capture and reporting. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
This commit is contained in:
parent
882ad85e23
commit
14c7a41ba5
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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" });
|
||||
}
|
||||
}
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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<string, ConditionalOutput> = {};
|
||||
|
|
|
|||
5
app/client/src/sagas/types.ts
Normal file
5
app/client/src/sagas/types.ts
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
import type { EvalTreeResponseData } from "workers/Evaluation/types";
|
||||
|
||||
export interface UpdateDataTreeMessageData {
|
||||
workerResponse: EvalTreeResponseData;
|
||||
}
|
||||
|
|
@ -427,6 +427,7 @@ export interface EvaluationError extends DataTreeError {
|
|||
| PropertyEvaluationErrorType.VALIDATION;
|
||||
originalBinding?: string;
|
||||
kind?: Partial<PropertyEvaluationErrorKind>;
|
||||
stack?: string;
|
||||
}
|
||||
|
||||
export interface LintError extends DataTreeError {
|
||||
|
|
|
|||
|
|
@ -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 = {
|
||||
|
|
|
|||
|
|
@ -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";
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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<DataTree, DataTree>[];
|
||||
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,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -109,6 +109,7 @@ export const extractInfoFromBindings = (
|
|||
context: {
|
||||
script: binding,
|
||||
},
|
||||
stack: (error as Error).stack,
|
||||
};
|
||||
|
||||
return {
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user