diff --git a/src/platform/packages/shared/shared-ux/error_boundary/lib/error_boundary_labels.ts b/src/platform/packages/shared/shared-ux/error_boundary/lib/error_boundary_labels.ts new file mode 100644 index 0000000000000..1feb79077f130 --- /dev/null +++ b/src/platform/packages/shared/shared-ux/error_boundary/lib/error_boundary_labels.ts @@ -0,0 +1,16 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the "Elastic License + * 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side + * Public License v 1"; you may not use this file except in compliance with, at + * your election, the "Elastic License 2.0", the "GNU Affero General Public + * License v3.0 only", or the "Server Side Public License, v 1". + */ + +export const getErrorBoundaryLabels = ( + errorType: 'PageFatalReactError' | 'SectionFatalReactError' +) => { + return { + errorType, + }; +}; diff --git a/src/platform/packages/shared/shared-ux/error_boundary/lib/index.ts b/src/platform/packages/shared/shared-ux/error_boundary/lib/index.ts index cba7a7385da6c..bc2b9bd514ca8 100644 --- a/src/platform/packages/shared/shared-ux/error_boundary/lib/index.ts +++ b/src/platform/packages/shared/shared-ux/error_boundary/lib/index.ts @@ -7,4 +7,4 @@ * License v3.0 only", or the "Server Side Public License, v 1". */ -export { mutateError } from './mutate_error'; +export { getErrorBoundaryLabels } from './error_boundary_labels'; diff --git a/src/platform/packages/shared/shared-ux/error_boundary/lib/mutate_error.ts b/src/platform/packages/shared/shared-ux/error_boundary/lib/mutate_error.ts deleted file mode 100644 index fbf9056779a8f..0000000000000 --- a/src/platform/packages/shared/shared-ux/error_boundary/lib/mutate_error.ts +++ /dev/null @@ -1,22 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the "Elastic License - * 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side - * Public License v 1"; you may not use this file except in compliance with, at - * your election, the "Elastic License 2.0", the "GNU Affero General Public - * License v3.0 only", or the "Server Side Public License, v 1". - */ - -import { REACT_FATAL_ERROR_EVENT_TYPE } from './telemetry_events'; - -/** - * Adds ability to use APM to filter for errors caught by this error boundary. - * The Error is mutated rather than copied, to keep the original prototype so that it can be captured in APM without side effects. - */ -export function mutateError(error: Error) { - const customError: Error & { react_error_type?: string; original_name?: string } = error; - customError.react_error_type = REACT_FATAL_ERROR_EVENT_TYPE; - customError.original_name = error.name; - customError.name = 'FatalReactError'; - return customError; -} diff --git a/src/platform/packages/shared/shared-ux/error_boundary/src/services/error_boundary_services.test.tsx b/src/platform/packages/shared/shared-ux/error_boundary/src/services/error_boundary_services.test.tsx index def0d3c7a9ec2..dd7589fb83738 100644 --- a/src/platform/packages/shared/shared-ux/error_boundary/src/services/error_boundary_services.test.tsx +++ b/src/platform/packages/shared/shared-ux/error_boundary/src/services/error_boundary_services.test.tsx @@ -37,7 +37,7 @@ describe('', () => { expect(reportEventSpy).toBeCalledWith('fatal-error-react', { component_name: 'BadComponent', component_stack: expect.any(String), - error_message: 'FatalReactError: This is an error to show the test user!', + error_message: 'Error: This is an error to show the test user!', error_stack: expect.any(String), }); }); @@ -66,7 +66,7 @@ describe('', () => { expect(reportEventSpy1).toBeCalledWith('fatal-error-react', { component_name: 'BadComponent', component_stack: expect.any(String), - error_message: 'FatalReactError: This is an error to show the test user!', + error_message: 'Error: This is an error to show the test user!', error_stack: expect.any(String), }); }); diff --git a/src/platform/packages/shared/shared-ux/error_boundary/src/services/error_service.ts b/src/platform/packages/shared/shared-ux/error_boundary/src/services/error_service.ts index a211c6450f17f..8e43e5a6c56d3 100644 --- a/src/platform/packages/shared/shared-ux/error_boundary/src/services/error_service.ts +++ b/src/platform/packages/shared/shared-ux/error_boundary/src/services/error_service.ts @@ -42,9 +42,7 @@ export class KibanaErrorService { * or treated with "danger" coloring and include a detailed error message. */ private getIsFatal(error: Error) { - const customError: Error & { react_error_type?: string; original_name?: string } = error; - const errorName = customError.original_name ?? customError.name; - const isChunkLoadError = MATCH_CHUNK_LOADERROR.test(errorName); + const isChunkLoadError = MATCH_CHUNK_LOADERROR.test(error.name); return !isChunkLoadError; // "ChunkLoadError" is recoverable by refreshing the page } diff --git a/src/platform/packages/shared/shared-ux/error_boundary/src/ui/error_boundary.test.tsx b/src/platform/packages/shared/shared-ux/error_boundary/src/ui/error_boundary.test.tsx index e5d7c8043e2f8..5cffd20950e8f 100644 --- a/src/platform/packages/shared/shared-ux/error_boundary/src/ui/error_boundary.test.tsx +++ b/src/platform/packages/shared/shared-ux/error_boundary/src/ui/error_boundary.test.tsx @@ -96,7 +96,7 @@ describe('', () => { expect(mockDeps.analytics.reportEvent.mock.calls[0][0]).toBe('fatal-error-react'); expect(mockDeps.analytics.reportEvent.mock.calls[0][1]).toMatchObject({ component_name: 'BadComponent', - error_message: 'FatalReactError: This is an error to show the test user!', + error_message: 'Error: This is an error to show the test user!', }); }); @@ -118,7 +118,7 @@ describe('', () => { ).toBe(true); expect( mockDeps.analytics.reportEvent.mock.calls[0][1].error_stack.startsWith( - 'FatalReactError: This is an error to show the test user!' + 'Error: This is an error to show the test user!' ) ).toBe(true); }); @@ -133,15 +133,8 @@ describe('', () => { expect(apm.captureError).toHaveBeenCalledTimes(1); expect(apm.captureError).toHaveBeenCalledWith( - new Error('This is an error to show the test user!') - ); - expect(Object.keys((apm.captureError as jest.Mock).mock.calls[0][0])).toEqual([ - 'react_error_type', - 'original_name', - 'name', - ]); - expect((apm.captureError as jest.Mock).mock.calls[0][0].react_error_type).toEqual( - 'fatal-error-react' + new Error('This is an error to show the test user!'), + { labels: { errorType: 'PageFatalReactError' } } ); }); }); diff --git a/src/platform/packages/shared/shared-ux/error_boundary/src/ui/error_boundary.tsx b/src/platform/packages/shared/shared-ux/error_boundary/src/ui/error_boundary.tsx index 12911239ac888..bdb931ee66af9 100644 --- a/src/platform/packages/shared/shared-ux/error_boundary/src/ui/error_boundary.tsx +++ b/src/platform/packages/shared/shared-ux/error_boundary/src/ui/error_boundary.tsx @@ -10,7 +10,7 @@ import { apm } from '@elastic/apm-rum'; import React from 'react'; -import { mutateError } from '../../lib'; +import { getErrorBoundaryLabels } from '../../lib'; import type { KibanaErrorBoundaryServices } from '../../types'; import { useErrorBoundary } from '../services'; import { FatalPrompt, RecoverablePrompt } from './message_components'; @@ -41,10 +41,11 @@ class ErrorBoundaryInternal extends React.Component< } componentDidCatch(error: Error, errorInfo: React.ErrorInfo) { - const customError = mutateError(error); - apm.captureError(customError); + apm.captureError(error, { + labels: getErrorBoundaryLabels('PageFatalReactError'), + }); console.error('Error caught by Kibana React Error Boundary'); // eslint-disable-line no-console - console.error(customError); // eslint-disable-line no-console + console.error(error); // eslint-disable-line no-console const { name, isFatal } = this.props.services.errorService.registerError(error, errorInfo); this.setState(() => { diff --git a/src/platform/packages/shared/shared-ux/error_boundary/src/ui/section_error_boundary.test.tsx b/src/platform/packages/shared/shared-ux/error_boundary/src/ui/section_error_boundary.test.tsx index e50026ea22375..5aed2ef73f317 100644 --- a/src/platform/packages/shared/shared-ux/error_boundary/src/ui/section_error_boundary.test.tsx +++ b/src/platform/packages/shared/shared-ux/error_boundary/src/ui/section_error_boundary.test.tsx @@ -92,7 +92,7 @@ describe('', () => { expect(mockDeps.analytics.reportEvent.mock.calls[0][0]).toBe('fatal-error-react'); expect(mockDeps.analytics.reportEvent.mock.calls[0][1]).toMatchObject({ component_name: 'BadComponent', - error_message: 'FatalReactError: This is an error to show the test user!', + error_message: 'Error: This is an error to show the test user!', }); }); @@ -114,7 +114,7 @@ describe('', () => { ).toBe(true); expect( mockDeps.analytics.reportEvent.mock.calls[0][1].error_stack.startsWith( - 'FatalReactError: This is an error to show the test user!' + 'Error: This is an error to show the test user!' ) ).toBe(true); }); @@ -129,15 +129,8 @@ describe('', () => { expect(apm.captureError).toHaveBeenCalledTimes(1); expect(apm.captureError).toHaveBeenCalledWith( - new Error('This is an error to show the test user!') - ); - expect(Object.keys((apm.captureError as jest.Mock).mock.calls[0][0])).toEqual([ - 'react_error_type', - 'original_name', - 'name', - ]); - expect((apm.captureError as jest.Mock).mock.calls[0][0].react_error_type).toEqual( - 'fatal-error-react' + new Error('This is an error to show the test user!'), + { labels: { errorType: 'SectionFatalReactError' } } ); }); }); diff --git a/src/platform/packages/shared/shared-ux/error_boundary/src/ui/section_error_boundary.tsx b/src/platform/packages/shared/shared-ux/error_boundary/src/ui/section_error_boundary.tsx index baedde1a87307..e418ad7fc17fa 100644 --- a/src/platform/packages/shared/shared-ux/error_boundary/src/ui/section_error_boundary.tsx +++ b/src/platform/packages/shared/shared-ux/error_boundary/src/ui/section_error_boundary.tsx @@ -7,10 +7,10 @@ * License v3.0 only", or the "Server Side Public License, v 1". */ -import { apm } from '@elastic/apm-rum'; import React from 'react'; +import { apm } from '@elastic/apm-rum'; -import { mutateError } from '../../lib'; +import { getErrorBoundaryLabels } from '../../lib'; import type { KibanaErrorBoundaryServices } from '../../types'; import { useErrorBoundary } from '../services'; import { SectionFatalPrompt, SectionRecoverablePrompt } from './message_components'; @@ -65,10 +65,11 @@ class SectionErrorBoundaryInternal extends React.Component< } componentDidCatch(error: Error, errorInfo: React.ErrorInfo): void { - const customError = mutateError(error); - apm.captureError(customError); + apm.captureError(error, { + labels: getErrorBoundaryLabels('SectionFatalReactError'), + }); console.error('Error caught by Kibana React Error Boundary'); // eslint-disable-line no-console - console.error(customError); // eslint-disable-line no-console + console.error(error); // eslint-disable-line no-console const { name, isFatal } = this.props.services.errorService.registerError(error, errorInfo); this.setState({ error, errorInfo, componentName: name, isFatal });