diff --git a/frontend/libs/console/legacy-ce/src/lib/components/Services/ApiExplorer/OneGraphExplorer/utils.js b/frontend/libs/console/legacy-ce/src/lib/components/Services/ApiExplorer/OneGraphExplorer/utils.js index 361077e1d8e..7169f220a5f 100644 --- a/frontend/libs/console/legacy-ce/src/lib/components/Services/ApiExplorer/OneGraphExplorer/utils.js +++ b/frontend/libs/console/legacy-ce/src/lib/components/Services/ApiExplorer/OneGraphExplorer/utils.js @@ -64,7 +64,6 @@ export const clickRunQueryButton = () => { 'Could not find run query button in the DOM (.execute-button)' ); programmaticallyTraceError(error); - console.warn(error); } }; diff --git a/frontend/libs/console/legacy-ce/src/lib/components/Services/Data/DataSources/CreateDataSource/Neon/useCreateHasuraCloudDatasource.ts b/frontend/libs/console/legacy-ce/src/lib/components/Services/Data/DataSources/CreateDataSource/Neon/useCreateHasuraCloudDatasource.ts index 61f5b244d63..6f9f3abd6db 100644 --- a/frontend/libs/console/legacy-ce/src/lib/components/Services/Data/DataSources/CreateDataSource/Neon/useCreateHasuraCloudDatasource.ts +++ b/frontend/libs/console/legacy-ce/src/lib/components/Services/Data/DataSources/CreateDataSource/Neon/useCreateHasuraCloudDatasource.ts @@ -164,13 +164,10 @@ export function useCreateHasuraCloudDatasource( setState(prevState => { if (prevState.status === 'adding-env-var') { // this is an unexpected error; so we need alerts about this - programmaticallyTraceError( - new Error('Failed creating env vars in Hasura'), - { - sourceError: error, - errorMessage: error.message ?? '', - } - ); + programmaticallyTraceError({ + error: 'Failed creating env vars in Hasura', + cause: error, + }); return { status: 'adding-env-var-failed', payload: { dbUrl }, @@ -178,13 +175,10 @@ export function useCreateHasuraCloudDatasource( // if adding data-source fails unexpectedly, set the error state } else if (prevState.status === 'adding-data-source') { // this is an unexpected error; so we need alerts about this - programmaticallyTraceError( - new Error('Failed adding created data source in Hasura'), - { - sourceError: error, - errorMessage: error.message ?? '', - } - ); + programmaticallyTraceError({ + error: 'Failed adding created data source in Hasura', + cause: error, + }); return { status: 'adding-data-source-failed', diff --git a/frontend/libs/console/legacy-ce/src/lib/features/Analytics/components/Analytics.tsx b/frontend/libs/console/legacy-ce/src/lib/features/Analytics/components/Analytics.tsx index 5c734e8c5c4..c2863036671 100644 --- a/frontend/libs/console/legacy-ce/src/lib/features/Analytics/components/Analytics.tsx +++ b/frontend/libs/console/legacy-ce/src/lib/features/Analytics/components/Analytics.tsx @@ -134,7 +134,6 @@ export function Analytics(props: AnalyticsProps) { const overrideError = new Error( `All the following attributes will be overridden: ${overrideHtmlAttributes} for the element with name "${name}"` ); - console.error(overrideError); programmaticallyTraceError(overrideError); } } diff --git a/frontend/libs/console/legacy-ce/src/lib/features/Analytics/core/programmaticallyTraceError.ts b/frontend/libs/console/legacy-ce/src/lib/features/Analytics/core/programmaticallyTraceError.ts index 61959fb2548..8a69739c2b5 100644 --- a/frontend/libs/console/legacy-ce/src/lib/features/Analytics/core/programmaticallyTraceError.ts +++ b/frontend/libs/console/legacy-ce/src/lib/features/Analytics/core/programmaticallyTraceError.ts @@ -1,13 +1,113 @@ -import type { ExceptionContext } from './sentry/captureException'; +import * as Sentry from '@sentry/react'; import { captureException } from './sentry/captureException'; +type HumanReadableString = string; + +type Options = { + level?: 'error' | 'warning'; + /** + * Logging to the Console has a specific purpose: Sentry is NOT enabled for every type of Console + * (at the time of writing, Sentry is enabled only for the Cloud Console). Logging to the browser's + * console allows the eventual customers that is trying to understand the root cause of a problem, + * to report what they see in the browser's console in the issue they are going to open. + */ + logToConsole?: boolean; +}; + +type HumanReadableStringWithErrorCauseAndOptions = Options & { + error: HumanReadableString; + cause?: Error; +}; +type ErrorWithOptions = Options & { + error: Error; +}; + +const noop = () => {}; + /** * Programmatically trace a caught error. + * + * @example Simplest usage: pass just a string. + * programmaticallyTraceError('Something went wrong when updating the metadata') + * + * @example Simplest usage: pass the error you receive. + * catch (error) { + * programmaticallyTraceError(error) + * } + * + * @example Pass a human-readable message, but also the causing error. + * catch (error) { + * programmaticallyTraceError({ error: 'Something went wrong when updating the metadata', cause: error }) + * } */ export function programmaticallyTraceError( - error: Error, - exceptionContext: ExceptionContext = {}, - level: 'error' | 'warning' = 'error' + errorOrErrorWithOptions: + | HumanReadableString + | Error + | HumanReadableStringWithErrorCauseAndOptions + | ErrorWithOptions ) { - captureException(error, exceptionContext, level); + // -------------------------------------------------- + // SIMPLE USAGE + // -------------------------------------------------- + + // If you pass a string, it's converted to an error and passed to Sentry. + if (typeof errorOrErrorWithOptions === 'string') { + captureException(new Error(errorOrErrorWithOptions)); + + return; + } + + // If you pass an error, it's passed to Sentry as is. + if (errorOrErrorWithOptions instanceof Error) { + captureException(errorOrErrorWithOptions); + + return; + } + + // -------------------------------------------------- + // OPTIONS-RICH USAGE + // -------------------------------------------------- + + const { + error, + level = 'error', + logToConsole = true, + } = errorOrErrorWithOptions; + + const consoleLog = logToConsole + ? level === 'warning' + ? console.warn + : console.error + : noop; + + // If you pass a cause, the cause itself is the original error and IT IS the error that will be + // tracked to Sentry. Instead, the passed human-friendly string will be added as a breadcrumb that + // you can see in Sentry right after the error. + if ('cause' in errorOrErrorWithOptions && !!errorOrErrorWithOptions.cause) { + const { cause } = errorOrErrorWithOptions; + + const message = typeof error === 'string' ? error : error.message; + + Sentry.addBreadcrumb({ level, message }); + captureException(cause); + consoleLog(message); + consoleLog(cause); + + return; + } + + // The string will be converted to an error and tracked in Sentry + if (typeof error === 'string') { + const errorToLog = new Error(error); + + captureException(errorToLog, level); + consoleLog(errorToLog); + + return; + } + + // The error is tracked in Sentry + captureException(error, level); + consoleLog(error); } diff --git a/frontend/libs/console/legacy-ce/src/lib/features/Analytics/core/sentry/captureException.ts b/frontend/libs/console/legacy-ce/src/lib/features/Analytics/core/sentry/captureException.ts index 1670edf06e4..ab98264ca9b 100644 --- a/frontend/libs/console/legacy-ce/src/lib/features/Analytics/core/sentry/captureException.ts +++ b/frontend/libs/console/legacy-ce/src/lib/features/Analytics/core/sentry/captureException.ts @@ -1,23 +1,15 @@ import * as Sentry from '@sentry/react'; -export type ExceptionContext = { - sourceError?: Error; - errorMessage?: string; -}; - /** - * This function allows us to capture caught exceptions that we want the engineering team to be - * alerted about + * A simple wrapper around Sentry's captureException. * * @see https://docs.sentry.io/platforms/javascript/enriching-events/context/ */ export function captureException( error: Error, - exceptionContext: ExceptionContext = {}, level: 'error' | 'warning' = 'error' ) { Sentry.captureException(error, { level, - contexts: { debug: exceptionContext }, }); } diff --git a/frontend/libs/console/legacy-ce/src/lib/features/Analytics/core/sentry/startSentryTracing.ts b/frontend/libs/console/legacy-ce/src/lib/features/Analytics/core/sentry/startSentryTracing.ts index d897a11a344..df37f520e13 100644 --- a/frontend/libs/console/legacy-ce/src/lib/features/Analytics/core/sentry/startSentryTracing.ts +++ b/frontend/libs/console/legacy-ce/src/lib/features/Analytics/core/sentry/startSentryTracing.ts @@ -56,6 +56,12 @@ export function startSentryTracing(globalVars: Globals, envVars: EnvVars) { // sensitive data dom: false, }), + + // ATTENTION: functions like programmaticallyTraceError could internally log errors to the + // browser's console, causing an infinite loop! + // new CaptureConsoleIntegration({ + // levels: ['error'], + // }), ], // Allow grouping logs by environment diff --git a/frontend/libs/console/legacy-ce/src/lib/features/CloudOnboarding/OnboardingWizard/components/QueryScreen/TemplateSummary.tsx b/frontend/libs/console/legacy-ce/src/lib/features/CloudOnboarding/OnboardingWizard/components/QueryScreen/TemplateSummary.tsx index 6b5b42b4ac5..caabab644ba 100644 --- a/frontend/libs/console/legacy-ce/src/lib/features/CloudOnboarding/OnboardingWizard/components/QueryScreen/TemplateSummary.tsx +++ b/frontend/libs/console/legacy-ce/src/lib/features/CloudOnboarding/OnboardingWizard/components/QueryScreen/TemplateSummary.tsx @@ -62,13 +62,10 @@ export function TemplateSummary(props: Props) { staleTime, onError: (e: any) => { // this is unexpected; so get alerted - programmaticallyTraceError( - new Error('failed to get a sample query in template summary'), - { - sourceError: e, - errorMessage: e.message ?? '', - } - ); + programmaticallyTraceError({ + error: 'failed to get a sample query in template summary', + cause: e, + }); }, }); diff --git a/frontend/libs/console/legacy-ce/src/lib/features/CloudOnboarding/OnboardingWizard/utils.ts b/frontend/libs/console/legacy-ce/src/lib/features/CloudOnboarding/OnboardingWizard/utils.ts index 3c7e7cc787f..44884d95c8e 100644 --- a/frontend/libs/console/legacy-ce/src/lib/features/CloudOnboarding/OnboardingWizard/utils.ts +++ b/frontend/libs/console/legacy-ce/src/lib/features/CloudOnboarding/OnboardingWizard/utils.ts @@ -118,7 +118,6 @@ export const emitOnboardingEvent = (variables: Record) => { variables, cloudHeaders ).catch(error => { - console.error(error); programmaticallyTraceError(error); }); }; diff --git a/frontend/libs/console/legacy-ce/src/lib/features/CloudOnboarding/OneClickDeployment/components/WorkflowProgress/WorkflowProgress.tsx b/frontend/libs/console/legacy-ce/src/lib/features/CloudOnboarding/OneClickDeployment/components/WorkflowProgress/WorkflowProgress.tsx index 5da95e87a64..7d0d098c8bd 100644 --- a/frontend/libs/console/legacy-ce/src/lib/features/CloudOnboarding/OneClickDeployment/components/WorkflowProgress/WorkflowProgress.tsx +++ b/frontend/libs/console/legacy-ce/src/lib/features/CloudOnboarding/OneClickDeployment/components/WorkflowProgress/WorkflowProgress.tsx @@ -159,14 +159,10 @@ export function WorkflowProgress(props: WorkflowProgressProps) { ); }, error => { - programmaticallyTraceError( - new Error('failed subscribing to one click deployment status'), - { - errorMessage: error.message, - sourceError: error, - }, - 'error' - ); + programmaticallyTraceError({ + error: 'failed subscribing to one click deployment status', + cause: error, + }); } ); return () => { diff --git a/frontend/libs/console/legacy-ce/src/lib/features/OpenTelemetry/OpenTelemetryProvider/hooks/useOnSetOpenTelemetryError.ts b/frontend/libs/console/legacy-ce/src/lib/features/OpenTelemetry/OpenTelemetryProvider/hooks/useOnSetOpenTelemetryError.ts index 66d74acedd0..54e21d2086c 100644 --- a/frontend/libs/console/legacy-ce/src/lib/features/OpenTelemetry/OpenTelemetryProvider/hooks/useOnSetOpenTelemetryError.ts +++ b/frontend/libs/console/legacy-ce/src/lib/features/OpenTelemetry/OpenTelemetryProvider/hooks/useOnSetOpenTelemetryError.ts @@ -80,12 +80,9 @@ export function useOnSetOpenTelemetryError( } ); - programmaticallyTraceError( - new Error( - 'OpenTelemetry set_opentelemetry_config error not parsed', - // @ts-expect-error This error will automatically disappear with Nx that targets new browsers by default - { cause: err } - ) - ); + programmaticallyTraceError({ + error: 'OpenTelemetry set_opentelemetry_config error not parsed', + cause: err instanceof Error ? err : undefined, + }); }; } diff --git a/frontend/libs/console/legacy-ce/src/lib/features/OpenTelemetry/OpenTelemetryProvider/hooks/useTrackTypeMisalignments.ts b/frontend/libs/console/legacy-ce/src/lib/features/OpenTelemetry/OpenTelemetryProvider/hooks/useTrackTypeMisalignments.ts index bcc0ae97216..a3b87c61119 100644 --- a/frontend/libs/console/legacy-ce/src/lib/features/OpenTelemetry/OpenTelemetryProvider/hooks/useTrackTypeMisalignments.ts +++ b/frontend/libs/console/legacy-ce/src/lib/features/OpenTelemetry/OpenTelemetryProvider/hooks/useTrackTypeMisalignments.ts @@ -58,11 +58,8 @@ export function useTrackTypeMisalignments( } ); - programmaticallyTraceError( - new Error( - 'OpenTelemetry metadata not parsed', - // @ts-expect-error This error will automatically disappear with Nx that targets new browsers by default - { cause: result.error } - ) - ); + programmaticallyTraceError({ + error: 'OpenTelemetry metadata not parsed', + cause: result.error, + }); }