Skip to content

chore: fix issues in eval error logs sent to faro #40285

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Apr 17, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion app/client/src/instrumentation/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import {
getWebInstrumentations,
type Faro,
InternalLoggerLevel,
LogLevel,
} from "@grafana/faro-react";
import {
FaroTraceExporter,
Expand Down Expand Up @@ -57,7 +58,12 @@ if (isTracingEnabled()) {
],
ignoreUrls,
consoleInstrumentation: {
consoleErrorAsLog: false,
disabledLevels: [
LogLevel.DEBUG,
LogLevel.TRACE,
LogLevel.INFO,
LogLevel.LOG,
],
},
trackResources: true,
trackWebVitalsAttribution: true,
Expand Down
4 changes: 2 additions & 2 deletions app/client/src/instrumentation/sendFaroErrors.ts
Original file line number Diff line number Diff line change
@@ -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 {
Expand All @@ -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;
Expand Down
27 changes: 18 additions & 9 deletions app/client/src/sagas/EvalErrorHandler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -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) {
Expand All @@ -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,
Expand Down Expand Up @@ -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,
Expand All @@ -296,22 +303,24 @@ 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,
});
break;
}
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,
Expand All @@ -320,7 +329,7 @@ export function* evalErrorHandler(
}
default: {
log.error(error);
captureException(error, { errorName: "UnknownEvalError" });
captureException(reconstructedError, { errorName: "UnknownEvalError" });
}
}
});
Expand Down
5 changes: 1 addition & 4 deletions app/client/src/sagas/EvalWorkerActionSagas.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
24 changes: 24 additions & 0 deletions app/client/src/sagas/helper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 = (
Expand All @@ -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> = {};
Expand Down
5 changes: 5 additions & 0 deletions app/client/src/sagas/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import type { EvalTreeResponseData } from "workers/Evaluation/types";

export interface UpdateDataTreeMessageData {
workerResponse: EvalTreeResponseData;
}
1 change: 1 addition & 0 deletions app/client/src/utils/DynamicBindingUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -427,6 +427,7 @@ export interface EvaluationError extends DataTreeError {
| PropertyEvaluationErrorType.VALIDATION;
originalBinding?: string;
kind?: Partial<PropertyEvaluationErrorKind>;
stack?: string;
}

export interface LintError extends DataTreeError {
Expand Down
10 changes: 9 additions & 1 deletion app/client/src/workers/Evaluation/JSObject/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 = {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down
2 changes: 1 addition & 1 deletion app/client/src/workers/Evaluation/evalTreeWithChanges.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
6 changes: 6 additions & 0 deletions app/client/src/workers/Evaluation/handlers/evalTree.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
6 changes: 4 additions & 2 deletions app/client/src/workers/Evaluation/helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down Expand Up @@ -465,7 +465,7 @@ export const generateSerialisedUpdates = (
): {
serialisedUpdates: string;
updates: Diff<DataTree, DataTree>[];
error?: { type: string; message: string };
error?: EvalError;
} => {
const updates = generateOptimisedUpdates(
prevState,
Expand All @@ -491,6 +491,7 @@ export const generateSerialisedUpdates = (
error: {
type: EvalErrorTypes.SERIALIZATION_ERROR,
message: (error as Error).message,
stack: (error as Error).stack,
},
};
}
Expand Down Expand Up @@ -547,6 +548,7 @@ export function updatePrevState(
dataTreeEvaluator.errors.push({
type: EvalErrorTypes.UPDATE_DATA_TREE_ERROR,
message: error.message,
stack: error.stack,
});
}
}
Expand Down
2 changes: 2 additions & 0 deletions app/client/src/workers/common/DataTreeEvaluator/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1205,6 +1205,7 @@ export default class DataTreeEvaluator {
context: {
propertyPath: fullPropertyPath,
},
stack: (error as Error).stack,
});
evalPropertyValue = undefined;
}
Expand Down Expand Up @@ -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.
Expand Down
1 change: 1 addition & 0 deletions app/client/src/workers/common/DependencyMap/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,7 @@ export const extractInfoFromBindings = (
context: {
script: binding,
},
stack: (error as Error).stack,
};

return {
Expand Down