chore: Move sentry to faro for client side (#40220)

## Description
> [!TIP]  
> _Add a TL;DR when the description is longer than 500 words or
extremely technical (helps the content, marketing, and DevRel team)._
>
> _Please also include relevant motivation and context. List any
dependencies that are required for this change. Add links to Notion,
Figma or any other documents that might be relevant to the PR._


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/14401534892>
> Commit: cad55550d2e517bec0031fe4043ec76cfb4d67e3
> <a
href="https://internal.appsmith.com/app/cypress-dashboard/rundetails-65890b3c81d7400d08fa9ee5?branch=master&workflowId=14401534892&attempt=2"
target="_blank">Cypress dashboard</a>.
> Tags: `@tag.All`
> Spec:
> <hr>Fri, 11 Apr 2025 12:38:06 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

- **Refactor**
- Revamped the error reporting system across the platform with enhanced
contextual logging for faster issue diagnosis.

- **Chore**
- Removed legacy error tracking integrations, streamlining overall error
handling and system reliability.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
This commit is contained in:
Apeksha Bhosale 2025-04-16 11:45:47 +05:30 committed by GitHub
parent de7f73d508
commit 6b6e348203
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
66 changed files with 276 additions and 302 deletions

View File

@ -1,10 +1,10 @@
import React, { Component } from "react";
import styled from "styled-components";
import AppCrashImage from "assets/images/404-image.png";
import * as Sentry from "@sentry/react";
import log from "loglevel";
import AnalyticsUtil from "ee/utils/AnalyticsUtil";
import { Button } from "@appsmith/ads";
import captureException from "instrumentation/sendFaroErrors";
const Wrapper = styled.div`
display: flex;
@ -32,7 +32,7 @@ class AppErrorBoundary extends Component {
componentDidCatch(error: Error, errorInfo: React.ErrorInfo) {
log.error({ error, errorInfo });
Sentry.captureException(error);
captureException(error, { errorName: "AppErrorBoundary" });
AnalyticsUtil.logEvent("APP_CRASH", { error, errorInfo });
this.setState({
hasError: true,

View File

@ -9,7 +9,7 @@ import { Tag } from "@blueprintjs/core";
import styled from "styled-components";
import { UIComponentTypes } from "entities/Plugin";
import log from "loglevel";
import * as Sentry from "@sentry/react";
import captureException from "instrumentation/sendFaroErrors";
import type { FormEvalOutput } from "reducers/evaluationReducers/formEvaluationReducer";
import {
checkIfSectionCanRender,
@ -103,7 +103,7 @@ const FormRender = (props: Props) => {
}
} catch (e) {
log.error(e);
Sentry.captureException(e);
captureException(e, { errorName: "FormRenderError" });
return (
<ErrorComponent

View File

@ -20,7 +20,6 @@ import type { APP_MODE } from "entities/App";
import type { CanvasWidgetsReduxState } from "ee/reducers/entityReducers/canvasWidgetsReducer";
import type { ENTITY_TYPE } from "ee/entities/AppsmithConsole/utils";
import type { Replayable } from "entities/Replay/ReplayEntity/ReplayEditor";
import * as Sentry from "@sentry/react";
import type { DSLWidget } from "../WidgetProvider/constants";
import type {
LayoutOnLoadActionErrors,
@ -30,6 +29,7 @@ import { ReplayOperation } from "entities/Replay/ReplayEntity/ReplayOperations";
import type { PACKAGE_PULL_STATUS } from "ee/constants/ModuleConstants";
import type { ApiResponse } from "api/ApiResponses";
import type { EvaluationReduxAction } from "./EvaluationReduxActionTypes";
import captureException from "instrumentation/sendFaroErrors";
export interface FetchPageListPayload {
applicationId: string;
@ -322,13 +322,10 @@ export interface UpdatePageActionPayload {
export const updatePageAction = (
payload: UpdatePageActionPayload,
): ReduxAction<UpdatePageActionPayload> => {
// Update page *needs* id to be there. We found certain scenarios
// where this was not happening and capturing the error to know gather
// more info: https://github.com/appsmithorg/appsmith/issues/16435
if (!payload.id) {
Sentry.captureException(
new Error("Attempting to update page without page id"),
);
captureException(new Error("Attempting to update page without page id"), {
errorName: "PageActions_UpdatePage",
});
}
return {

View File

@ -1,4 +1,4 @@
import * as Sentry from "@sentry/react";
import captureException from "instrumentation/sendFaroErrors";
import type { AxiosResponse } from "axios";
import { CONTENT_TYPE_HEADER_KEY } from "PluginActionEditor/constants/CommonApiConstants";
@ -7,7 +7,8 @@ export const validateJsonResponseMeta = (response: AxiosResponse) => {
response.headers[CONTENT_TYPE_HEADER_KEY] === "application/json" &&
!response.data.responseMeta
) {
Sentry.captureException(new Error("Api responded without response meta"), {
captureException(new Error("Api responded without response meta"), {
errorName: "ValidateJsonResponseMeta",
contexts: { response: response.data },
});
}

View File

@ -1,12 +1,13 @@
import type { AxiosError } from "axios";
import * as Sentry from "@sentry/react";
import type { ApiResponse } from "api/types";
import captureException from "instrumentation/sendFaroErrors";
export const handleMissingResponseMeta = async (
error: AxiosError<ApiResponse>,
) => {
if (error.response?.data && !error.response.data.responseMeta) {
Sentry.captureException(new Error("Api responded without response meta"), {
captureException(new Error("Api responded without response meta"), {
errorName: "MissingResponseMeta",
contexts: { response: { ...error.response.data } },
});

View File

@ -3,10 +3,10 @@ import {
ERROR_CODES,
SERVER_ERROR_CODES,
} from "ee/constants/ApiConstants";
import * as Sentry from "@sentry/react";
import type { AxiosError } from "axios";
import type { ApiResponse } from "api/types";
import { is404orAuthPath } from "api/helpers";
import captureException from "instrumentation/sendFaroErrors";
export async function handleNotFoundError(error: AxiosError<ApiResponse>) {
if (is404orAuthPath()) return null;
@ -20,7 +20,7 @@ export async function handleNotFoundError(error: AxiosError<ApiResponse>) {
(SERVER_ERROR_CODES.RESOURCE_NOT_FOUND.includes(errorData.error?.code) ||
SERVER_ERROR_CODES.UNABLE_TO_FIND_PAGE.includes(errorData?.error?.code))
) {
Sentry.captureException(error);
captureException(error, { errorName: "NotFoundError" });
return Promise.reject({
...error,

View File

@ -1,10 +1,10 @@
import store from "store";
import type { AxiosError } from "axios";
import * as Sentry from "@sentry/react";
import { is404orAuthPath } from "api/helpers";
import { logoutUser } from "actions/userActions";
import { AUTH_LOGIN_URL } from "constants/routes";
import { API_STATUS_CODES, ERROR_CODES } from "ee/constants/ApiConstants";
import captureException from "instrumentation/sendFaroErrors";
export const handleUnauthorizedError = async (error: AxiosError) => {
if (is404orAuthPath()) return null;
@ -20,7 +20,7 @@ export const handleUnauthorizedError = async (error: AxiosError) => {
}),
);
Sentry.captureException(error);
captureException(error, { errorName: "UnauthorizedError" });
return Promise.reject({
...error,

View File

@ -97,7 +97,6 @@ import { clearEvalCache } from "../../sagas/EvaluationsSaga";
import { getQueryParams } from "utils/URLUtils";
import log from "loglevel";
import { migrateIncorrectDynamicBindingPathLists } from "utils/migrations/IncorrectDynamicBindingPathLists";
import * as Sentry from "@sentry/react";
import { ERROR_CODES } from "ee/constants/ApiConstants";
import AnalyticsUtil from "ee/utils/AnalyticsUtil";
import DEFAULT_TEMPLATE from "templates/default";
@ -152,6 +151,7 @@ import {
selectCombinedPreviewMode,
selectGitApplicationCurrentBranch,
} from "selectors/gitModSelectors";
import captureException from "instrumentation/sendFaroErrors";
export const checkIfMigrationIsNeeded = (
fetchPageResponse?: FetchPageResponse,
@ -570,7 +570,9 @@ export function* savePageSaga(action: ReduxAction<{ isRetry?: boolean }>) {
const { message } = incorrectBindingError;
if (isRetry) {
Sentry.captureException(new Error("Failed to correct binding paths"));
captureException(new Error("Failed to correct binding paths"), {
errorName: "PageSagas_BindingPathCorrection",
});
yield put({
type: ReduxActionErrorTypes.FAILED_CORRECTING_BINDING_PATHS,
payload: {

View File

@ -9,7 +9,7 @@ import SegmentSingleton from "utils/Analytics/segment";
import MixpanelSingleton, {
type SessionRecordingConfig,
} from "utils/Analytics/mixpanel";
import SentryUtil from "utils/Analytics/sentry";
import FaroUtil from "utils/Analytics/sentry";
import SmartlookUtil from "utils/Analytics/smartlook";
import TrackedUser from "ee/utils/Analytics/trackedUser";
@ -31,7 +31,7 @@ async function initialize(
user: User,
sessionRecordingConfig: SessionRecordingConfig,
) {
SentryUtil.init();
// SentryUtil.init();
await SmartlookUtil.init();
segmentAnalytics = SegmentSingleton.getInstance();
@ -94,7 +94,7 @@ async function identifyUser(userData: User, sendAdditionalData?: boolean) {
await segmentAnalytics.identify(trackedUser.userId, userProperties);
}
SentryUtil.identifyUser(trackedUser.userId, userData);
FaroUtil.identifyUser(trackedUser.userId, userData);
if (trackedUser.email) {
SmartlookUtil.identify(trackedUser.userId, trackedUser.email);

View File

@ -1,4 +1,3 @@
import * as Sentry from "@sentry/react";
import { Route } from "react-router";
export const SentryRoute = Sentry.withSentryRouting(Route);
export const SentryRoute = Route;

View File

@ -14,7 +14,6 @@ import { Classes, Collapse } from "@blueprintjs/core";
import { UNDEFINED_VALIDATION } from "utils/validation/common";
import copy from "copy-to-clipboard";
import * as Sentry from "@sentry/react";
import type { CodeEditorExpected } from "components/editorComponents/CodeEditor/index";
import type { Indices } from "constants/Layers";
import { Layers } from "constants/Layers";
@ -29,6 +28,7 @@ import { getPathNavigationUrl } from "selectors/navigationSelectors";
import { Button, Icon, Link, toast, Tooltip } from "@appsmith/ads";
import type { EvaluationError } from "utils/DynamicBindingUtils";
import { DEBUGGER_TAB_KEYS } from "../Debugger/constants";
import captureException from "instrumentation/sendFaroErrors";
const modifiers: IPopoverSharedProps["modifiers"] = {
offset: {
@ -290,8 +290,8 @@ export function PreparedStatementViewer(props: {
const { parameters, value } = props.evaluatedValue;
if (!value) {
Sentry.captureException("Prepared statement got no value", {
level: "debug",
captureException(new Error("Prepared statement got no value"), {
errorName: "PreparedStatementError",
extra: { props },
});

View File

@ -78,7 +78,6 @@ import { Button } from "@appsmith/ads";
import "codemirror/addon/fold/brace-fold";
import "codemirror/addon/fold/foldgutter";
import "codemirror/addon/fold/foldgutter.css";
import * as Sentry from "@sentry/react";
import type { EvaluationError, LintError } from "utils/DynamicBindingUtils";
import { getEvalErrorPath, isDynamicValue } from "utils/DynamicBindingUtils";
import {
@ -1919,6 +1918,4 @@ const mapDispatchToProps = (dispatch: any) => ({
resetActiveField: () => dispatch(resetActiveEditorField()),
});
export default Sentry.withProfiler(
connect(mapStateToProps, mapDispatchToProps)(CodeEditor),
);
export default connect(mapStateToProps, mapDispatchToProps)(CodeEditor);

View File

@ -1,4 +1,3 @@
import * as Sentry from "@sentry/react";
import classNames from "classnames";
import React, {
memo,
@ -173,4 +172,4 @@ export const EntityExplorerSidebar = memo(({ children }: Props) => {
EntityExplorerSidebar.displayName = "EntityExplorerSidebar";
export default Sentry.withProfiler(EntityExplorerSidebar);
export default EntityExplorerSidebar;

View File

@ -1,7 +1,7 @@
import React from "react";
import styled from "styled-components";
import * as Sentry from "@sentry/react";
import * as log from "loglevel";
import captureException from "instrumentation/sendFaroErrors";
import type { ReactNode, CSSProperties } from "react";
@ -39,7 +39,7 @@ class ErrorBoundary extends React.Component<Props, State> {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
componentDidCatch(error: any, errorInfo: any) {
log.error({ error, errorInfo });
Sentry.captureException(error);
captureException(error, { errorName: "ErrorBoundary" });
}
render() {

View File

@ -6,7 +6,6 @@ import React, {
useMemo,
useRef,
} from "react";
import * as Sentry from "@sentry/react";
import { useSelector } from "react-redux";
import { getSelectedWidgets } from "selectors/ui";
import WidgetPropertyPane from "pages/Editor/PropertyPane";
@ -104,4 +103,4 @@ export const PropertyPaneSidebar = memo(() => {
PropertyPaneSidebar.displayName = "PropertyPaneSidebar";
export default Sentry.withProfiler(PropertyPaneSidebar);
export default PropertyPaneSidebar;

View File

@ -2,7 +2,6 @@ import React, { Component } from "react";
import type { AppState } from "ee/reducers";
import { connect } from "react-redux";
import type { Placement } from "popper.js";
import * as Sentry from "@sentry/react";
import _ from "lodash";
import type { ControlProps } from "./BaseControl";
import BaseControl from "./BaseControl";
@ -407,6 +406,6 @@ const mapStateToProps = (state: AppState): ReduxStateProps => ({
datasources: state.entities.datasources,
});
const EvaluatedValuePopupWrapper = Sentry.withProfiler(
connect(mapStateToProps)(EvaluatedValuePopupWrapperClass),
const EvaluatedValuePopupWrapper = connect(mapStateToProps)(
EvaluatedValuePopupWrapperClass,
);

View File

@ -1,5 +1,5 @@
import type { AppState } from "ee/reducers";
import * as Sentry from "@sentry/react";
import type { CodeEditorExpected } from "components/editorComponents/CodeEditor";
import { EditorTheme } from "components/editorComponents/CodeEditor/EditorConfig";
import EvaluatedValuePopup from "components/editorComponents/CodeEditor/EvaluatedValuePopup";
@ -548,6 +548,6 @@ const mapStateToProps = (
};
};
const EvaluatedValuePopupWrapper = Sentry.withProfiler(
connect(mapStateToProps)(EvaluatedValuePopupWrapperClass),
const EvaluatedValuePopupWrapper = connect(mapStateToProps)(
EvaluatedValuePopupWrapperClass,
);

View File

@ -2,7 +2,6 @@ import React, { Component } from "react";
import type { AppState } from "ee/reducers";
import { connect } from "react-redux";
import type { Placement } from "popper.js";
import * as Sentry from "@sentry/react";
import _, { toString } from "lodash";
import type { ControlProps } from "./BaseControl";
import BaseControl from "./BaseControl";
@ -497,6 +496,6 @@ const mapStateToProps = (
};
};
const EvaluatedValuePopupWrapper = Sentry.withProfiler(
connect(mapStateToProps)(EvaluatedValuePopupWrapperClass),
const EvaluatedValuePopupWrapper = connect(mapStateToProps)(
EvaluatedValuePopupWrapperClass,
);

View File

@ -10,7 +10,7 @@ import isString from "lodash/isString";
import isUndefined from "lodash/isUndefined";
import includes from "lodash/includes";
import map from "lodash/map";
import * as Sentry from "@sentry/react";
import captureException from "instrumentation/sendFaroErrors";
import { useDispatch } from "react-redux";
import { ReduxActionTypes } from "ee/constants/ReduxActionConstants";
import { DraggableListControl } from "pages/Editor/PropertyPane/DraggableListControl";
@ -131,10 +131,13 @@ class TabControl extends BaseControl<ControlProps, State> {
return parsedData;
} catch (error) {
Sentry.captureException({
message: "Tab Migration Failed",
oldData: this.props.propertyValue,
});
captureException(
{
message: "Tab Migration Failed",
oldData: this.props.propertyValue,
},
{ errorName: "TabMigrationError" },
);
}
} else {
return this.props.propertyValue;

View File

@ -3,7 +3,7 @@ import { sagas as CE_Sagas } from "ce/sagas";
import { ReduxActionTypes } from "ee/constants/ReduxActionConstants";
import { call, all, spawn, race, take } from "redux-saga/effects";
import log from "loglevel";
import * as sentry from "@sentry/react";
import captureException from "instrumentation/sendFaroErrors";
const sagasArr = [...CE_Sagas];
@ -22,7 +22,7 @@ export function* rootSaga(sagasToRun = sagasArr): any {
break;
} catch (e) {
log.error(e);
sentry.captureException(e);
captureException(e, { errorName: "RootSagaError" });
}
}
}),

View File

@ -1,4 +1,4 @@
import { captureException } from "@sentry/react";
import captureException from "instrumentation/sendFaroErrors";
import type { ApiResponse } from "api/types";
import log from "loglevel";
@ -22,7 +22,7 @@ export default function handleApiErrors(error?: Error, response?: ApiResponse) {
}
} else {
log.error(error);
captureException(error);
captureException(error, { errorName: "GitApiError" });
}
return apiError;

View File

@ -23,6 +23,12 @@ import {
import log from "loglevel";
import { isTracingEnabled } from "instrumentation/utils";
declare global {
interface Window {
faro: Faro | null;
}
}
const { appVersion, observability } = getAppsmithConfigs();
const { deploymentName, serviceInstanceId, serviceName, tracingUrl } =
observability;
@ -36,29 +42,41 @@ if (isTracingEnabled()) {
? InternalLoggerLevel.ERROR
: InternalLoggerLevel.OFF;
faro = initializeFaro({
url: tracingUrl,
app: {
name: serviceName,
version: appVersion.sha,
environment: deploymentName,
},
instrumentations: [new ReactIntegration(), ...getWebInstrumentations()],
ignoreUrls,
consoleInstrumentation: {
consoleErrorAsLog: true,
},
trackResources: true,
trackWebVitalsAttribution: true,
internalLoggerLevel,
sessionTracking: {
generateSessionId: () => {
// Disabling session tracing will not send any instrumentation data to the grafana backend
// Instead, hardcoding the session id to a constant value to indirecly disable session tracing
return "SESSION_ID";
},
},
});
try {
if (!window.faro) {
faro = initializeFaro({
url: tracingUrl,
app: {
name: serviceName,
version: appVersion.sha,
environment: deploymentName,
},
instrumentations: [
new ReactIntegration(),
...getWebInstrumentations({}),
],
ignoreUrls,
consoleInstrumentation: {
consoleErrorAsLog: false,
},
trackResources: true,
trackWebVitalsAttribution: true,
internalLoggerLevel,
sessionTracking: {
generateSessionId: () => {
// Disabling session tracing will not send any instrumentation data to the grafana backend
// Instead, hardcoding the session id to a constant value to indirecly disable session tracing
return "SESSION_ID";
},
},
});
}
} catch (error) {
// eslint-disable-next-line no-console
console.error(error);
}
faro = window.faro;
const tracerProvider = new WebTracerProvider({
resource: new Resource({
@ -68,18 +86,20 @@ if (isTracingEnabled()) {
}),
});
tracerProvider.addSpanProcessor(
new FaroSessionSpanProcessor(
new BatchSpanProcessor(new FaroTraceExporter({ ...faro })),
faro.metas,
),
);
if (faro) {
tracerProvider.addSpanProcessor(
new FaroSessionSpanProcessor(
new BatchSpanProcessor(new FaroTraceExporter({ ...faro })),
faro.metas,
),
);
tracerProvider.register({
contextManager: new ZoneContextManager(),
});
tracerProvider.register({
contextManager: new ZoneContextManager(),
});
faro.api.initOTEL(trace, context);
faro.api.initOTEL(trace, context);
}
}
export const getTraceAndContext = () => {
@ -91,3 +111,5 @@ export const getTraceAndContext = () => {
// return default OTEL context and trace if faro is not initialized
return faro.api.getOTEL() || { trace, context };
};
export { faro };

View File

@ -0,0 +1,21 @@
import { v4 as uuidv4 } from "uuid";
// eslint-disable-next-line @typescript-eslint/no-explicit-any
export function captureException(exception: any, hint?: any): string {
const eventId = uuidv4();
const context = hint || {};
try {
window.faro?.api.pushError(
exception instanceof Error ? exception : new Error(String(exception)),
{ type: "error", context: context },
);
} catch (error) {
// eslint-disable-next-line
console.error(error);
}
return eventId;
}
export default captureException;

View File

@ -20,7 +20,6 @@ import CenteredWrapper from "components/designSystems/appsmith/CenteredWrapper";
import { getCurrentUser } from "selectors/usersSelectors";
import type { User } from "constants/userConstants";
import RequestConfirmationModal from "pages/Editor/RequestConfirmationModal";
import * as Sentry from "@sentry/react";
import { getTheme, ThemeMode } from "selectors/themeSelectors";
import { ThemeProvider } from "styled-components";
import type { Theme } from "constants/DefaultTheme";
@ -222,6 +221,4 @@ const mapDispatchToProps = (dispatch: any) => {
};
};
export default withRouter(
connect(mapStateToProps, mapDispatchToProps)(Sentry.withProfiler(Editor)),
);
export default withRouter(connect(mapStateToProps, mapDispatchToProps)(Editor));

View File

@ -15,7 +15,6 @@ import {
} from "selectors/appViewSelectors";
import EditorContextProvider from "components/editorComponents/EditorContextProvider";
import AppViewerPageContainer from "./AppViewerPageContainer";
import * as Sentry from "@sentry/react";
import {
getCurrentPageDescription,
getIsAutoLayout,
@ -259,4 +258,4 @@ function AppViewer(props: Props) {
return <ThemeProvider theme={lightTheme}>{renderChildren()}</ThemeProvider>;
}
export default withRouter(Sentry.withProfiler(AppViewer));
export default withRouter(AppViewer);

View File

@ -1,7 +1,6 @@
import log from "loglevel";
import React, { useCallback } from "react";
import styled from "styled-components";
import * as Sentry from "@sentry/react";
import { useDispatch, useSelector } from "react-redux";
import type { CanvasWidgetStructure } from "WidgetProvider/constants";
import useWidgetFocus from "utils/hooks/useWidgetFocus";
@ -20,6 +19,7 @@ import { getAppThemeSettings } from "ee/selectors/applicationSelectors";
import CodeModeTooltip from "pages/Editor/WidgetsEditor/components/CodeModeTooltip";
import { getIsAnvilLayout } from "layoutSystems/anvil/integrations/selectors";
import { focusWidget } from "actions/widgetActions";
import captureException from "instrumentation/sendFaroErrors";
interface CanvasProps {
widgetsStructure: CanvasWidgetStructure;
@ -120,7 +120,7 @@ const Canvas = (props: CanvasProps) => {
return renderChildren();
} catch (error) {
log.error("Error rendering DSL", error);
Sentry.captureException(error);
captureException(error, { errorName: "Canvas" });
return null;
}

View File

@ -1,4 +1,3 @@
import * as Sentry from "@sentry/react";
import React, { useRef } from "react";
import {
Button,
@ -108,4 +107,4 @@ function ConversionButton() {
ConversionButton.displayName = "ConversionButton";
export default Sentry.withProfiler(ConversionButton);
export default ConversionButton;

View File

@ -1,4 +1,3 @@
import * as Sentry from "@sentry/react";
import React, { useState } from "react";
import { useDispatch, useSelector } from "react-redux";
import type { AppState } from "ee/reducers";
@ -130,4 +129,4 @@ export function SnapShotBannerCTA() {
SnapShotBannerCTA.displayName = "SnapShotBannerCTA";
export default Sentry.withProfiler(SnapShotBannerCTA);
export default SnapShotBannerCTA;

View File

@ -1,5 +1,3 @@
import * as Sentry from "@sentry/react";
import React from "react";
import ConversionButton from "../CanvasLayoutConversion/ConversionButton";
import styled from "styled-components";
@ -47,4 +45,4 @@ export function CanvasPropertyPane() {
CanvasPropertyPane.displayName = "CanvasPropertyPane";
export default Sentry.withProfiler(CanvasPropertyPane);
export default CanvasPropertyPane;

View File

@ -2,7 +2,6 @@ import React, { useCallback, useEffect } from "react";
import EntityProperty from "./EntityProperty";
import { useDispatch, useSelector } from "react-redux";
import * as Sentry from "@sentry/react";
import type { AppState } from "ee/reducers";
import classNames from "classnames";
import styled from "styled-components";
@ -177,4 +176,4 @@ export function EntityProperties() {
);
}
export default Sentry.withProfiler(EntityProperties);
export default EntityProperties;

View File

@ -2,7 +2,6 @@ import React, { useMemo } from "react";
import { useRouteMatch } from "react-router-dom";
import { useDispatch, useSelector } from "react-redux";
import JsEditorForm from "./Form";
import * as Sentry from "@sentry/react";
import { getJSCollectionDataByBaseId } from "selectors/editorSelectors";
import CenteredWrapper from "components/designSystems/appsmith/CenteredWrapper";
import Spinner from "components/editorComponents/Spinner";
@ -12,6 +11,7 @@ import AppJSEditorContextMenu from "./AppJSEditorContextMenu";
import { updateFunctionProperty } from "actions/jsPaneActions";
import type { OnUpdateSettingsProps } from "./JSEditorToolbar";
import { saveJSObjectName } from "actions/jsActionActions";
const LoadingContainer = styled(CenteredWrapper)`
height: 50%;
`;
@ -67,4 +67,4 @@ function JSEditor() {
return <EntityNotFoundPane />;
}
export default Sentry.withProfiler(JSEditor);
export default JSEditor;

View File

@ -1,5 +1,4 @@
import React from "react";
import * as Sentry from "@sentry/react";
import {
createMessage,
MULTI_SELECT_PROPERTY_PANE_MESSAGE,
@ -57,4 +56,4 @@ function MultiSelectPropertyPane() {
MultiSelectPropertyPane.displayName = "MultiSelectPropertyPane";
export default Sentry.withProfiler(MultiSelectPropertyPane);
export default MultiSelectPropertyPane;

View File

@ -1,4 +1,3 @@
import * as Sentry from "@sentry/react";
import type { ComponentPropsWithoutRef } from "react";
import React, { useState } from "react";
import { Collapse } from "@blueprintjs/core";
@ -56,4 +55,4 @@ export function SettingSection(props: SettingSectionProps) {
SettingSection.displayName = "SettingSection";
export default Sentry.withProfiler(SettingSection);
export default SettingSection;

View File

@ -1,7 +1,6 @@
import { last } from "lodash";
import classNames from "classnames";
import styled from "styled-components";
import * as Sentry from "@sentry/react";
import React, { useState } from "react";
import { useDispatch, useSelector } from "react-redux";
@ -246,4 +245,4 @@ export function ThemeCard(props: ThemeCard) {
ThemeCard.displayName = "ThemeCard";
export default Sentry.withProfiler(ThemeCard);
export default ThemeCard;

View File

@ -3,7 +3,7 @@ import { get, startCase } from "lodash";
import { useDispatch, useSelector } from "react-redux";
import React, { useCallback } from "react";
import ThemeCard from "./ThemeCard";
import { ThemeCard } from "./ThemeCard";
import {
AppThemingMode,
getAppThemingStack,

View File

@ -1,5 +1,4 @@
import React, { useMemo } from "react";
import * as Sentry from "@sentry/react";
import { last } from "lodash";
import ThemeEditor from "./ThemeEditor";
@ -47,4 +46,4 @@ export function ThemePropertyPane() {
ThemePropertyPane.displayName = "ThemePropertyPane";
export default Sentry.withProfiler(ThemePropertyPane);
export default ThemePropertyPane;

View File

@ -51,8 +51,8 @@ import Helmet from "react-helmet";
import { useFeatureFlag } from "utils/hooks/useFeatureFlag";
import { FEATURE_FLAG } from "ee/entities/FeatureFlag";
import { getHTMLPageTitle } from "ee/utils/BusinessFeatures/brandingPageHelpers";
import * as Sentry from "@sentry/react";
import CsrfTokenInput from "pages/UserAuth/CsrfTokenInput";
import captureException from "instrumentation/sendFaroErrors";
import { getSafeErrorMessage } from "ee/constants/approvedErrorMessages";
const validate = (values: LoginFormValues, props: ValidateProps) => {
@ -116,12 +116,7 @@ export function Login(props: LoginFormProps) {
if (queryParams.get("error")) {
errorMessage = queryParams.get("message") || queryParams.get("error") || "";
showError = true;
Sentry.captureException("Login failed", {
level: "error",
extra: {
error: new Error(errorMessage),
},
});
captureException(new Error(errorMessage), { errorName: "LoginError" });
}
let loginURL = "/api/v1/" + LOGIN_SUBMIT_PATH;

View File

@ -60,11 +60,11 @@ import { FEATURE_FLAG } from "ee/entities/FeatureFlag";
import { getHTMLPageTitle } from "ee/utils/BusinessFeatures/brandingPageHelpers";
import log from "loglevel";
import { SELF_HOSTING_DOC } from "constants/ThirdPartyConstants";
import * as Sentry from "@sentry/react";
import CsrfTokenInput from "pages/UserAuth/CsrfTokenInput";
import { useIsCloudBillingEnabled } from "hooks";
import { isLoginHostname } from "utils/cloudBillingUtils";
import { getIsAiAgentFlowEnabled } from "ee/selectors/aiAgentSelectors";
import captureException from "instrumentation/sendFaroErrors";
import { getSafeErrorMessage } from "ee/constants/approvedErrorMessages";
declare global {
@ -145,11 +145,8 @@ export function SignUp(props: SignUpFormProps) {
if (queryParams.get("error")) {
errorMessage = queryParams.get("error") || "";
showError = true;
Sentry.captureException("Sign up failed", {
level: "error",
extra: {
error: new Error(errorMessage),
},
captureException(new Error(errorMessage), {
errorName: "SignUp",
});
}

View File

@ -2,11 +2,11 @@ import React, { useEffect } from "react";
import Container from "./Container";
import type { RouteComponentProps } from "react-router-dom";
import { Spinner } from "@appsmith/ads";
import * as Sentry from "@sentry/react";
import { EMAIL_VERIFICATION_PATH } from "ee/constants/ApiConstants";
import { Redirect } from "react-router-dom";
import { VerificationErrorType } from "./VerificationError";
import CsrfTokenInput from "pages/UserAuth/CsrfTokenInput";
import captureException from "instrumentation/sendFaroErrors";
const VerifyUser = (
props: RouteComponentProps<{
@ -24,7 +24,9 @@ const VerifyUser = (
useEffect(() => {
if (!token || !email) {
Sentry.captureMessage("User email verification link is damaged");
captureException(new Error("User email verification link is damaged"), {
errorName: "VerificationLinkDamaged",
});
}
const formElement: HTMLFormElement = document.getElementById(

View File

@ -2,10 +2,10 @@ import { ReduxActionTypes } from "ee/constants/ReduxActionConstants";
import { SubmissionError } from "redux-form";
import { useCallback, useEffect, useState } from "react";
import type { Dispatch } from "redux";
import * as Sentry from "@sentry/react";
import UserApi from "ee/api/UserApi";
import { toast } from "@appsmith/ads";
import type { ApiResponse } from "../../api/ApiResponses";
import captureException from "instrumentation/sendFaroErrors";
export interface LoginFormValues {
username?: string;
@ -95,7 +95,9 @@ export const useResendEmailVerification = (
if (!email) {
const errorMessage = "Email not found for retry verification";
Sentry.captureMessage(errorMessage);
captureException(new Error(errorMessage), {
errorName: "EmailVerificationRetryError",
});
toast.show(errorMessage, { kind: "error" });
return;

View File

@ -3,7 +3,7 @@ import { ReduxActionTypes } from "ee/constants/ReduxActionConstants";
import { applyChange, type Diff } from "deep-diff";
import type { DataTree } from "entities/DataTree/dataTreeTypes";
import { createImmerReducer } from "utils/ReducerUtils";
import * as Sentry from "@sentry/react";
import captureException from "instrumentation/sendFaroErrors";
export type EvaluatedTreeState = DataTree;
@ -32,7 +32,8 @@ const evaluatedTreeReducer = createImmerReducer(initialState, {
applyChange(state, undefined, update);
} catch (e) {
Sentry.captureException(e, {
captureException(e, {
errorName: "TreeReducer",
extra: {
update,
updateLength: updates.length,

View File

@ -7,7 +7,6 @@ import {
take,
takeLatest,
} from "redux-saga/effects";
import * as Sentry from "@sentry/react";
import {
clearActionResponse,
executePageLoadActions,
@ -170,6 +169,7 @@ import {
selectGitOpsModalOpen,
} from "selectors/gitModSelectors";
import { createActionExecutionResponse } from "./PluginActionSagaUtils";
import captureException from "instrumentation/sendFaroErrors";
interface FilePickerInstumentationObject {
numberOfFiles: number;
@ -989,11 +989,12 @@ function* executeOnPageLoadJSAction(pageAction: PageAction) {
);
if (!collection) {
Sentry.captureException(
captureException(
new Error(
"Collection present in layoutOnLoadActions but no collection exists ",
),
{
errorName: "MissingJSCollection",
extra: {
collectionId,
actionId: pageAction.id,

View File

@ -44,7 +44,7 @@ import {
selectApplicationVersion,
} from "selectors/editorSelectors";
import { find } from "lodash";
import * as Sentry from "@sentry/react";
import captureException from "instrumentation/sendFaroErrors";
import { getAllPageIdentities } from "./selectors";
import type { SagaIterator } from "@redux-saga/types";
import type { AxiosPromise } from "axios";
@ -126,8 +126,8 @@ export function* fetchAppSelectedTheme(
payload: response.data,
});
} else {
Sentry.captureException("Unable to fetch the selected theme", {
level: "fatal",
captureException(new Error("Unable to fetch the selected theme"), {
errorName: "ThemeFetchError",
extra: {
pageIdentities,
applicationId,

View File

@ -34,7 +34,6 @@ import {
} from "ee/constants/messages";
import store from "store";
import * as Sentry from "@sentry/react";
import { getLoginUrl } from "ee/utils/adminSettingsHelpers";
import type { PluginErrorDetails } from "api/ActionAPI";
import showToast from "sagas/ToastSagas";
@ -42,6 +41,7 @@ import AppsmithConsole from "../utils/AppsmithConsole";
import type { SourceEntity } from "../entities/AppsmithConsole";
import { getAppMode } from "ee/selectors/applicationSelectors";
import { APP_MODE } from "../entities/App";
import captureException from "instrumentation/sendFaroErrors";
const shouldShowToast = (action: string) => {
return action in toastMessageErrorTypes;
@ -298,7 +298,7 @@ export function* errorSaga(errorAction: ReduxAction<ErrorActionPayload>) {
break;
}
case ErrorEffectTypes.LOG_TO_SENTRY: {
yield call(Sentry.captureException, error);
yield call(captureException, error, { errorName: "ErrorSagaError" });
break;
}
}

View File

@ -17,7 +17,7 @@ import { get } from "lodash";
import LOG_TYPE from "entities/AppsmithConsole/logtype";
import { select } from "redux-saga/effects";
import AppsmithConsole from "utils/AppsmithConsole";
import * as Sentry from "@sentry/react";
import captureException from "instrumentation/sendFaroErrors";
import AnalyticsUtil from "ee/utils/AnalyticsUtil";
import {
createMessage,
@ -232,7 +232,8 @@ export function* evalErrorHandler(
if (error.context.logToSentry) {
// Send the generic error message to sentry for better grouping
Sentry.captureException(new Error(error.message), {
captureException(new Error(error.message), {
errorName: "CyclicalDependencyError",
tags: {
node,
entityType,
@ -264,22 +265,23 @@ export function* evalErrorHandler(
kind: "error",
});
log.error(error);
Sentry.captureException(error);
captureException(error, { errorName: "EvalTreeError" });
break;
}
case EvalErrorTypes.BAD_UNEVAL_TREE_ERROR: {
log.error(error);
Sentry.captureException(error);
captureException(error, { errorName: "BadUnevalTreeError" });
break;
}
case EvalErrorTypes.EVAL_PROPERTY_ERROR: {
Sentry.captureException(error);
captureException(error, { errorName: "EvalPropertyError" });
log.error(error);
break;
}
case EvalErrorTypes.CLONE_ERROR: {
log.debug(error);
Sentry.captureException(new Error(error.message), {
captureException(new Error(error.message), {
errorName: "CloneError",
extra: {
request: error.context,
},
@ -294,18 +296,22 @@ export function* evalErrorHandler(
text: `${error.message} at: ${error.context?.propertyPath}`,
});
log.error(error);
Sentry.captureException(error);
captureException(error, {
errorName: "ParseJSError",
entity: error.context,
});
break;
}
case EvalErrorTypes.EXTRACT_DEPENDENCY_ERROR: {
Sentry.captureException(new Error(error.message), {
captureException(new Error(error.message), {
errorName: "ExtractDependencyError",
extra: error.context,
});
break;
}
default: {
log.error(error);
Sentry.captureException(error);
captureException(error, { errorName: "UnknownEvalError" });
}
}
});

View File

@ -30,7 +30,6 @@ import { PropertyEvaluationErrorType } from "utils/DynamicBindingUtils";
import { EVAL_WORKER_ACTIONS } from "ee/workers/Evaluation/evalWorkerActions";
import log from "loglevel";
import type { WidgetProps } from "widgets/BaseWidget";
import * as Sentry from "@sentry/react";
import type { Action } from "redux";
import {
EVAL_AND_LINT_REDUX_ACTIONS,
@ -123,6 +122,7 @@ import type {
AffectedJSObjects,
EvaluationReduxAction,
} from "actions/EvaluationReduxActionTypes";
import captureException from "instrumentation/sendFaroErrors";
const APPSMITH_CONFIGS = getAppsmithConfigs();
@ -932,7 +932,7 @@ export function* evaluateActionSelectorFieldSaga(action: any) {
);
} catch (e) {
log.error(e);
Sentry.captureException(e);
captureException(e, { errorName: "EvaluationError" });
}
}
@ -1003,7 +1003,7 @@ export default function* evaluationSagaListeners() {
yield call(evaluationChangeListenerSaga);
} catch (e) {
log.error(e);
Sentry.captureException(e);
captureException(e, { errorName: "EvaluationError" });
}
}
}

View File

@ -11,7 +11,7 @@ import {
import type { ReduxAction } from "actions/ReduxActionTypes";
import { ReduxActionTypes } from "ee/constants/ReduxActionConstants";
import log from "loglevel";
import * as Sentry from "@sentry/react";
import captureException from "instrumentation/sendFaroErrors";
import { getFormEvaluationState } from "selectors/formSelectors";
import { evalFormConfig } from "./EvaluationsSaga";
import type {
@ -372,7 +372,7 @@ export default function* formEvaluationChangeListener() {
yield call(formEvaluationChangeListenerSaga);
} catch (e) {
log.error(e);
Sentry.captureException(e);
captureException(e, { errorName: "FormEvaluationError" });
}
}
}

View File

@ -20,7 +20,6 @@ import { ReduxActionTypes } from "ee/constants/ReduxActionConstants";
import { resetApplicationWidgets, resetPageList } from "actions/pageActions";
import { resetCurrentApplication } from "ee/actions/applicationActions";
import log from "loglevel";
import * as Sentry from "@sentry/react";
import { resetRecentEntities } from "actions/globalSearchActions";
import {
@ -93,6 +92,7 @@ import type { ApplicationPayload } from "entities/Application";
import type { Page } from "entities/Page";
import type { PACKAGE_PULL_STATUS } from "ee/constants/ModuleConstants";
import { validateSessionToken } from "utils/SessionUtils";
import captureException from "instrumentation/sendFaroErrors";
export const URL_CHANGE_ACTIONS = [
ReduxActionTypes.CURRENT_APPLICATION_NAME_UPDATE,
@ -278,10 +278,12 @@ export function* getInitResponses({
ReduxActionTypes.END_CONSOLIDATED_PAGE_LOAD,
shouldInitialiseUserDetails,
);
Sentry.captureMessage(
`consolidated api failure for ${JSON.stringify(
params,
)} errored message response ${e}`,
captureException(
new Error(`consolidated api failure for ${JSON.stringify(params)}`),
{
errorName: "ConsolidatedApiError",
extra: { error: e },
},
);
throw new PageNotFoundError(`Cannot find page with base id: ${basePageId}`);
}
@ -372,7 +374,7 @@ export function* startAppEngine(action: ReduxAction<AppEnginePayload>) {
if (e instanceof AppEngineApiError) return;
Sentry.captureException(e);
captureException(e, { errorName: "AppEngineError" });
yield put(safeCrashAppRequest());
} finally {
endSpan(rootSpan);
@ -469,7 +471,7 @@ function* eagerPageInitSaga() {
} catch (error) {
// Log error but don't block the rest of the initialization
log.error("Error validating session token:", error);
Sentry.captureException(error);
captureException(error, { errorName: "SessionValidationError" });
}
const url = window.location.pathname;

View File

@ -8,7 +8,7 @@ import {
takeLatest,
} from "redux-saga/effects";
import * as Sentry from "@sentry/react";
import captureException from "instrumentation/sendFaroErrors";
import log from "loglevel";
import {
@ -132,7 +132,7 @@ export function* openPropertyPaneSaga(replay: any) {
);
} catch (e) {
log.error(e);
Sentry.captureException(e);
captureException(e, { errorName: "OpenPropertyPaneError" });
}
}
@ -164,7 +164,7 @@ export function* postUndoRedoSaga(replay: any) {
scrollWidgetIntoView(widgetIds[0]);
} catch (e) {
log.error(e);
Sentry.captureException(e);
captureException(e, { errorName: "PostUndoRedoError" });
}
}
@ -257,7 +257,7 @@ export function* undoRedoSaga(action: ReduxAction<UndoRedoPayload>) {
}
} catch (e) {
log.error(e);
Sentry.captureException(e);
captureException(e, { errorName: "UndoRedoSagaError" });
}
}

View File

@ -16,7 +16,7 @@ import {
ReduxActionTypes,
} from "ee/constants/ReduxActionConstants";
import log from "loglevel";
import * as Sentry from "@sentry/react";
import captureException from "instrumentation/sendFaroErrors";
import { findLoadingEntities } from "utils/WidgetLoadingStateUtils";
const actionExecutionRequestActions = [
@ -101,7 +101,7 @@ export default function* actionExecutionChangeListeners() {
yield call(actionExecutionChangeListenerSaga);
} catch (e) {
log.error(e);
Sentry.captureException(e);
captureException(e, { errorName: "WidgetLoadingError" });
}
}
}

View File

@ -16,7 +16,6 @@ import {
createSnapshotSaga,
deleteApplicationSnapshotSaga,
} from "./SnapshotSagas";
import * as Sentry from "@sentry/react";
import log from "loglevel";
import { saveAllPagesSaga } from "ee/sagas/PageSagas";
import { updateApplicationLayout } from "ee/actions/applicationActions";
@ -27,6 +26,7 @@ import {
import { updateApplicationLayoutType } from "./AutoLayoutUpdateSagas";
import AnalyticsUtil from "ee/utils/AnalyticsUtil";
import { nestDSL } from "@shared/dsl";
import captureException from "instrumentation/sendFaroErrors";
/**
* This method is used to convert from auto-layout to fixed layout
@ -214,7 +214,7 @@ function* logLayoutConversionErrorSaga() {
(state: AppState) => state.ui.layoutConversion.conversionError,
);
yield call(Sentry.captureException, error);
yield call(captureException, error, { errorName: "LayoutConversionError" });
} catch (e) {
throw e;
}

View File

@ -1,99 +1,24 @@
import * as Sentry from "@sentry/react";
import { getAppsmithConfigs } from "ee/configs";
import log from "loglevel";
import { captureException } from "instrumentation/sendFaroErrors";
import type { User } from "constants/userConstants";
class SentryUtil {
class FaroUtil {
static init() {
const { sentry } = getAppsmithConfigs();
try {
if (sentry.enabled && !window.Sentry) {
window.Sentry = Sentry;
Sentry.init({
...sentry,
beforeSend(event) {
const exception = SentryUtil.extractSentryException(event);
if (exception?.type === "ChunkLoadError") {
// Only log ChunkLoadErrors after the 2 retries
if (!exception.value?.includes("failed after 2 retries")) {
return null;
}
}
// Handle Non-Error rejections
if (exception?.value?.startsWith("Non-Error")) {
// TODO: Fix this the next time the file is edited
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const serializedData: any = event.extra?.__serialized__;
if (!serializedData) return null; // if no data is attached, ignore error
const actualErrorMessage = serializedData.error
? serializedData.error.message
: serializedData.message;
if (!actualErrorMessage) return null; // If no message is attached, ignore error
// Now modify the original error
exception.value = actualErrorMessage;
event.message = actualErrorMessage;
}
return event;
},
beforeBreadcrumb(breadcrumb) {
if (
breadcrumb.category === "console" &&
breadcrumb.level !== "error"
) {
return null;
}
if (breadcrumb.category === "sentry.transaction") {
return null;
}
if (breadcrumb.category === "redux.action") {
if (
breadcrumb.data &&
breadcrumb.data.type === "SET_EVALUATED_TREE"
) {
breadcrumb.data = undefined;
}
}
return breadcrumb;
},
});
}
} catch (error) {
log.error("Failed to initialize Sentry:", error);
}
// No initialization needed
}
public static identifyUser(userId: string, userData: User) {
const { sentry } = getAppsmithConfigs();
if (sentry.enabled) {
Sentry.configureScope(function (scope) {
scope.setUser({
id: userId,
username: userData.username,
email: userData.email,
});
});
}
// Set user context for error reporting
window.faro?.api.setUser({
id: userId,
username: userData.username,
email: userData.email,
});
}
private static extractSentryException(event: Sentry.Event) {
if (!event.exception) return null;
const value = event.exception.values ? event.exception.values[0] : null;
return value;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
public static captureException(error: Error, context?: any) {
captureException(error, context);
}
}
export default SentryUtil;
export default FaroUtil;

View File

@ -1,4 +1,4 @@
import * as Sentry from "@sentry/react";
import captureException from "instrumentation/sendFaroErrors";
//Following function is the fix for the missing where key
/**
@ -47,10 +47,13 @@ export function getPathAndValueFromActionDiffObject(actionObjectDiff: any) {
return acc;
} catch (error) {
Sentry.captureException({
message: `Adding key: where failed, cannot create path`,
oldData: actionObjectDiff,
});
captureException(
{
message: `Adding key: where failed, cannot create path`,
oldData: actionObjectDiff,
},
{ errorName: "ActionDiffPathError" },
);
}
},
"",

View File

@ -15,8 +15,10 @@ import {
concatWithArray,
} from "./helpers";
import WidgetFactory from "../WidgetProvider/factory";
import * as Sentry from "@sentry/react";
import { Colors } from "constants/Colors";
import * as FaroErrors from "instrumentation/sendFaroErrors";
jest.mock("instrumentation/sendFaroErrors");
describe("flattenObject test", () => {
it("Check if non nested object is returned correctly", () => {
@ -202,6 +204,10 @@ describe("#getLocale", () => {
});
describe("#captureInvalidDynamicBindingPath", () => {
beforeEach(() => {
jest.clearAllMocks();
});
it("DSL should not be altered", () => {
const baseDSL = {
widgetName: "RadioGroup1",
@ -278,7 +284,6 @@ describe("#captureInvalidDynamicBindingPath", () => {
helpText: "Sets a default selected option",
propertyName: "defaultOptionValue",
label: "Default selected value",
// placeholderText: "Y",
controlType: "INPUT_TEXT",
isBindProperty: true,
isTriggerProperty: false,
@ -340,7 +345,6 @@ describe("#captureInvalidDynamicBindingPath", () => {
label: "Animate loading",
controlType: "SWITCH",
helpText: "Controls the loading of the widget",
// defaultValue: true,
isJSConvertible: true,
isBindProperty: true,
isTriggerProperty: false,
@ -449,7 +453,6 @@ describe("#captureInvalidDynamicBindingPath", () => {
helpText: "Sets a default selected option",
propertyName: "defaultOptionValue",
label: "Default selected value",
// placeholderText: "Y",
controlType: "INPUT_TEXT",
isBindProperty: true,
isTriggerProperty: false,
@ -511,7 +514,6 @@ describe("#captureInvalidDynamicBindingPath", () => {
label: "Animate loading",
controlType: "SWITCH",
helpText: "Controls the loading of the widget",
// defaultValue: true,
isJSConvertible: true,
isBindProperty: true,
isTriggerProperty: false,
@ -541,13 +543,18 @@ describe("#captureInvalidDynamicBindingPath", () => {
},
]);
const sentrySpy = jest.spyOn(Sentry, "captureException");
const mockCaptureException = jest.fn();
(FaroErrors.captureException as jest.Mock).mockImplementation(
mockCaptureException,
);
captureInvalidDynamicBindingPath(baseDSL);
expect(sentrySpy).toHaveBeenCalledWith(
expect(mockCaptureException).toHaveBeenCalledWith(
new Error(
`INVALID_DynamicPathBinding_CLIENT_ERROR: Invalid dynamic path binding list: RadioGroup1.options`,
),
{ errorName: "InvalidDynamicPathBinding" },
);
});
});

View File

@ -17,7 +17,6 @@ import moment from "moment";
import { isDynamicValue } from "./DynamicBindingUtils";
import type { ApiResponse } from "api/ApiResponses";
import type { DSLWidget } from "WidgetProvider/constants";
import * as Sentry from "@sentry/react";
import { matchPath } from "react-router";
import {
BUILDER_CUSTOM_PATH,
@ -45,6 +44,7 @@ import { klona as klonaJson } from "klona/json";
import { startAndEndSpanForFn } from "instrumentation/generateTraces";
import type { Property } from "entities/Action";
import captureException from "instrumentation/sendFaroErrors";
export const snapToGrid = (
columnWidth: number,
@ -944,10 +944,11 @@ export const captureInvalidDynamicBindingPath = (
* Checks if dynamicBindingPathList contains a property path that doesn't have a binding
*/
if (!isDynamicValue(pathValue)) {
Sentry.captureException(
captureException(
new Error(
`INVALID_DynamicPathBinding_CLIENT_ERROR: Invalid dynamic path binding list: ${currentDSL.widgetName}.${dBindingPath.key}`,
),
{ errorName: "InvalidDynamicPathBinding" },
);
return;

View File

@ -16,7 +16,6 @@ import _ from "lodash";
import derivedProperties from "./parsedDerivedProperties";
import BaseInputWidget from "widgets/BaseInputWidget";
import type { BaseInputWidgetProps } from "widgets/BaseInputWidget/widget";
import * as Sentry from "@sentry/react";
import log from "loglevel";
import {
formatCurrencyNumber,
@ -44,6 +43,7 @@ import { getDefaultCurrency } from "../component/CurrencyCodeDropdown";
import IconSVG from "../icon.svg";
import ThumbnailSVG from "../thumbnail.svg";
import { WIDGET_TAGS } from "constants/WidgetConstants";
import captureException from "instrumentation/sendFaroErrors";
export function defaultValueValidation(
// TODO: Fix this the next time the file is edited
@ -499,7 +499,7 @@ class CurrencyInputWidget extends BaseInputWidget<
this.props.updateWidgetMetaProperty("text", formattedValue);
} catch (e) {
log.error(e);
Sentry.captureException(e);
captureException(e, { errorName: "CurrencyInputWidget" });
}
}
}
@ -517,7 +517,7 @@ class CurrencyInputWidget extends BaseInputWidget<
} catch (e) {
formattedValue = value;
log.error(e);
Sentry.captureException(e);
captureException(e, { errorName: "CurrencyInputWidget" });
}
// text is stored as what user has typed
@ -575,7 +575,7 @@ class CurrencyInputWidget extends BaseInputWidget<
}
} catch (e) {
log.error(e);
Sentry.captureException(e);
captureException(e, { errorName: "CurrencyInputWidget" });
this.props.updateWidgetMetaProperty("text", this.props.text);
}

View File

@ -1,4 +1,3 @@
import * as Sentry from "@sentry/react";
import React, { useCallback, useContext, useMemo, useState } from "react";
import type { BaseInputComponentProps } from "./BaseInputField";
@ -15,6 +14,7 @@ import derived from "widgets/CurrencyInputWidget/widget/derived";
import { isEmpty } from "../helper";
import { BASE_LABEL_TEXT_SIZE } from "../component/FieldLabel";
import { getLocaleDecimalSeperator } from "widgets/WidgetUtils";
import captureException from "instrumentation/sendFaroErrors";
type CurrencyInputComponentProps = BaseInputComponentProps & {
currencyCountryCode: string;
@ -132,7 +132,7 @@ function CurrencyInputField({
}
} catch (e) {
text = inputValue;
Sentry.captureException(e);
captureException(e, { errorName: "JSONFormWidget_CurrencyInputField" });
}
const value = derived.value({ text });

View File

@ -1,4 +1,3 @@
import * as Sentry from "@sentry/react";
import { set } from "lodash";
import type { ControllerProps } from "react-hook-form";
import { useFormContext } from "react-hook-form";
@ -8,6 +7,7 @@ import FormContext from "../FormContext";
import type { FieldType } from "../constants";
import { startAndEndSpanForFn } from "instrumentation/generateTraces";
import { klonaRegularWithTelemetry } from "utils/helpers";
import captureException from "instrumentation/sendFaroErrors";
export interface UseRegisterFieldValidityProps {
isValid: boolean;
@ -53,7 +53,9 @@ function useRegisterFieldValidity({
}
}
} catch (e) {
Sentry.captureException(e);
captureException(e, {
errorName: "JSONFormWidget_useRegisterFieldValidity",
});
}
}, 0);
}, [isValid, fieldName, fieldType, error, clearErrors, setError]);

View File

@ -3,7 +3,7 @@ import countryDetails from "./countryDetails";
import { MapTypes } from "../constants";
import { geoAlbers, geoAzimuthalEqualArea, geoMercator } from "d3-geo";
import log from "loglevel";
import * as Sentry from "@sentry/react";
import captureException from "instrumentation/sendFaroErrors";
import { retryPromise } from "utils/AppsmithUtils";
interface GeoSpecialAreas {
@ -75,7 +75,9 @@ export const loadMapGenerator = () => {
if (error.code !== 20) {
log.error({ error });
Sentry.captureException(error);
captureException(error, {
errorName: "MapChartWidget_utilities",
});
}
},
)

View File

@ -19,7 +19,6 @@ import type { BaseInputWidgetProps } from "widgets/BaseInputWidget/widget";
import { mergeWidgetConfig } from "utils/helpers";
import type { CountryCode } from "libphonenumber-js";
import { AsYouType, parseIncompletePhoneNumber } from "libphonenumber-js";
import * as Sentry from "@sentry/react";
import log from "loglevel";
import type { SetterConfig, Stylesheet } from "entities/AppTheming";
import {
@ -39,6 +38,7 @@ import { getDefaultISDCode } from "../component/ISDCodeDropdown";
import IconSVG from "../icon.svg";
import ThumbnailSVG from "../thumbnail.svg";
import { WIDGET_TAGS } from "constants/WidgetConstants";
import captureException from "instrumentation/sendFaroErrors";
export function defaultValueValidation(
// TODO: Fix this the next time the file is edited
@ -348,7 +348,7 @@ class PhoneInputWidget extends BaseInputWidget<
this.props.updateWidgetMetaProperty("text", formattedValue);
} catch (e) {
log.error(e);
Sentry.captureException(e);
captureException(e, { errorName: "PhoneInputWidget" });
}
}
}

View File

@ -19,8 +19,8 @@ import {
getLocaleThousandSeparator,
} from "widgets/WidgetUtils";
import { limitDecimalValue } from "widgets/CurrencyInputWidget/component/utilities";
import * as Sentry from "@sentry/react";
import { getLocale } from "utils/helpers";
import captureException from "instrumentation/sendFaroErrors";
const FOCUS_CLASS = "has-focus";
@ -237,7 +237,7 @@ export function InlineCellEditor({
value = convertToNumber(inputValue);
} catch (e) {
Sentry.captureException(e);
captureException(e, { errorName: "TableWidgetV2_InlineCellEditor" });
}
}

View File

@ -19,8 +19,8 @@ import CurrencyTypeDropdown, {
CurrencyDropdownOptions,
} from "widgets/CurrencyInputWidget/component/CurrencyCodeDropdown";
import { getLocale } from "utils/helpers";
import * as Sentry from "@sentry/react";
import { getLocaleThousandSeparator } from "widgets/WidgetUtils";
import captureException from "instrumentation/sendFaroErrors";
const Container = styled.div<{
isCellEditMode?: boolean;
@ -227,7 +227,7 @@ function PlainTextCell(
return currency?.id + " " + formattedValue;
}
} catch (e) {
Sentry.captureException(e);
captureException(e, { errorName: "TableWidgetV2_PlainTextCell" });
return value;
}

View File

@ -10,9 +10,9 @@ import { ValidationTypes } from "constants/WidgetValidation";
import { generateReactKey } from "utils/generators";
import { EVAL_VALUE_PATH } from "utils/DynamicBindingUtils";
import { AutocompleteDataType } from "utils/autocomplete/AutocompleteDataType";
import * as Sentry from "@sentry/react";
import type { DSLWidget } from "WidgetProvider/constants";
import { DATA_BIND_REGEX_GLOBAL } from "constants/BindingsConstants";
import captureException from "instrumentation/sendFaroErrors";
function migrateTabsDataUsingMigrator(currentDSL: DSLWidget) {
if (currentDSL.type === "TABS_WIDGET" && currentDSL.version === 1) {
@ -20,7 +20,8 @@ function migrateTabsDataUsingMigrator(currentDSL: DSLWidget) {
currentDSL.type = "TABS_MIGRATOR_WIDGET";
currentDSL.version = 1;
} catch (error) {
Sentry.captureException({
captureException(error, {
errorName: "TabsMigrator",
message: "Tabs Migration Failed",
oldData: currentDSL.tabs,
});
@ -116,7 +117,8 @@ const migrateTabsData = (currentDSL: DSLWidget) => {
currentDSL.version = 2;
delete currentDSL.tabs;
} catch (error) {
Sentry.captureException({
captureException(error, {
errorName: "TabsMigrator",
message: "Tabs Migration Failed",
oldData: currentDSL.tabs,
});

View File

@ -1,7 +1,6 @@
import _ from "lodash";
import React from "react";
import log from "loglevel";
import * as Sentry from "@sentry/react";
import type { WidgetState } from "widgets/BaseWidget";
import { EventType } from "constants/AppsmithActionConstants/ActionConstants";
@ -30,6 +29,7 @@ import type { CurrencyInputWidgetProps } from "./types";
import { WDSBaseInputWidget } from "widgets/wds/WDSBaseInputWidget";
import { getCountryCodeFromCurrencyCode, validateInput } from "./helpers";
import type { KeyDownEvent } from "widgets/wds/WDSBaseInputWidget/component/types";
import captureException from "instrumentation/sendFaroErrors";
class WDSCurrencyInputWidget extends WDSBaseInputWidget<
CurrencyInputWidgetProps,
@ -189,7 +189,7 @@ class WDSCurrencyInputWidget extends WDSBaseInputWidget<
} catch (e) {
formattedValue = value;
log.error(e);
Sentry.captureException(e);
captureException(e, { errorName: "WDSCurrencyInputWidget" });
}
this.props.updateWidgetMetaProperty("text", String(formattedValue));
@ -248,7 +248,7 @@ class WDSCurrencyInputWidget extends WDSBaseInputWidget<
}
} catch (e) {
log.error(e);
Sentry.captureException(e);
captureException(e, { errorName: "WDSCurrencyInputWidget" });
this.props.updateWidgetMetaProperty("text", this.props.text);
}
@ -311,7 +311,7 @@ class WDSCurrencyInputWidget extends WDSBaseInputWidget<
this.props.updateWidgetMetaProperty("text", formattedValue);
} catch (e) {
log.error(e);
Sentry.captureException(e);
captureException(e, { errorName: "WDSCurrencyInputWidget" });
}
}
}

View File

@ -1,7 +1,6 @@
import React from "react";
import log from "loglevel";
import merge from "lodash/merge";
import * as Sentry from "@sentry/react";
import { klonaRegularWithTelemetry, mergeWidgetConfig } from "utils/helpers";
import type { CountryCode } from "libphonenumber-js";
import type { WidgetState } from "widgets/BaseWidget";
@ -21,6 +20,7 @@ import * as config from "../config";
import { PhoneInputComponent } from "../component";
import type { PhoneInputWidgetProps } from "./types";
import { getCountryCode, validateInput } from "./helpers";
import captureException from "instrumentation/sendFaroErrors";
class WDSPhoneInputWidget extends WDSBaseInputWidget<
PhoneInputWidgetProps,
@ -163,7 +163,7 @@ class WDSPhoneInputWidget extends WDSBaseInputWidget<
this.props.updateWidgetMetaProperty("text", formattedValue);
} catch (e) {
log.error(e);
Sentry.captureException(e);
captureException(e, { errorName: "WDSPhoneInputWidget" });
}
}
}

View File

@ -12,8 +12,8 @@ import { get, isEmpty, toPath } from "lodash";
import { APP_MODE } from "entities/App";
import { isAction } from "ee/workers/Evaluation/evaluationUtils";
import log from "loglevel";
import * as Sentry from "@sentry/react";
import { getMemberExpressionObjectFromProperty } from "@shared/ast";
import captureException from "instrumentation/sendFaroErrors";
interface ErrorMetaData {
userScript: string;
@ -224,7 +224,7 @@ export function convertAllDataTypesToString(e: any) {
return JSON.stringify(e);
} catch (error) {
log.debug(error);
Sentry.captureException(error);
captureException(error, { errorName: "ErrorModifier_StringifyError" });
}
}
}