Skip to content

Commit 14c7a41

Browse files
authored
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: 3bbb8c1 > <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 -->
1 parent 882ad85 commit 14c7a41

File tree

14 files changed

+82
-21
lines changed

14 files changed

+82
-21
lines changed

app/client/src/instrumentation/index.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import {
1515
getWebInstrumentations,
1616
type Faro,
1717
InternalLoggerLevel,
18+
LogLevel,
1819
} from "@grafana/faro-react";
1920
import {
2021
FaroTraceExporter,
@@ -57,7 +58,12 @@ if (isTracingEnabled()) {
5758
],
5859
ignoreUrls,
5960
consoleInstrumentation: {
60-
consoleErrorAsLog: false,
61+
disabledLevels: [
62+
LogLevel.DEBUG,
63+
LogLevel.TRACE,
64+
LogLevel.INFO,
65+
LogLevel.LOG,
66+
],
6167
},
6268
trackResources: true,
6369
trackWebVitalsAttribution: true,

app/client/src/instrumentation/sendFaroErrors.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { v4 as uuidv4 } from "uuid";
2+
import { error as errorLogger } from "loglevel";
23

34
// eslint-disable-next-line @typescript-eslint/no-explicit-any
45
export function captureException(exception: any, hint?: any): string {
@@ -11,8 +12,7 @@ export function captureException(exception: any, hint?: any): string {
1112
{ type: "error", context: context },
1213
);
1314
} catch (error) {
14-
// eslint-disable-next-line
15-
console.error(error);
15+
errorLogger(error);
1616
}
1717

1818
return eventId;

app/client/src/sagas/EvalErrorHandler.ts

Lines changed: 18 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ import type { AppState } from "ee/reducers";
3030
import { toast } from "@appsmith/ads";
3131
import { isDynamicEntity } from "ee/entities/DataTree/isDynamicEntity";
3232
import { getEntityPayloadInfo } from "ee/utils/getEntityPayloadInfo";
33+
import { reconstructErrorFromEvalError } from "./helper";
3334

3435
const getDebuggerErrors = (state: AppState) => state.ui.debugger.errors;
3536

@@ -217,6 +218,8 @@ export function* evalErrorHandler(
217218
}
218219

219220
errors.forEach((error) => {
221+
const reconstructedError = reconstructErrorFromEvalError(error);
222+
220223
switch (error.type) {
221224
case EvalErrorTypes.CYCLICAL_DEPENDENCY_ERROR: {
222225
if (error.context) {
@@ -232,7 +235,7 @@ export function* evalErrorHandler(
232235

233236
if (error.context.logToSentry) {
234237
// Send the generic error message to sentry for better grouping
235-
captureException(new Error(error.message), {
238+
captureException(reconstructedError, {
236239
errorName: "CyclicalDependencyError",
237240
tags: {
238241
node,
@@ -265,22 +268,26 @@ export function* evalErrorHandler(
265268
kind: "error",
266269
});
267270
log.error(error);
268-
captureException(error, { errorName: "EvalTreeError" });
271+
captureException(reconstructedError, { errorName: "EvalTreeError" });
269272
break;
270273
}
271274
case EvalErrorTypes.BAD_UNEVAL_TREE_ERROR: {
272275
log.error(error);
273-
captureException(error, { errorName: "BadUnevalTreeError" });
276+
captureException(reconstructedError, {
277+
errorName: "BadUnevalTreeError",
278+
});
274279
break;
275280
}
276281
case EvalErrorTypes.EVAL_PROPERTY_ERROR: {
277-
captureException(error, { errorName: "EvalPropertyError" });
282+
captureException(reconstructedError, {
283+
errorName: "EvalPropertyError",
284+
});
278285
log.error(error);
279286
break;
280287
}
281288
case EvalErrorTypes.CLONE_ERROR: {
282289
log.debug(error);
283-
captureException(new Error(error.message), {
290+
captureException(reconstructedError, {
284291
errorName: "CloneError",
285292
extra: {
286293
request: error.context,
@@ -296,22 +303,24 @@ export function* evalErrorHandler(
296303
text: `${error.message} at: ${error.context?.propertyPath}`,
297304
});
298305
log.error(error);
299-
captureException(error, {
306+
captureException(reconstructedError, {
300307
errorName: "ParseJSError",
301308
entity: error.context,
302309
});
303310
break;
304311
}
305312
case EvalErrorTypes.EXTRACT_DEPENDENCY_ERROR: {
306-
captureException(new Error(error.message), {
313+
captureException(reconstructedError, {
307314
errorName: "ExtractDependencyError",
308315
extra: error.context,
309316
});
310317
break;
311318
}
312319
case EvalErrorTypes.UPDATE_DATA_TREE_ERROR: {
313320
// Log to Sentry with additional context
314-
captureException(error, { errorName: "UpdateDataTreeError" });
321+
captureException(reconstructedError, {
322+
errorName: "UpdateDataTreeError",
323+
});
315324
// Log locally with error details
316325
log.error(`Evaluation Error: ${error.message}`, {
317326
type: error.type,
@@ -320,7 +329,7 @@ export function* evalErrorHandler(
320329
}
321330
default: {
322331
log.error(error);
323-
captureException(error, { errorName: "UnknownEvalError" });
332+
captureException(reconstructedError, { errorName: "UnknownEvalError" });
324333
}
325334
}
326335
});

app/client/src/sagas/EvalWorkerActionSagas.ts

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -24,10 +24,7 @@ import type { LintTreeSagaRequestData } from "plugins/Linting/types";
2424
import { evalErrorHandler } from "./EvalErrorHandler";
2525
import { getUnevaluatedDataTree } from "selectors/dataTreeSelectors";
2626
import { endSpan, startRootSpan } from "instrumentation/generateTraces";
27-
28-
export interface UpdateDataTreeMessageData {
29-
workerResponse: EvalTreeResponseData;
30-
}
27+
import type { UpdateDataTreeMessageData } from "./types";
3128

3229
// TODO: Fix this the next time the file is edited
3330
// eslint-disable-next-line @typescript-eslint/no-explicit-any

app/client/src/sagas/helper.ts

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ import {
3737
} from "../constants/Datasource";
3838
import { type Datasource, ToastMessageType } from "../entities/Datasource";
3939
import { getNextEntityName } from "utils/AppsmithUtils";
40+
import type { EvalError } from "utils/DynamicBindingUtils";
4041

4142
// function to extract all objects that have dynamic values
4243
export const extractFetchDynamicValueFormConfigs = (
@@ -53,6 +54,29 @@ export const extractFetchDynamicValueFormConfigs = (
5354
return output;
5455
};
5556

57+
/**
58+
* Reconstructs an EvalError from a serialized error object. This attaches the correct stack trace and error type to the error.
59+
* this is used to send the error to faro.
60+
*
61+
* @param serializedError - The serialized error object to reconstruct.
62+
* @returns A reconstructed Error object.
63+
*/
64+
export function reconstructErrorFromEvalError(serializedError: EvalError) {
65+
const error = new Error(serializedError.message);
66+
67+
if (serializedError.stack) {
68+
error.stack = serializedError.stack;
69+
}
70+
71+
if (serializedError.context) {
72+
Object.assign(error, {
73+
context: serializedError.context,
74+
});
75+
}
76+
77+
return error;
78+
}
79+
5680
// Function to extract all the objects that have to fetch dynamic values
5781
export const extractQueueOfValuesToBeFetched = (evalOutput: FormEvalOutput) => {
5882
let output: Record<string, ConditionalOutput> = {};

app/client/src/sagas/types.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
import type { EvalTreeResponseData } from "workers/Evaluation/types";
2+
3+
export interface UpdateDataTreeMessageData {
4+
workerResponse: EvalTreeResponseData;
5+
}

app/client/src/utils/DynamicBindingUtils.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -427,6 +427,7 @@ export interface EvaluationError extends DataTreeError {
427427
| PropertyEvaluationErrorType.VALIDATION;
428428
originalBinding?: string;
429429
kind?: Partial<PropertyEvaluationErrorKind>;
430+
stack?: string;
430431
}
431432

432433
export interface LintError extends DataTreeError {

app/client/src/workers/Evaluation/JSObject/index.ts

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -210,7 +210,15 @@ export function saveResolvedFunctionsAndJSUpdates(
210210
}
211211
}
212212
} catch (e) {
213-
//if we need to push error as popup in case
213+
dataTreeEvalRef.errors.push({
214+
type: EvalErrorTypes.PARSE_JS_ERROR,
215+
message: (e as Error).message,
216+
stack: (e as Error).stack,
217+
context: {
218+
entityType: entity.ENTITY_TYPE,
219+
propertyPath: entityName + ".body",
220+
},
221+
});
214222
}
215223
} else {
216224
const parsedBody = {

app/client/src/workers/Evaluation/evalTreeWithChanges.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import { generateDataTreeWidget } from "entities/DataTree/dataTreeWidget";
77
import { create } from "mutative";
88
import { klona } from "klona/json";
99
import type { WidgetEntity } from "plugins/Linting/lib/entity/WidgetEntity";
10-
import type { UpdateDataTreeMessageData } from "sagas/EvalWorkerActionSagas";
10+
import type { UpdateDataTreeMessageData } from "sagas/types";
1111
import DataTreeEvaluator from "workers/common/DataTreeEvaluator";
1212
import * as evalTreeWithChanges from "./evalTreeWithChanges";
1313
import { APP_MODE } from "entities/App";

app/client/src/workers/Evaluation/evalTreeWithChanges.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import type {
77
} from "./types";
88
import { MessageType, sendMessage } from "utils/MessageUtil";
99
import { MAIN_THREAD_ACTION } from "ee/workers/Evaluation/evalWorkerActions";
10-
import type { UpdateDataTreeMessageData } from "sagas/EvalWorkerActionSagas";
10+
import type { UpdateDataTreeMessageData } from "sagas/types";
1111
import {
1212
generateOptimisedUpdatesAndSetPrevState,
1313
getNewDataTreeUpdates,

app/client/src/workers/Evaluation/handlers/evalTree.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -307,6 +307,12 @@ export async function evalTree(
307307
dataTreeEvaluator?.setPrevState(parsedUpdates[0].rhs);
308308
} catch (e) {
309309
updates = "[]";
310+
311+
errors.push({
312+
type: EvalErrorTypes.EVAL_TREE_ERROR,
313+
message: (e as Error).message,
314+
stack: (e as Error).stack,
315+
});
310316
}
311317
isNewTree = false;
312318
} else {

app/client/src/workers/Evaluation/helpers.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import type { DataTree } from "entities/DataTree/dataTreeTypes";
66
import equal from "fast-deep-equal";
77
import { get, isObject, set } from "lodash";
88
import { isMoment } from "moment";
9-
import { EvalErrorTypes } from "utils/DynamicBindingUtils";
9+
import { EvalErrorTypes, type EvalError } from "utils/DynamicBindingUtils";
1010
import { create } from "mutative";
1111
export const fn_keys: string = "__fn_keys__";
1212
import { klona } from "klona/json";
@@ -465,7 +465,7 @@ export const generateSerialisedUpdates = (
465465
): {
466466
serialisedUpdates: string;
467467
updates: Diff<DataTree, DataTree>[];
468-
error?: { type: string; message: string };
468+
error?: EvalError;
469469
} => {
470470
const updates = generateOptimisedUpdates(
471471
prevState,
@@ -491,6 +491,7 @@ export const generateSerialisedUpdates = (
491491
error: {
492492
type: EvalErrorTypes.SERIALIZATION_ERROR,
493493
message: (error as Error).message,
494+
stack: (error as Error).stack,
494495
},
495496
};
496497
}
@@ -547,6 +548,7 @@ export function updatePrevState(
547548
dataTreeEvaluator.errors.push({
548549
type: EvalErrorTypes.UPDATE_DATA_TREE_ERROR,
549550
message: error.message,
551+
stack: error.stack,
550552
});
551553
}
552554
}

app/client/src/workers/common/DataTreeEvaluator/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1205,6 +1205,7 @@ export default class DataTreeEvaluator {
12051205
context: {
12061206
propertyPath: fullPropertyPath,
12071207
},
1208+
stack: (error as Error).stack,
12081209
});
12091210
evalPropertyValue = undefined;
12101211
}
@@ -1408,6 +1409,7 @@ export default class DataTreeEvaluator {
14081409
this.errors.push({
14091410
type: EvalErrorTypes.EVAL_TREE_ERROR,
14101411
message: (error as Error).message,
1412+
stack: (error as Error).stack,
14111413
});
14121414
} finally {
14131415
// Restore the dataStore since it was a part of contextTree and prone to mutation.

app/client/src/workers/common/DependencyMap/utils.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,7 @@ export const extractInfoFromBindings = (
109109
context: {
110110
script: binding,
111111
},
112+
stack: (error as Error).stack,
112113
};
113114

114115
return {

0 commit comments

Comments
 (0)