From 184a20f908e48cb87473849c69620d0a05df8af0 Mon Sep 17 00:00:00 2001 From: Thomas Lebeau Date: Fri, 26 Apr 2024 14:48:03 +0200 Subject: [PATCH 01/11] =?UTF-8?q?=E2=9C=A8=20[RUM-4013]=20add=20handling?= =?UTF-8?q?=20stack=20in=20beforeSend=20context?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../core/src/browser/fetchObservable.spec.ts | 11 ++++-- packages/core/src/browser/fetchObservable.ts | 4 ++- .../core/src/browser/xhrObservable.spec.ts | 13 +++++++ packages/core/src/browser/xhrObservable.ts | 4 ++- .../src/domain/console/consoleObservable.ts | 2 +- .../core/src/tools/experimentalFeatures.ts | 1 + .../core/src/tools/instrumentMethod.spec.ts | 1 + packages/core/src/tools/instrumentMethod.ts | 4 +++ .../tools/stackTrace/handlingStack.spec.ts | 4 +-- .../src/tools/stackTrace/handlingStack.ts | 5 ++- packages/rum-core/src/boot/rumPublicApi.ts | 2 +- .../src/domain/requestCollection.spec.ts | 13 +++++-- .../rum-core/src/domain/requestCollection.ts | 3 ++ .../resource/resourceCollection.spec.ts | 36 ++++++++++++++++++- .../src/domain/resource/resourceCollection.ts | 8 ++++- packages/rum-core/src/domainContext.types.ts | 2 ++ 16 files changed, 98 insertions(+), 15 deletions(-) diff --git a/packages/core/src/browser/fetchObservable.spec.ts b/packages/core/src/browser/fetchObservable.spec.ts index c36bd5b834..796bf1bc2e 100644 --- a/packages/core/src/browser/fetchObservable.spec.ts +++ b/packages/core/src/browser/fetchObservable.spec.ts @@ -1,10 +1,12 @@ -import type { FetchStub, FetchStubManager, FetchStubPromise } from '../../test' +import type { FetchStubManager, FetchStubPromise } from '../../test' import { stubFetch } from '../../test' import { isIE } from '../tools/utils/browserDetection' import type { Subscription } from '../tools/observable' import type { FetchResolveContext, FetchContext } from './fetchObservable' import { initFetchObservable } from './fetchObservable' +const HANDLING_STACK_REGEX = /^Error: \n\s+at fetchStub @/ + describe('fetch proxy', () => { const FAKE_URL = 'http://fake-url/' const FAKE_RELATIVE_URL = '/fake-path' @@ -29,7 +31,9 @@ describe('fetch proxy', () => { requests.push(context) } }) - fetchStub = window.fetch as FetchStub + fetchStub = function fetchStub(...args): FetchStubPromise { + return window.fetch(...args) as FetchStubPromise + } }) afterEach(() => { @@ -47,6 +51,7 @@ describe('fetch proxy', () => { expect(request.url).toEqual(FAKE_URL) expect(request.status).toEqual(500) expect(request.isAborted).toBe(false) + expect(request.handlingStack).toMatch(HANDLING_STACK_REGEX) done() }) }) @@ -61,6 +66,7 @@ describe('fetch proxy', () => { expect(request.status).toEqual(0) expect(request.isAborted).toBe(false) expect(request.error).toEqual(new Error('fetch error')) + expect(request.handlingStack).toMatch(HANDLING_STACK_REGEX) done() }) }) @@ -75,6 +81,7 @@ describe('fetch proxy', () => { expect(request.status).toEqual(0) expect(request.isAborted).toBe(true) expect(request.error).toEqual(new DOMException('The user aborted a request', 'AbortError')) + expect(request.handlingStack).toMatch(HANDLING_STACK_REGEX) done() }) }) diff --git a/packages/core/src/browser/fetchObservable.ts b/packages/core/src/browser/fetchObservable.ts index 9cf90201e3..fd16a803ab 100644 --- a/packages/core/src/browser/fetchObservable.ts +++ b/packages/core/src/browser/fetchObservable.ts @@ -12,6 +12,7 @@ interface FetchContextBase { input: unknown init?: RequestInit url: string + handlingStack: string } export interface FetchStartContext extends FetchContextBase { @@ -51,7 +52,7 @@ function createFetchObservable() { } function beforeSend( - { parameters, onPostCall }: InstrumentedMethodCall, + { parameters, onPostCall, handlingStack }: InstrumentedMethodCall, observable: Observable ) { const [input, init] = parameters @@ -72,6 +73,7 @@ function beforeSend( method, startClocks, url, + handlingStack, } observable.notify(context) diff --git a/packages/core/src/browser/xhrObservable.spec.ts b/packages/core/src/browser/xhrObservable.spec.ts index 1da22942bd..f0d5e223ef 100644 --- a/packages/core/src/browser/xhrObservable.spec.ts +++ b/packages/core/src/browser/xhrObservable.spec.ts @@ -5,6 +5,8 @@ import type { Subscription } from '../tools/observable' import type { XhrCompleteContext, XhrContext } from './xhrObservable' import { initXhrObservable } from './xhrObservable' +const HANDLING_STACK_REGEX = /^Error: \n {2}at setup @/ + describe('xhr observable', () => { let requestsTrackingSubscription: Subscription let contextEditionSubscription: Subscription | undefined @@ -51,6 +53,7 @@ describe('xhr observable', () => { expect(request.status).toBe(200) expect(request.isAborted).toBe(false) expect(request.duration).toEqual(jasmine.any(Number)) + expect(request.handlingStack).toMatch(HANDLING_STACK_REGEX) done() }, }) @@ -66,6 +69,7 @@ describe('xhr observable', () => { onComplete() { const request = requests[0] expect(request.method).toBe('GET') + expect(request.handlingStack).toMatch(HANDLING_STACK_REGEX) done() }, }) @@ -85,6 +89,7 @@ describe('xhr observable', () => { expect(request.status).toBe(404) expect(request.isAborted).toBe(false) expect(request.duration).toEqual(jasmine.any(Number)) + expect(request.handlingStack).toMatch(HANDLING_STACK_REGEX) done() }, }) @@ -104,6 +109,7 @@ describe('xhr observable', () => { expect(request.status).toBe(500) expect(request.isAborted).toBe(false) expect(request.duration).toEqual(jasmine.any(Number)) + expect(request.handlingStack).toMatch(HANDLING_STACK_REGEX) done() }, }) @@ -123,6 +129,7 @@ describe('xhr observable', () => { expect(request.status).toBe(0) expect(request.isAborted).toBe(false) expect(request.duration).toEqual(jasmine.any(Number)) + expect(request.handlingStack).toMatch(HANDLING_STACK_REGEX) done() }, }) @@ -151,6 +158,7 @@ describe('xhr observable', () => { expect(request.isAborted).toBe(false) expect(xhr.status).toBe(0) expect(xhr.onreadystatechange).toHaveBeenCalledTimes(1) + expect(request.handlingStack).toMatch(HANDLING_STACK_REGEX) done() }, }) @@ -171,6 +179,7 @@ describe('xhr observable', () => { expect(request.duration).toEqual(jasmine.any(Number)) expect(request.isAborted).toBe(true) expect(xhr.status).toBe(0) + expect(request.handlingStack).toMatch(HANDLING_STACK_REGEX) done() }, }) @@ -192,6 +201,7 @@ describe('xhr observable', () => { expect(request.isAborted).toBe(false) expect(request.duration).toEqual(jasmine.any(Number)) expect(xhr.onreadystatechange).toHaveBeenCalled() + expect(request.handlingStack).toMatch(HANDLING_STACK_REGEX) done() }, }) @@ -213,6 +223,7 @@ describe('xhr observable', () => { expect(request.isAborted).toBe(false) expect(request.duration).toEqual(jasmine.any(Number)) expect(xhr.onreadystatechange).toHaveBeenCalled() + expect(request.handlingStack).toMatch(HANDLING_STACK_REGEX) done() }, }) @@ -284,6 +295,7 @@ describe('xhr observable', () => { expect(firstRequest.status).toBe(200) expect(firstRequest.isAborted).toBe(false) expect(firstRequest.duration).toEqual(jasmine.any(Number)) + expect(firstRequest.handlingStack).toMatch(HANDLING_STACK_REGEX) const secondRequest = requests[1] expect(secondRequest.method).toBe('GET') @@ -291,6 +303,7 @@ describe('xhr observable', () => { expect(secondRequest.status).toBe(400) expect(secondRequest.isAborted).toBe(false) expect(secondRequest.duration).toEqual(jasmine.any(Number)) + expect(secondRequest.handlingStack).toMatch(/^Error: \n {2}at StubXhr.onLoad @/) expect(xhr.onreadystatechange).toHaveBeenCalledTimes(2) expect(listeners.load.length).toBe(0) diff --git a/packages/core/src/browser/xhrObservable.ts b/packages/core/src/browser/xhrObservable.ts index f97433abbc..2462bfff32 100644 --- a/packages/core/src/browser/xhrObservable.ts +++ b/packages/core/src/browser/xhrObservable.ts @@ -19,6 +19,7 @@ export interface XhrStartContext extends Omit { startClocks: ClocksState isAborted: boolean xhr: XMLHttpRequest + handlingStack: string } export interface XhrCompleteContext extends Omit { @@ -66,7 +67,7 @@ function openXhr({ target: xhr, parameters: [method, url] }: InstrumentedMethodC } function sendXhr( - { target: xhr }: InstrumentedMethodCall, + { target: xhr, handlingStack }: InstrumentedMethodCall, configuration: Configuration, observable: Observable ) { @@ -80,6 +81,7 @@ function sendXhr( startContext.startClocks = clocksNow() startContext.isAborted = false startContext.xhr = xhr + startContext.handlingStack = handlingStack let hasBeenReported = false diff --git a/packages/core/src/domain/console/consoleObservable.ts b/packages/core/src/domain/console/consoleObservable.ts index 6bde7abb57..4da1ffcb09 100644 --- a/packages/core/src/domain/console/consoleObservable.ts +++ b/packages/core/src/domain/console/consoleObservable.ts @@ -41,7 +41,7 @@ function createConsoleObservable(api: ConsoleApiName) { globalConsole[api] = (...params: unknown[]) => { originalConsoleApi.apply(console, params) - const handlingStack = createHandlingStack() + const handlingStack = createHandlingStack(2) callMonitored(() => { observable.notify(buildConsoleLog(params, api, handlingStack)) diff --git a/packages/core/src/tools/experimentalFeatures.ts b/packages/core/src/tools/experimentalFeatures.ts index 36482909f9..e4e04b2418 100644 --- a/packages/core/src/tools/experimentalFeatures.ts +++ b/packages/core/src/tools/experimentalFeatures.ts @@ -20,6 +20,7 @@ export enum ExperimentalFeature { WRITABLE_RESOURCE_GRAPHQL = 'writable_resource_graphql', CUSTOM_VITALS = 'custom_vitals', TOLERANT_RESOURCE_TIMINGS = 'tolerant_resource_timings', + MICRO_FRONTEND = 'micro_frontend', } const enabledExperimentalFeatures: Set = new Set() diff --git a/packages/core/src/tools/instrumentMethod.spec.ts b/packages/core/src/tools/instrumentMethod.spec.ts index e28469bf5f..0bf106a21d 100644 --- a/packages/core/src/tools/instrumentMethod.spec.ts +++ b/packages/core/src/tools/instrumentMethod.spec.ts @@ -59,6 +59,7 @@ describe('instrumentMethod', () => { target: object, parameters: jasmine.any(Object), onPostCall: jasmine.any(Function), + handlingStack: jasmine.stringMatching(/^Error: \n\s+at UserContext. @/), }) expect(instrumentationSpy.calls.mostRecent().args[0].parameters[0]).toBe(2) expect(instrumentationSpy.calls.mostRecent().args[0].parameters[1]).toBe(3) diff --git a/packages/core/src/tools/instrumentMethod.ts b/packages/core/src/tools/instrumentMethod.ts index d0e62d905e..7ba9d97908 100644 --- a/packages/core/src/tools/instrumentMethod.ts +++ b/packages/core/src/tools/instrumentMethod.ts @@ -2,6 +2,7 @@ import { setTimeout } from './timer' import { callMonitored } from './monitor' import { noop } from './utils/functionUtils' import { arrayFrom, startsWith } from './utils/polyfills' +import { createHandlingStack } from './stackTrace/handlingStack' /** * Object passed to the callback of an instrumented method call. See `instrumentMethod` for more @@ -25,6 +26,8 @@ export type InstrumentedMethodCall) => void + + handlingStack: string } type PostCallCallback = ( @@ -116,6 +119,7 @@ function createInstrumentedMethod { postCallCallback = callback }, + handlingStack: createHandlingStack(3), }, ]) diff --git a/packages/core/src/tools/stackTrace/handlingStack.spec.ts b/packages/core/src/tools/stackTrace/handlingStack.spec.ts index 6581def96a..f08583d6f7 100644 --- a/packages/core/src/tools/stackTrace/handlingStack.spec.ts +++ b/packages/core/src/tools/stackTrace/handlingStack.spec.ts @@ -3,7 +3,7 @@ import { createHandlingStack } from './handlingStack' describe('createHandlingStack', () => { let handlingStack: string function internalCall() { - handlingStack = createHandlingStack() + handlingStack = createHandlingStack(2) } function userCallTwo() { internalCall() @@ -15,6 +15,6 @@ describe('createHandlingStack', () => { it('should create handling stack trace without internal calls', () => { userCallOne() - expect(handlingStack).toMatch('Error: \n {2}at userCallTwo @ (.*)\n {2}at userCallOne @ (.*)') + expect(handlingStack).toMatch(/^Error: \n\s+at userCallTwo @ (.*)\n\s+at userCallOne @ (.*)/) }) }) diff --git a/packages/core/src/tools/stackTrace/handlingStack.ts b/packages/core/src/tools/stackTrace/handlingStack.ts index ea175c97c0..e7fc397238 100644 --- a/packages/core/src/tools/stackTrace/handlingStack.ts +++ b/packages/core/src/tools/stackTrace/handlingStack.ts @@ -9,14 +9,13 @@ import { computeStackTrace } from './computeStackTrace' * - Has to be called at the utmost position of the call stack. * - No monitored function should encapsulate it, that is why we need to use callMonitored inside it. */ -export function createHandlingStack(): string { +export function createHandlingStack(framesToSkip = 0): string { /** * Skip the two internal frames: * - SDK API (console.error, ...) * - this function * in order to keep only the user calls */ - const internalFramesToSkip = 2 const error = new Error() let formattedStack: string @@ -31,7 +30,7 @@ export function createHandlingStack(): string { callMonitored(() => { const stackTrace = computeStackTrace(error) - stackTrace.stack = stackTrace.stack.slice(internalFramesToSkip) + stackTrace.stack = stackTrace.stack.slice(framesToSkip) formattedStack = toStackTraceString(stackTrace) }) diff --git a/packages/rum-core/src/boot/rumPublicApi.ts b/packages/rum-core/src/boot/rumPublicApi.ts index 45619ab18b..1846db4108 100644 --- a/packages/rum-core/src/boot/rumPublicApi.ts +++ b/packages/rum-core/src/boot/rumPublicApi.ts @@ -234,7 +234,7 @@ export function makeRumPublicApi(startRumImpl: StartRum, recorderApi: RecorderAp }), addError: (error: unknown, context?: object) => { - const handlingStack = createHandlingStack() + const handlingStack = createHandlingStack(2) callMonitored(() => { strategy.addError({ error, // Do not sanitize error here, it is needed unserialized by computeRawError() diff --git a/packages/rum-core/src/domain/requestCollection.spec.ts b/packages/rum-core/src/domain/requestCollection.spec.ts index ecb46b6223..86625370a4 100644 --- a/packages/rum-core/src/domain/requestCollection.spec.ts +++ b/packages/rum-core/src/domain/requestCollection.spec.ts @@ -1,6 +1,6 @@ import type { Payload } from '@datadog/browser-core' import { isIE, RequestType } from '@datadog/browser-core' -import type { FetchStub, FetchStubManager } from '@datadog/browser-core/test' +import type { FetchStub, FetchStubManager, FetchStubPromise } from '@datadog/browser-core/test' import { SPEC_ENDPOINTS, stubFetch, stubXhr, withXhr } from '@datadog/browser-core/test' import type { RumConfiguration } from './configuration' import { validateAndBuildRumConfiguration } from './configuration' @@ -20,6 +20,7 @@ describe('collect fetch', () => { let startSpy: jasmine.Spy<(requestStartEvent: RequestStartEvent) => void> let completeSpy: jasmine.Spy<(requestCompleteEvent: RequestCompleteEvent) => void> let stopFetchTracking: () => void + const HANDLING_STACK_REGEX = /^Error: \n\s+at fetchStub @/ beforeEach(() => { if (isIE()) { @@ -46,7 +47,9 @@ describe('collect fetch', () => { } ;({ stop: stopFetchTracking } = trackFetch(lifeCycle, configuration, tracerStub as Tracer)) - fetchStub = window.fetch as FetchStub + fetchStub = function fetchStub(...args): FetchStubPromise { + return window.fetch(...args) as FetchStubPromise + } window.onunhandledrejection = (ev: PromiseRejectionEvent) => { throw new Error(`unhandled rejected promise \n ${ev.reason as string}`) } @@ -77,6 +80,7 @@ describe('collect fetch', () => { expect(request.method).toEqual('GET') expect(request.url).toEqual(FAKE_URL) expect(request.status).toEqual(200) + expect(request.handlingStack).toMatch(HANDLING_STACK_REGEX) done() }) }) @@ -91,6 +95,7 @@ describe('collect fetch', () => { expect(request.method).toEqual('GET') expect(request.url).toEqual(FAKE_URL) expect(request.status).toEqual(200) + expect(request.handlingStack).toMatch(HANDLING_STACK_REGEX) done() }) }) @@ -105,6 +110,7 @@ describe('collect fetch', () => { expect(request.method).toEqual('GET') expect(request.url).toEqual(FAKE_URL) expect(request.status).toEqual(200) + expect(request.handlingStack).toMatch(HANDLING_STACK_REGEX) done() }) }) @@ -119,6 +125,7 @@ describe('collect fetch', () => { expect(request.method).toEqual('GET') expect(request.url).toEqual(FAKE_URL) expect(request.status).toEqual(500) + expect(request.handlingStack).toMatch(HANDLING_STACK_REGEX) done() }) }) @@ -191,6 +198,7 @@ describe('collect xhr', () => { let completeSpy: jasmine.Spy<(requestCompleteEvent: RequestCompleteEvent) => void> let stubXhrManager: { reset(): void } let stopXhrTracking: () => void + const HANDLING_STACK_REGEX = /^Error: \n\s+at setup @/ beforeEach(() => { if (isIE()) { @@ -253,6 +261,7 @@ describe('collect xhr', () => { expect(request.method).toEqual('GET') expect(request.url).toContain('/ok') expect(request.status).toEqual(200) + expect(request.handlingStack).toMatch(HANDLING_STACK_REGEX) done() }, }) diff --git a/packages/rum-core/src/domain/requestCollection.ts b/packages/rum-core/src/domain/requestCollection.ts index 249de4980a..3ebb02da6d 100644 --- a/packages/rum-core/src/domain/requestCollection.ts +++ b/packages/rum-core/src/domain/requestCollection.ts @@ -56,6 +56,7 @@ export interface RequestCompleteEvent { init?: RequestInit error?: Error isAborted: boolean + handlingStack: string } let nextRequestIndex = 1 @@ -102,6 +103,7 @@ export function trackXhr(lifeCycle: LifeCycle, configuration: RumConfiguration, url: context.url, xhr: context.xhr, isAborted: context.isAborted, + handlingStack: context.handlingStack, }) break } @@ -146,6 +148,7 @@ export function trackFetch(lifeCycle: LifeCycle, configuration: RumConfiguration init: context.init, input: context.input, isAborted: context.isAborted, + handlingStack: context.handlingStack, }) }) break diff --git a/packages/rum-core/src/domain/resource/resourceCollection.spec.ts b/packages/rum-core/src/domain/resource/resourceCollection.spec.ts index a86ea034ef..e3116534f5 100644 --- a/packages/rum-core/src/domain/resource/resourceCollection.spec.ts +++ b/packages/rum-core/src/domain/resource/resourceCollection.spec.ts @@ -7,7 +7,8 @@ import { ResourceType, ExperimentalFeature, } from '@datadog/browser-core' -import type { RumFetchResourceEventDomainContext } from '../../domainContext.types' +import { mockExperimentalFeatures } from '@datadog/browser-core/test' +import type { RumFetchResourceEventDomainContext, RumXhrResourceEventDomainContext } from '../../domainContext.types' import { setup, createRumSessionManagerMock, createPerformanceEntry } from '../../../test' import type { TestSetupBuilder } from '../../../test' import type { RawRumResourceEvent } from '../../rawRumEvent.types' @@ -456,6 +457,37 @@ describe('resourceCollection', () => { expect(privateFields.rule_psr).toEqual(0) }) }) + + describe('with micro-frontend feature flag enabled', () => { + const HANDLING_STACK_REGEX = /^Error: \n\s+at @/ + + beforeEach(() => { + mockExperimentalFeatures([ExperimentalFeature.MICRO_FRONTEND]) + }) + + it('should collect handlingStack from completed fetch request', () => { + if (isIE()) { + pending('No IE support') + } + + const { lifeCycle, rawRumEvents } = setupBuilder.build() + const response = new Response() + lifeCycle.notify(LifeCycleEventType.REQUEST_COMPLETED, createCompletedRequest({ response })) + const domainContext = rawRumEvents[0].domainContext as RumFetchResourceEventDomainContext + + expect(domainContext.handlingStack).toMatch(HANDLING_STACK_REGEX) + }) + + it('should collect handlingStack from completed XHR request', () => { + const { lifeCycle, rawRumEvents } = setupBuilder.build() + const xhr = new XMLHttpRequest() + lifeCycle.notify(LifeCycleEventType.REQUEST_COMPLETED, createCompletedRequest({ xhr })) + + const domainContext = rawRumEvents[0].domainContext as RumXhrResourceEventDomainContext + + expect(domainContext.handlingStack).toMatch(HANDLING_STACK_REGEX) + }) + }) }) function createCompletedRequest(details?: Partial): RequestCompleteEvent { @@ -466,6 +498,8 @@ function createCompletedRequest(details?: Partial): Reques status: 200, type: RequestType.XHR, url: 'https://resource.com/valid', + handlingStack: + 'Error: \n at @ http://localhost/foo.js:1:2\n at @ http://localhost/vendor.js:1:2', ...details, } return request as RequestCompleteEvent diff --git a/packages/rum-core/src/domain/resource/resourceCollection.ts b/packages/rum-core/src/domain/resource/resourceCollection.ts index 1b55dabebc..7b63e4719f 100644 --- a/packages/rum-core/src/domain/resource/resourceCollection.ts +++ b/packages/rum-core/src/domain/resource/resourceCollection.ts @@ -104,7 +104,7 @@ function processRequest( correspondingTimingOverrides, pageStateInfo ) - return { + const collectedData = { startTime: startClocks.relative, rawRumEvent: resourceEvent, domainContext: { @@ -117,6 +117,12 @@ function processRequest( isAborted: request.isAborted, } as RumFetchResourceEventDomainContext | RumXhrResourceEventDomainContext, } + + if (isExperimentalFeatureEnabled(ExperimentalFeature.MICRO_FRONTEND)) { + collectedData.domainContext.handlingStack = request.handlingStack + } + + return collectedData } function processResourceEntry( diff --git a/packages/rum-core/src/domainContext.types.ts b/packages/rum-core/src/domainContext.types.ts index 9327290681..be9f1aee56 100644 --- a/packages/rum-core/src/domainContext.types.ts +++ b/packages/rum-core/src/domainContext.types.ts @@ -33,12 +33,14 @@ export interface RumFetchResourceEventDomainContext { error?: Error performanceEntry?: PerformanceEntry isAborted: boolean + handlingStack?: string } export interface RumXhrResourceEventDomainContext { xhr: XMLHttpRequest performanceEntry?: PerformanceEntry isAborted: boolean + handlingStack?: string } export interface RumOtherResourceEventDomainContext { From 60046ebe33a3a82eeda0877d8930fa2e702faede Mon Sep 17 00:00:00 2001 From: Thomas Lebeau Date: Tue, 30 Apr 2024 13:35:44 +0200 Subject: [PATCH 02/11] add handling stack for addErrors and addAction --- .../rum-core/src/boot/preStartRum.spec.ts | 1 + .../rum-core/src/boot/rumPublicApi.spec.ts | 15 ++++++++++++ packages/rum-core/src/boot/rumPublicApi.ts | 21 +++++++++------- .../src/domain/action/actionCollection.ts | 24 +++++++++++++++++-- .../src/domain/error/errorCollection.ts | 15 +++++++++--- packages/rum-core/src/domainContext.types.ts | 2 ++ 6 files changed, 65 insertions(+), 13 deletions(-) diff --git a/packages/rum-core/src/boot/preStartRum.spec.ts b/packages/rum-core/src/boot/preStartRum.spec.ts index 89e3e14397..07fe7e4e2f 100644 --- a/packages/rum-core/src/boot/preStartRum.spec.ts +++ b/packages/rum-core/src/boot/preStartRum.spec.ts @@ -489,6 +489,7 @@ describe('preStartRum', () => { strategy.addError(error) strategy.init(DEFAULT_INIT_CONFIGURATION) expect(addErrorSpy).toHaveBeenCalledOnceWith(error, undefined) + // todo: test with handlingStack }) it('startView', () => { diff --git a/packages/rum-core/src/boot/rumPublicApi.spec.ts b/packages/rum-core/src/boot/rumPublicApi.spec.ts index 576cb07175..bd1f80eae6 100644 --- a/packages/rum-core/src/boot/rumPublicApi.spec.ts +++ b/packages/rum-core/src/boot/rumPublicApi.spec.ts @@ -183,11 +183,26 @@ describe('rum public api', () => { name: 'foo', startClocks: jasmine.any(Object), type: ActionType.CUSTOM, + handlingStack: jasmine.any(String), }, { context: {}, user: {}, hasReplay: undefined }, ]) }) + it('should generate a handling stack', () => { + rumPublicApi.init(DEFAULT_INIT_CONFIGURATION) + + function triggerAction() { + rumPublicApi.addAction('foo', { bar: 'baz' }) + } + + triggerAction() + + expect(addActionSpy).toHaveBeenCalledTimes(1) + const stacktrace = addActionSpy.calls.argsFor(0)[0].handlingStack + expect(stacktrace).toMatch(/^Error:\s+at triggerAction @/) + }) + describe('save context when sending an action', () => { it('saves the date', () => { const { clock } = setupBuilder.withFakeClock().build() diff --git a/packages/rum-core/src/boot/rumPublicApi.ts b/packages/rum-core/src/boot/rumPublicApi.ts index 1846db4108..bfa3574616 100644 --- a/packages/rum-core/src/boot/rumPublicApi.ts +++ b/packages/rum-core/src/boot/rumPublicApi.ts @@ -224,14 +224,19 @@ export function makeRumPublicApi(startRumImpl: StartRum, recorderApi: RecorderAp getInitConfiguration: monitor(() => deepClone(strategy.initConfiguration)), - addAction: monitor((name: string, context?: object) => { - strategy.addAction({ - name: sanitize(name)!, - context: sanitize(context) as Context, - startClocks: clocksNow(), - type: ActionType.CUSTOM, - }) - }), + addAction: (name: string, context?: object) => { + const handlingStack = createHandlingStack(2) + + callMonitored(() => + strategy.addAction({ + name: sanitize(name)!, + context: sanitize(context) as Context, + startClocks: clocksNow(), + type: ActionType.CUSTOM, + handlingStack, + }) + ) + }, addError: (error: unknown, context?: object) => { const handlingStack = createHandlingStack(2) diff --git a/packages/rum-core/src/domain/action/actionCollection.ts b/packages/rum-core/src/domain/action/actionCollection.ts index 4233455daf..a64cea738a 100644 --- a/packages/rum-core/src/domain/action/actionCollection.ts +++ b/packages/rum-core/src/domain/action/actionCollection.ts @@ -1,5 +1,13 @@ import type { ClocksState, Context, Observable } from '@datadog/browser-core' -import { noop, assign, combine, toServerDuration, generateUUID } from '@datadog/browser-core' +import { + noop, + assign, + combine, + toServerDuration, + generateUUID, + isExperimentalFeatureEnabled, + ExperimentalFeature, +} from '@datadog/browser-core' import type { RawRumActionEvent } from '../../rawRumEvent.types' import { ActionType, RumEventType } from '../../rawRumEvent.types' @@ -9,6 +17,7 @@ import type { RumConfiguration } from '../configuration' import type { CommonContext } from '../contexts/commonContext' import type { PageStateHistory } from '../contexts/pageStateHistory' import { PageState } from '../contexts/pageStateHistory' +import type { RumActionEventDomainContext } from '../../domainContext.types' import type { ActionContexts, ClickAction } from './trackClickActions' import { trackClickActions } from './trackClickActions' @@ -19,6 +28,7 @@ export interface CustomAction { name: string startClocks: ClocksState context?: Context + handlingStack?: string } export type AutoAction = ClickAction @@ -101,11 +111,21 @@ function processAction( autoActionProperties ) + const domainContext: RumActionEventDomainContext = isAutoAction(action) ? { events: action.events } : {} + + if ( + !isAutoAction(action) && + action.handlingStack && + isExperimentalFeatureEnabled(ExperimentalFeature.MICRO_FRONTEND) + ) { + domainContext.handlingStack = action.handlingStack + } + return { customerContext, rawRumEvent: actionEvent, startTime: action.startClocks.relative, - domainContext: isAutoAction(action) ? { events: action.events } : {}, + domainContext, } } diff --git a/packages/rum-core/src/domain/error/errorCollection.ts b/packages/rum-core/src/domain/error/errorCollection.ts index e0c83733c5..8207cf0a2f 100644 --- a/packages/rum-core/src/domain/error/errorCollection.ts +++ b/packages/rum-core/src/domain/error/errorCollection.ts @@ -10,6 +10,8 @@ import { Observable, trackRuntimeError, NonErrorPrefix, + isExperimentalFeatureEnabled, + ExperimentalFeature, } from '@datadog/browser-core' import type { RumConfiguration } from '../configuration' import type { RawRumErrorEvent } from '../../rawRumEvent.types' @@ -20,6 +22,7 @@ import type { FeatureFlagContexts } from '../contexts/featureFlagContext' import type { CommonContext } from '../contexts/commonContext' import type { PageStateHistory } from '../contexts/pageStateHistory' import { PageState } from '../contexts/pageStateHistory' +import type { RumErrorEventDomainContext } from '../../domainContext.types' import { trackConsoleError } from './trackConsoleError' import { trackReportError } from './trackReportError' @@ -119,11 +122,17 @@ function processError( rawRumEvent.feature_flags = featureFlagContext } + const domainContext: RumErrorEventDomainContext = { + error: error.originalError, + } + + if (isExperimentalFeatureEnabled(ExperimentalFeature.MICRO_FRONTEND)) { + domainContext.handlingStack = error.handlingStack + } + return { rawRumEvent, startTime: error.startClocks.relative, - domainContext: { - error: error.originalError, - }, + domainContext, } } diff --git a/packages/rum-core/src/domainContext.types.ts b/packages/rum-core/src/domainContext.types.ts index be9f1aee56..d403e0db51 100644 --- a/packages/rum-core/src/domainContext.types.ts +++ b/packages/rum-core/src/domainContext.types.ts @@ -24,6 +24,7 @@ export interface RumViewEventDomainContext { export interface RumActionEventDomainContext { events?: Event[] + handlingStack?: string } export interface RumFetchResourceEventDomainContext { @@ -49,6 +50,7 @@ export interface RumOtherResourceEventDomainContext { export interface RumErrorEventDomainContext { error: unknown + handlingStack?: string } export interface RumLongTaskEventDomainContext { From 8751cecf2692919ecdb10b0064ea4e0b548346e8 Mon Sep 17 00:00:00 2001 From: Thomas Lebeau Date: Wed, 15 May 2024 16:41:48 +0200 Subject: [PATCH 03/11] =?UTF-8?q?=F0=9F=A7=AA=20add=20e2e=20tests?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/core/src/tools/instrumentMethod.ts | 46 +++---- test/e2e/scenario/microfrontend.scenario.ts | 128 ++++++++++++++++++++ 2 files changed, 147 insertions(+), 27 deletions(-) create mode 100644 test/e2e/scenario/microfrontend.scenario.ts diff --git a/packages/core/src/tools/instrumentMethod.ts b/packages/core/src/tools/instrumentMethod.ts index 7ba9d97908..85ec72e67e 100644 --- a/packages/core/src/tools/instrumentMethod.ts +++ b/packages/core/src/tools/instrumentMethod.ts @@ -80,34 +80,14 @@ export function instrumentMethod | undefined { - if (typeof instrumentation !== 'function') { - return undefined + const instrumentation = function (this: TARGET): ReturnType | undefined { + if (stopped) { + // eslint-disable-next-line @typescript-eslint/no-unsafe-return, @typescript-eslint/no-unsafe-call + return original.apply(this, arguments as unknown as Parameters) } - // eslint-disable-next-line @typescript-eslint/no-unsafe-return, @typescript-eslint/no-unsafe-call - return instrumentation.apply(this, arguments as unknown as Parameters) - } - targetPrototype[method] = instrumentationWrapper as TARGET[METHOD] - - return { - stop: () => { - if (targetPrototype[method] === instrumentationWrapper) { - targetPrototype[method] = original - } else { - instrumentation = original - } - }, - } -} -function createInstrumentedMethod( - original: TARGET[METHOD], - onPreCall: (this: null, callInfos: InstrumentedMethodCall) => void -): TARGET[METHOD] { - // eslint-disable-next-line @typescript-eslint/no-unsafe-return - return function (this: TARGET) { const parameters = arrayFrom(arguments) as Parameters let postCallCallback: PostCallCallback | undefined @@ -119,7 +99,7 @@ function createInstrumentedMethod { postCallCallback = callback }, - handlingStack: createHandlingStack(3), + handlingStack: createHandlingStack(2), }, ]) @@ -132,7 +112,19 @@ function createInstrumentedMethod { + stopped = true + // If the instrumentation has been removed by a third party, keep the last one + if (targetPrototype[method] === instrumentation) { + targetPrototype[method] = original + } + }, + } } export function instrumentSetter( diff --git a/test/e2e/scenario/microfrontend.scenario.ts b/test/e2e/scenario/microfrontend.scenario.ts new file mode 100644 index 0000000000..c7f06dba2e --- /dev/null +++ b/test/e2e/scenario/microfrontend.scenario.ts @@ -0,0 +1,128 @@ +import { flushEvents, createTest, html } from '../lib/framework' + +const HANDLING_STACK_REGEX = /^Error: \n\s+at testHandlingStack @/ + +const RUM_CONFIG = { + enableExperimentalFeatures: ['micro_frontend'], + beforeSend: (event: any, domainContext: any) => { + if ('handlingStack' in domainContext) { + event.context!.handlingStack = domainContext.handlingStack + } + + return true + }, +} + +function createBody(additionalBody: string = '') { + return ( + html` + + ` + additionalBody + ) +} + +describe('microfrontend', () => { + createTest('expose handling stack for fetch requests') + .withRum(RUM_CONFIG) + .withBody( + createBody( + html`` + ) + ) + .run(async ({ intakeRegistry }) => { + await $('#done') + await flushEvents() + + const event = intakeRegistry.rumResourceEvents.find((event) => event.resource.type === 'fetch') + + expect(event).toBeTruthy() + expect(event?.context?.handlingStack).toMatch(HANDLING_STACK_REGEX) + }) + + createTest('expose handling stack for xhr requests') + .withRum(RUM_CONFIG) + .withBody( + createBody( + html`` + ) + ) + .run(async ({ intakeRegistry }) => { + await $('#done') + await flushEvents() + + const event = intakeRegistry.rumResourceEvents.find((event) => event.resource.type === 'xhr') + + expect(event).toBeTruthy() + expect(event?.context?.handlingStack).toMatch(HANDLING_STACK_REGEX) + }) + + createTest('expose handling stack for DD_RUM.addAction') + .withRum(RUM_CONFIG) + .withBody( + createBody( + html`` + ) + ) + .run(async ({ intakeRegistry }) => { + await $('#done') + await flushEvents() + + const event = intakeRegistry.rumActionEvents[0] + + expect(event).toBeTruthy() + expect(event?.context?.handlingStack).toMatch(HANDLING_STACK_REGEX) + }) + + createTest('expose handling stack for DD_RUM.addError') + .withRum(RUM_CONFIG) + .withBody( + createBody( + html`` + ) + ) + .run(async ({ intakeRegistry }) => { + await $('#done') + await flushEvents() + + const event = intakeRegistry.rumErrorEvents[0] + + expect(event).toBeTruthy() + expect(event?.context?.handlingStack).toMatch(HANDLING_STACK_REGEX) + }) +}) From 626d7a7c32097e6c4525e9e6ca9842624827b832 Mon Sep 17 00:00:00 2001 From: Thomas Lebeau Date: Thu, 16 May 2024 08:42:18 +0200 Subject: [PATCH 04/11] add e2e and loose up unit tests --- .../core/src/browser/fetchObservable.spec.ts | 14 +-- .../core/src/browser/xhrObservable.spec.ts | 24 ++-- .../core/src/tools/instrumentMethod.spec.ts | 2 +- .../rum-core/src/boot/preStartRum.spec.ts | 1 - .../src/domain/requestCollection.spec.ts | 17 ++- test/e2e/scenario/microfrontend.scenario.ts | 119 +++++++----------- 6 files changed, 69 insertions(+), 108 deletions(-) diff --git a/packages/core/src/browser/fetchObservable.spec.ts b/packages/core/src/browser/fetchObservable.spec.ts index 796bf1bc2e..3fd645ace3 100644 --- a/packages/core/src/browser/fetchObservable.spec.ts +++ b/packages/core/src/browser/fetchObservable.spec.ts @@ -1,12 +1,10 @@ -import type { FetchStubManager, FetchStubPromise } from '../../test' +import type { FetchStub, FetchStubManager, FetchStubPromise } from '../../test' import { stubFetch } from '../../test' import { isIE } from '../tools/utils/browserDetection' import type { Subscription } from '../tools/observable' import type { FetchResolveContext, FetchContext } from './fetchObservable' import { initFetchObservable } from './fetchObservable' -const HANDLING_STACK_REGEX = /^Error: \n\s+at fetchStub @/ - describe('fetch proxy', () => { const FAKE_URL = 'http://fake-url/' const FAKE_RELATIVE_URL = '/fake-path' @@ -31,9 +29,7 @@ describe('fetch proxy', () => { requests.push(context) } }) - fetchStub = function fetchStub(...args): FetchStubPromise { - return window.fetch(...args) as FetchStubPromise - } + fetchStub = window.fetch as FetchStub }) afterEach(() => { @@ -51,7 +47,7 @@ describe('fetch proxy', () => { expect(request.url).toEqual(FAKE_URL) expect(request.status).toEqual(500) expect(request.isAborted).toBe(false) - expect(request.handlingStack).toMatch(HANDLING_STACK_REGEX) + expect(request.handlingStack).toBeDefined() done() }) }) @@ -66,7 +62,7 @@ describe('fetch proxy', () => { expect(request.status).toEqual(0) expect(request.isAborted).toBe(false) expect(request.error).toEqual(new Error('fetch error')) - expect(request.handlingStack).toMatch(HANDLING_STACK_REGEX) + expect(request.handlingStack).toBeDefined() done() }) }) @@ -81,7 +77,7 @@ describe('fetch proxy', () => { expect(request.status).toEqual(0) expect(request.isAborted).toBe(true) expect(request.error).toEqual(new DOMException('The user aborted a request', 'AbortError')) - expect(request.handlingStack).toMatch(HANDLING_STACK_REGEX) + expect(request.handlingStack).toBeDefined() done() }) }) diff --git a/packages/core/src/browser/xhrObservable.spec.ts b/packages/core/src/browser/xhrObservable.spec.ts index f0d5e223ef..23a1781692 100644 --- a/packages/core/src/browser/xhrObservable.spec.ts +++ b/packages/core/src/browser/xhrObservable.spec.ts @@ -5,8 +5,6 @@ import type { Subscription } from '../tools/observable' import type { XhrCompleteContext, XhrContext } from './xhrObservable' import { initXhrObservable } from './xhrObservable' -const HANDLING_STACK_REGEX = /^Error: \n {2}at setup @/ - describe('xhr observable', () => { let requestsTrackingSubscription: Subscription let contextEditionSubscription: Subscription | undefined @@ -53,7 +51,7 @@ describe('xhr observable', () => { expect(request.status).toBe(200) expect(request.isAborted).toBe(false) expect(request.duration).toEqual(jasmine.any(Number)) - expect(request.handlingStack).toMatch(HANDLING_STACK_REGEX) + expect(request.handlingStack).toBeDefined() done() }, }) @@ -69,7 +67,7 @@ describe('xhr observable', () => { onComplete() { const request = requests[0] expect(request.method).toBe('GET') - expect(request.handlingStack).toMatch(HANDLING_STACK_REGEX) + expect(request.handlingStack).toBeDefined() done() }, }) @@ -89,7 +87,7 @@ describe('xhr observable', () => { expect(request.status).toBe(404) expect(request.isAborted).toBe(false) expect(request.duration).toEqual(jasmine.any(Number)) - expect(request.handlingStack).toMatch(HANDLING_STACK_REGEX) + expect(request.handlingStack).toBeDefined() done() }, }) @@ -109,7 +107,7 @@ describe('xhr observable', () => { expect(request.status).toBe(500) expect(request.isAborted).toBe(false) expect(request.duration).toEqual(jasmine.any(Number)) - expect(request.handlingStack).toMatch(HANDLING_STACK_REGEX) + expect(request.handlingStack).toBeDefined() done() }, }) @@ -129,7 +127,7 @@ describe('xhr observable', () => { expect(request.status).toBe(0) expect(request.isAborted).toBe(false) expect(request.duration).toEqual(jasmine.any(Number)) - expect(request.handlingStack).toMatch(HANDLING_STACK_REGEX) + expect(request.handlingStack).toBeDefined() done() }, }) @@ -158,7 +156,7 @@ describe('xhr observable', () => { expect(request.isAborted).toBe(false) expect(xhr.status).toBe(0) expect(xhr.onreadystatechange).toHaveBeenCalledTimes(1) - expect(request.handlingStack).toMatch(HANDLING_STACK_REGEX) + expect(request.handlingStack).toBeDefined() done() }, }) @@ -179,7 +177,7 @@ describe('xhr observable', () => { expect(request.duration).toEqual(jasmine.any(Number)) expect(request.isAborted).toBe(true) expect(xhr.status).toBe(0) - expect(request.handlingStack).toMatch(HANDLING_STACK_REGEX) + expect(request.handlingStack).toBeDefined() done() }, }) @@ -201,7 +199,7 @@ describe('xhr observable', () => { expect(request.isAborted).toBe(false) expect(request.duration).toEqual(jasmine.any(Number)) expect(xhr.onreadystatechange).toHaveBeenCalled() - expect(request.handlingStack).toMatch(HANDLING_STACK_REGEX) + expect(request.handlingStack).toBeDefined() done() }, }) @@ -223,7 +221,7 @@ describe('xhr observable', () => { expect(request.isAborted).toBe(false) expect(request.duration).toEqual(jasmine.any(Number)) expect(xhr.onreadystatechange).toHaveBeenCalled() - expect(request.handlingStack).toMatch(HANDLING_STACK_REGEX) + expect(request.handlingStack).toBeDefined() done() }, }) @@ -295,7 +293,7 @@ describe('xhr observable', () => { expect(firstRequest.status).toBe(200) expect(firstRequest.isAborted).toBe(false) expect(firstRequest.duration).toEqual(jasmine.any(Number)) - expect(firstRequest.handlingStack).toMatch(HANDLING_STACK_REGEX) + expect(firstRequest.handlingStack).toBeDefined() const secondRequest = requests[1] expect(secondRequest.method).toBe('GET') @@ -303,7 +301,7 @@ describe('xhr observable', () => { expect(secondRequest.status).toBe(400) expect(secondRequest.isAborted).toBe(false) expect(secondRequest.duration).toEqual(jasmine.any(Number)) - expect(secondRequest.handlingStack).toMatch(/^Error: \n {2}at StubXhr.onLoad @/) + expect(secondRequest.handlingStack).toBeDefined() expect(xhr.onreadystatechange).toHaveBeenCalledTimes(2) expect(listeners.load.length).toBe(0) diff --git a/packages/core/src/tools/instrumentMethod.spec.ts b/packages/core/src/tools/instrumentMethod.spec.ts index 0bf106a21d..b56d66df21 100644 --- a/packages/core/src/tools/instrumentMethod.spec.ts +++ b/packages/core/src/tools/instrumentMethod.spec.ts @@ -59,7 +59,7 @@ describe('instrumentMethod', () => { target: object, parameters: jasmine.any(Object), onPostCall: jasmine.any(Function), - handlingStack: jasmine.stringMatching(/^Error: \n\s+at UserContext. @/), + handlingStack: jasmine.any(String), }) expect(instrumentationSpy.calls.mostRecent().args[0].parameters[0]).toBe(2) expect(instrumentationSpy.calls.mostRecent().args[0].parameters[1]).toBe(3) diff --git a/packages/rum-core/src/boot/preStartRum.spec.ts b/packages/rum-core/src/boot/preStartRum.spec.ts index 07fe7e4e2f..89e3e14397 100644 --- a/packages/rum-core/src/boot/preStartRum.spec.ts +++ b/packages/rum-core/src/boot/preStartRum.spec.ts @@ -489,7 +489,6 @@ describe('preStartRum', () => { strategy.addError(error) strategy.init(DEFAULT_INIT_CONFIGURATION) expect(addErrorSpy).toHaveBeenCalledOnceWith(error, undefined) - // todo: test with handlingStack }) it('startView', () => { diff --git a/packages/rum-core/src/domain/requestCollection.spec.ts b/packages/rum-core/src/domain/requestCollection.spec.ts index 86625370a4..0df47d924e 100644 --- a/packages/rum-core/src/domain/requestCollection.spec.ts +++ b/packages/rum-core/src/domain/requestCollection.spec.ts @@ -1,6 +1,6 @@ import type { Payload } from '@datadog/browser-core' import { isIE, RequestType } from '@datadog/browser-core' -import type { FetchStub, FetchStubManager, FetchStubPromise } from '@datadog/browser-core/test' +import type { FetchStub, FetchStubManager } from '@datadog/browser-core/test' import { SPEC_ENDPOINTS, stubFetch, stubXhr, withXhr } from '@datadog/browser-core/test' import type { RumConfiguration } from './configuration' import { validateAndBuildRumConfiguration } from './configuration' @@ -20,7 +20,6 @@ describe('collect fetch', () => { let startSpy: jasmine.Spy<(requestStartEvent: RequestStartEvent) => void> let completeSpy: jasmine.Spy<(requestCompleteEvent: RequestCompleteEvent) => void> let stopFetchTracking: () => void - const HANDLING_STACK_REGEX = /^Error: \n\s+at fetchStub @/ beforeEach(() => { if (isIE()) { @@ -47,9 +46,7 @@ describe('collect fetch', () => { } ;({ stop: stopFetchTracking } = trackFetch(lifeCycle, configuration, tracerStub as Tracer)) - fetchStub = function fetchStub(...args): FetchStubPromise { - return window.fetch(...args) as FetchStubPromise - } + fetchStub = window.fetch as FetchStub window.onunhandledrejection = (ev: PromiseRejectionEvent) => { throw new Error(`unhandled rejected promise \n ${ev.reason as string}`) } @@ -80,7 +77,7 @@ describe('collect fetch', () => { expect(request.method).toEqual('GET') expect(request.url).toEqual(FAKE_URL) expect(request.status).toEqual(200) - expect(request.handlingStack).toMatch(HANDLING_STACK_REGEX) + expect(request.handlingStack).toBeDefined() done() }) }) @@ -95,7 +92,7 @@ describe('collect fetch', () => { expect(request.method).toEqual('GET') expect(request.url).toEqual(FAKE_URL) expect(request.status).toEqual(200) - expect(request.handlingStack).toMatch(HANDLING_STACK_REGEX) + expect(request.handlingStack).toBeDefined() done() }) }) @@ -110,7 +107,7 @@ describe('collect fetch', () => { expect(request.method).toEqual('GET') expect(request.url).toEqual(FAKE_URL) expect(request.status).toEqual(200) - expect(request.handlingStack).toMatch(HANDLING_STACK_REGEX) + expect(request.handlingStack).toBeDefined() done() }) }) @@ -125,7 +122,7 @@ describe('collect fetch', () => { expect(request.method).toEqual('GET') expect(request.url).toEqual(FAKE_URL) expect(request.status).toEqual(500) - expect(request.handlingStack).toMatch(HANDLING_STACK_REGEX) + expect(request.handlingStack).toBeDefined() done() }) }) @@ -261,7 +258,7 @@ describe('collect xhr', () => { expect(request.method).toEqual('GET') expect(request.url).toContain('/ok') expect(request.status).toEqual(200) - expect(request.handlingStack).toMatch(HANDLING_STACK_REGEX) + expect(request.handlingStack).toBeDefined() done() }, }) diff --git a/test/e2e/scenario/microfrontend.scenario.ts b/test/e2e/scenario/microfrontend.scenario.ts index c7f06dba2e..a422cc45c0 100644 --- a/test/e2e/scenario/microfrontend.scenario.ts +++ b/test/e2e/scenario/microfrontend.scenario.ts @@ -1,8 +1,8 @@ -import { flushEvents, createTest, html } from '../lib/framework' +import { flushEvents, createTest } from '../lib/framework' const HANDLING_STACK_REGEX = /^Error: \n\s+at testHandlingStack @/ -const RUM_CONFIG = { +const CONFIG = { enableExperimentalFeatures: ['micro_frontend'], beforeSend: (event: any, domainContext: any) => { if ('handlingStack' in domainContext) { @@ -13,37 +13,20 @@ const RUM_CONFIG = { }, } -function createBody(additionalBody: string = '') { - return ( - html` - - ` + additionalBody - ) -} - describe('microfrontend', () => { createTest('expose handling stack for fetch requests') - .withRum(RUM_CONFIG) - .withBody( - createBody( - html`` - ) - ) + .withRum(CONFIG) + .withRumInit((configuration) => { + window.DD_RUM!.init(configuration) + + const noop = () => {} + function testHandlingStack() { + fetch('/ok').then(noop, noop) + } + + testHandlingStack() + }) .run(async ({ intakeRegistry }) => { - await $('#done') await flushEvents() const event = intakeRegistry.rumResourceEvents.find((event) => event.resource.type === 'fetch') @@ -53,23 +36,19 @@ describe('microfrontend', () => { }) createTest('expose handling stack for xhr requests') - .withRum(RUM_CONFIG) - .withBody( - createBody( - html`` - ) - ) + .withRum(CONFIG) + .withRumInit((configuration) => { + window.DD_RUM!.init(configuration) + + function testHandlingStack() { + const xhr = new XMLHttpRequest() + xhr.open('GET', '/ok') + xhr.send() + } + + testHandlingStack() + }) .run(async ({ intakeRegistry }) => { - await $('#done') await flushEvents() const event = intakeRegistry.rumResourceEvents.find((event) => event.resource.type === 'xhr') @@ -79,21 +58,17 @@ describe('microfrontend', () => { }) createTest('expose handling stack for DD_RUM.addAction') - .withRum(RUM_CONFIG) - .withBody( - createBody( - html`` - ) - ) + .withRum(CONFIG) + .withRumInit((configuration) => { + window.DD_RUM!.init(configuration) + + function testHandlingStack() { + window.DD_RUM!.addAction('foo') + } + + testHandlingStack() + }) .run(async ({ intakeRegistry }) => { - await $('#done') await flushEvents() const event = intakeRegistry.rumActionEvents[0] @@ -103,21 +78,17 @@ describe('microfrontend', () => { }) createTest('expose handling stack for DD_RUM.addError') - .withRum(RUM_CONFIG) - .withBody( - createBody( - html`` - ) - ) + .withRum(CONFIG) + .withRumInit((configuration) => { + window.DD_RUM!.init(configuration) + + function testHandlingStack() { + window.DD_RUM!.addError(new Error('foo')) + } + + testHandlingStack() + }) .run(async ({ intakeRegistry }) => { - await $('#done') await flushEvents() const event = intakeRegistry.rumErrorEvents[0] From 1c2e2e3a78283225f856f238334dfaf6545bd10e Mon Sep 17 00:00:00 2001 From: Thomas Lebeau Date: Thu, 16 May 2024 17:23:10 +0200 Subject: [PATCH 05/11] remove unused variable --- packages/rum-core/src/domain/requestCollection.spec.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/rum-core/src/domain/requestCollection.spec.ts b/packages/rum-core/src/domain/requestCollection.spec.ts index 0df47d924e..6c2e770629 100644 --- a/packages/rum-core/src/domain/requestCollection.spec.ts +++ b/packages/rum-core/src/domain/requestCollection.spec.ts @@ -195,7 +195,6 @@ describe('collect xhr', () => { let completeSpy: jasmine.Spy<(requestCompleteEvent: RequestCompleteEvent) => void> let stubXhrManager: { reset(): void } let stopXhrTracking: () => void - const HANDLING_STACK_REGEX = /^Error: \n\s+at setup @/ beforeEach(() => { if (isIE()) { From 7a80140773388d726563d2a429b95c13edaec0f7 Mon Sep 17 00:00:00 2001 From: Thomas Lebeau Date: Fri, 17 May 2024 09:40:46 +0200 Subject: [PATCH 06/11] =?UTF-8?q?=F0=9F=A7=AA=20add=20missing=20test=20cov?= =?UTF-8?q?erage?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/action/actionCollection.spec.ts | 27 +++++++++++++++++-- .../src/domain/error/errorCollection.spec.ts | 20 ++++++++++++-- 2 files changed, 43 insertions(+), 4 deletions(-) diff --git a/packages/rum-core/src/domain/action/actionCollection.spec.ts b/packages/rum-core/src/domain/action/actionCollection.spec.ts index 79730ad78a..532d62edaf 100644 --- a/packages/rum-core/src/domain/action/actionCollection.spec.ts +++ b/packages/rum-core/src/domain/action/actionCollection.spec.ts @@ -1,5 +1,11 @@ -import type { Duration, RelativeTime, ServerDuration, TimeStamp } from '@datadog/browser-core' -import { createNewEvent } from '@datadog/browser-core/test' +import { + ExperimentalFeature, + type Duration, + type RelativeTime, + type ServerDuration, + type TimeStamp, +} from '@datadog/browser-core' +import { createNewEvent, mockExperimentalFeatures } from '@datadog/browser-core/test' import type { TestSetupBuilder } from '../../../test' import { setup } from '../../../test' import { RumEventType, ActionType } from '../../rawRumEvent.types' @@ -117,4 +123,21 @@ describe('actionCollection', () => { }) expect(rawRumEvents[0].domainContext).toEqual({}) }) + + it('should create action with handling stack', () => { + const { rawRumEvents } = setupBuilder.build() + + mockExperimentalFeatures([ExperimentalFeature.MICRO_FRONTEND]) + + addAction({ + name: 'foo', + startClocks: { relative: 1234 as RelativeTime, timeStamp: 123456789 as TimeStamp }, + type: ActionType.CUSTOM, + handlingStack: 'Error\n at foo\n at bar', + }) + + expect(rawRumEvents[0].domainContext).toEqual({ + handlingStack: 'Error\n at foo\n at bar', + }) + }) }) diff --git a/packages/rum-core/src/domain/error/errorCollection.spec.ts b/packages/rum-core/src/domain/error/errorCollection.spec.ts index e15fb8c68b..b0518b0430 100644 --- a/packages/rum-core/src/domain/error/errorCollection.spec.ts +++ b/packages/rum-core/src/domain/error/errorCollection.spec.ts @@ -1,6 +1,6 @@ import type { RelativeTime, TimeStamp, ErrorWithCause } from '@datadog/browser-core' -import { ErrorHandling, ErrorSource, NO_ERROR_STACK_PRESENT_MESSAGE } from '@datadog/browser-core' -import { FAKE_CSP_VIOLATION_EVENT } from '@datadog/browser-core/test' +import { ErrorHandling, ErrorSource, ExperimentalFeature, NO_ERROR_STACK_PRESENT_MESSAGE } from '@datadog/browser-core' +import { FAKE_CSP_VIOLATION_EVENT, mockExperimentalFeatures } from '@datadog/browser-core/test' import type { TestSetupBuilder } from '../../../test' import { setup } from '../../../test' import type { RawRumErrorEvent } from '../../rawRumEvent.types' @@ -220,6 +220,22 @@ describe('error collection', () => { expect(rawRumErrorEvent.feature_flags).toEqual({ feature: 'foo' }) }) + + it('should include handling stack', () => { + const { rawRumEvents } = setupBuilder.build() + + mockExperimentalFeatures([ExperimentalFeature.MICRO_FRONTEND]) + + addError({ + error: new Error('foo'), + startClocks: { relative: 1234 as RelativeTime, timeStamp: 123456789 as TimeStamp }, + handlingStack: 'Error\n at foo\n at bar', + }) + expect(rawRumEvents[0].domainContext).toEqual({ + error: new Error('foo'), + handlingStack: 'Error\n at foo\n at bar', + }) + }) }) describe('RAW_ERROR_COLLECTED LifeCycle event', () => { From 4625f07f3fb837d11676388917e019f1e6f33ac3 Mon Sep 17 00:00:00 2001 From: Thomas Lebeau Date: Fri, 17 May 2024 14:09:13 +0200 Subject: [PATCH 07/11] =?UTF-8?q?=E2=9C=85=20add=20e2e=20test=20for=20cons?= =?UTF-8?q?ole=20errors?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- test/e2e/scenario/microfrontend.scenario.ts | 28 +++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/test/e2e/scenario/microfrontend.scenario.ts b/test/e2e/scenario/microfrontend.scenario.ts index a422cc45c0..b00952d156 100644 --- a/test/e2e/scenario/microfrontend.scenario.ts +++ b/test/e2e/scenario/microfrontend.scenario.ts @@ -1,3 +1,4 @@ +import { withBrowserLogs } from '../lib/helpers/browser' import { flushEvents, createTest } from '../lib/framework' const HANDLING_STACK_REGEX = /^Error: \n\s+at testHandlingStack @/ @@ -96,4 +97,31 @@ describe('microfrontend', () => { expect(event).toBeTruthy() expect(event?.context?.handlingStack).toMatch(HANDLING_STACK_REGEX) }) + + describe('expose handling stack for console errors', () => { + createTest('expose handling stack for console errors') + .withRum(CONFIG) + .withRumInit((configuration) => { + window.DD_RUM!.init(configuration) + + function testHandlingStack() { + console.error('foo') + } + + testHandlingStack() + }) + .run(async ({ intakeRegistry }) => { + await flushEvents() + + const event = intakeRegistry.rumErrorEvents[0] + + await withBrowserLogs((logs) => { + expect(logs.length).toBe(1) + expect(logs[0].message).toMatch(/"foo"$/) + }) + + expect(event).toBeTruthy() + expect(event?.context?.handlingStack).toMatch(HANDLING_STACK_REGEX) + }) + }) }) From 0db8ccff2c9f1e719cece1c8984ed67c85b928fd Mon Sep 17 00:00:00 2001 From: Thomas Lebeau Date: Thu, 23 May 2024 11:06:05 +0200 Subject: [PATCH 08/11] fix missing imports --- packages/rum-core/src/domain/resource/resourceCollection.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/rum-core/src/domain/resource/resourceCollection.ts b/packages/rum-core/src/domain/resource/resourceCollection.ts index 3aff77fe7f..a483d46cc9 100644 --- a/packages/rum-core/src/domain/resource/resourceCollection.ts +++ b/packages/rum-core/src/domain/resource/resourceCollection.ts @@ -8,6 +8,8 @@ import { relativeToClocks, assign, isNumber, + ExperimentalFeature, + isExperimentalFeatureEnabled, } from '@datadog/browser-core' import type { RumConfiguration } from '../configuration' import type { RumPerformanceResourceTiming } from '../../browser/performanceCollection' From 11bfe462f776a5bb280630b8321b61192e48a03a Mon Sep 17 00:00:00 2001 From: Thomas Lebeau Date: Thu, 23 May 2024 11:13:16 +0200 Subject: [PATCH 09/11] remove unused imports --- .../src/domain/resource/resourceCollection.spec.ts | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/packages/rum-core/src/domain/resource/resourceCollection.spec.ts b/packages/rum-core/src/domain/resource/resourceCollection.spec.ts index f9d6c08e0b..3a78a2d607 100644 --- a/packages/rum-core/src/domain/resource/resourceCollection.spec.ts +++ b/packages/rum-core/src/domain/resource/resourceCollection.spec.ts @@ -1,12 +1,5 @@ import type { Duration, RelativeTime, ServerDuration, TimeStamp } from '@datadog/browser-core' -import { - resetExperimentalFeatures, - addExperimentalFeatures, - isIE, - RequestType, - ResourceType, - ExperimentalFeature, -} from '@datadog/browser-core' +import { isIE, RequestType, ResourceType, ExperimentalFeature } from '@datadog/browser-core' import { mockExperimentalFeatures } from '@datadog/browser-core/test' import type { RumFetchResourceEventDomainContext, RumXhrResourceEventDomainContext } from '../../domainContext.types' import { setup, createRumSessionManagerMock, createPerformanceEntry } from '../../../test' From 7cfb9e070ffb7d6ad35833f55b215589e0c32e59 Mon Sep 17 00:00:00 2001 From: Thomas Lebeau Date: Fri, 24 May 2024 10:29:13 +0200 Subject: [PATCH 10/11] =?UTF-8?q?=F0=9F=91=8C=20add=20type=20to=20e2e=20te?= =?UTF-8?q?sts?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- test/e2e/scenario/microfrontend.scenario.ts | 45 ++++++++++----------- 1 file changed, 22 insertions(+), 23 deletions(-) diff --git a/test/e2e/scenario/microfrontend.scenario.ts b/test/e2e/scenario/microfrontend.scenario.ts index b00952d156..8ba5e47796 100644 --- a/test/e2e/scenario/microfrontend.scenario.ts +++ b/test/e2e/scenario/microfrontend.scenario.ts @@ -1,11 +1,12 @@ +import type { RumEvent, RumEventDomainContext, RumInitConfiguration } from '@datadog/browser-rum-core/src' import { withBrowserLogs } from '../lib/helpers/browser' import { flushEvents, createTest } from '../lib/framework' const HANDLING_STACK_REGEX = /^Error: \n\s+at testHandlingStack @/ -const CONFIG = { +const CONFIG: Partial = { enableExperimentalFeatures: ['micro_frontend'], - beforeSend: (event: any, domainContext: any) => { + beforeSend: (event: RumEvent, domainContext: RumEventDomainContext) => { if ('handlingStack' in domainContext) { event.context!.handlingStack = domainContext.handlingStack } @@ -98,30 +99,28 @@ describe('microfrontend', () => { expect(event?.context?.handlingStack).toMatch(HANDLING_STACK_REGEX) }) - describe('expose handling stack for console errors', () => { - createTest('expose handling stack for console errors') - .withRum(CONFIG) - .withRumInit((configuration) => { - window.DD_RUM!.init(configuration) - - function testHandlingStack() { - console.error('foo') - } + createTest('expose handling stack for console errors') + .withRum(CONFIG) + .withRumInit((configuration) => { + window.DD_RUM!.init(configuration) - testHandlingStack() - }) - .run(async ({ intakeRegistry }) => { - await flushEvents() + function testHandlingStack() { + console.error('foo') + } - const event = intakeRegistry.rumErrorEvents[0] + testHandlingStack() + }) + .run(async ({ intakeRegistry }) => { + await flushEvents() - await withBrowserLogs((logs) => { - expect(logs.length).toBe(1) - expect(logs[0].message).toMatch(/"foo"$/) - }) + const event = intakeRegistry.rumErrorEvents[0] - expect(event).toBeTruthy() - expect(event?.context?.handlingStack).toMatch(HANDLING_STACK_REGEX) + await withBrowserLogs((logs) => { + expect(logs.length).toBe(1) + expect(logs[0].message).toMatch(/"foo"$/) }) - }) + + expect(event).toBeTruthy() + expect(event?.context?.handlingStack).toMatch(HANDLING_STACK_REGEX) + }) }) From 8a3328fd64dc7df6ce53780533843cfd6fcfa717 Mon Sep 17 00:00:00 2001 From: Thomas Lebeau Date: Mon, 27 May 2024 11:24:50 +0200 Subject: [PATCH 11/11] =?UTF-8?q?=F0=9F=91=8C=20fix=20pr=20comments?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/core/src/browser/fetchObservable.ts | 6 ++++-- packages/core/src/browser/xhrObservable.ts | 13 +++++++++---- .../src/domain/console/consoleObservable.ts | 2 +- .../core/src/tools/instrumentMethod.spec.ts | 18 +++++++++++++++++- packages/core/src/tools/instrumentMethod.ts | 10 +++++++--- .../src/tools/stackTrace/handlingStack.spec.ts | 2 +- .../core/src/tools/stackTrace/handlingStack.ts | 5 +++-- packages/rum-core/src/boot/rumPublicApi.ts | 4 ++-- .../rum-core/src/domain/requestCollection.ts | 2 +- 9 files changed, 45 insertions(+), 17 deletions(-) diff --git a/packages/core/src/browser/fetchObservable.ts b/packages/core/src/browser/fetchObservable.ts index fd16a803ab..53f75bf5e3 100644 --- a/packages/core/src/browser/fetchObservable.ts +++ b/packages/core/src/browser/fetchObservable.ts @@ -12,7 +12,7 @@ interface FetchContextBase { input: unknown init?: RequestInit url: string - handlingStack: string + handlingStack?: string } export interface FetchStartContext extends FetchContextBase { @@ -45,7 +45,9 @@ function createFetchObservable() { return } - const { stop } = instrumentMethod(window, 'fetch', (call) => beforeSend(call, observable)) + const { stop } = instrumentMethod(window, 'fetch', (call) => beforeSend(call, observable), { + computeHandlingStack: true, + }) return stop }) diff --git a/packages/core/src/browser/xhrObservable.ts b/packages/core/src/browser/xhrObservable.ts index 2462bfff32..c6ae65e558 100644 --- a/packages/core/src/browser/xhrObservable.ts +++ b/packages/core/src/browser/xhrObservable.ts @@ -19,7 +19,7 @@ export interface XhrStartContext extends Omit { startClocks: ClocksState isAborted: boolean xhr: XMLHttpRequest - handlingStack: string + handlingStack?: string } export interface XhrCompleteContext extends Omit { @@ -44,9 +44,14 @@ function createXhrObservable(configuration: Configuration) { return new Observable((observable) => { const { stop: stopInstrumentingStart } = instrumentMethod(XMLHttpRequest.prototype, 'open', openXhr) - const { stop: stopInstrumentingSend } = instrumentMethod(XMLHttpRequest.prototype, 'send', (call) => { - sendXhr(call, configuration, observable) - }) + const { stop: stopInstrumentingSend } = instrumentMethod( + XMLHttpRequest.prototype, + 'send', + (call) => { + sendXhr(call, configuration, observable) + }, + { computeHandlingStack: true } + ) const { stop: stopInstrumentingAbort } = instrumentMethod(XMLHttpRequest.prototype, 'abort', abortXhr) diff --git a/packages/core/src/domain/console/consoleObservable.ts b/packages/core/src/domain/console/consoleObservable.ts index 4da1ffcb09..6bde7abb57 100644 --- a/packages/core/src/domain/console/consoleObservable.ts +++ b/packages/core/src/domain/console/consoleObservable.ts @@ -41,7 +41,7 @@ function createConsoleObservable(api: ConsoleApiName) { globalConsole[api] = (...params: unknown[]) => { originalConsoleApi.apply(console, params) - const handlingStack = createHandlingStack(2) + const handlingStack = createHandlingStack() callMonitored(() => { observable.notify(buildConsoleLog(params, api, handlingStack)) diff --git a/packages/core/src/tools/instrumentMethod.spec.ts b/packages/core/src/tools/instrumentMethod.spec.ts index b56d66df21..880876b891 100644 --- a/packages/core/src/tools/instrumentMethod.spec.ts +++ b/packages/core/src/tools/instrumentMethod.spec.ts @@ -59,7 +59,7 @@ describe('instrumentMethod', () => { target: object, parameters: jasmine.any(Object), onPostCall: jasmine.any(Function), - handlingStack: jasmine.any(String), + handlingStack: undefined, }) expect(instrumentationSpy.calls.mostRecent().args[0].parameters[0]).toBe(2) expect(instrumentationSpy.calls.mostRecent().args[0].parameters[1]).toBe(3) @@ -104,6 +104,22 @@ describe('instrumentMethod', () => { expect(instrumentationSpy).toHaveBeenCalled() }) + it('computes the handling stack', () => { + const object = { method: () => 1 } + const instrumentationSpy = jasmine.createSpy() + instrumentMethod(object, 'method', instrumentationSpy, { computeHandlingStack: true }) + + function foo() { + object.method() + } + + foo() + + expect(instrumentationSpy.calls.mostRecent().args[0].handlingStack).toEqual( + jasmine.stringMatching(/^Error: \n {2}at foo @/) + ) + }) + describe('stop()', () => { it('does not call the instrumentation anymore', () => { const object = { method: () => 1 } diff --git a/packages/core/src/tools/instrumentMethod.ts b/packages/core/src/tools/instrumentMethod.ts index 85ec72e67e..1161e2224a 100644 --- a/packages/core/src/tools/instrumentMethod.ts +++ b/packages/core/src/tools/instrumentMethod.ts @@ -27,7 +27,10 @@ export type InstrumentedMethodCall) => void - handlingStack: string + /** + * The stack trace of the method call. + */ + handlingStack?: string } type PostCallCallback = ( @@ -68,7 +71,8 @@ type PostCallCallback( targetPrototype: TARGET, method: METHOD, - onPreCall: (this: null, callInfos: InstrumentedMethodCall) => void + onPreCall: (this: null, callInfos: InstrumentedMethodCall) => void, + { computeHandlingStack }: { computeHandlingStack?: boolean } = {} ) { let original = targetPrototype[method] @@ -99,7 +103,7 @@ export function instrumentMethod { postCallCallback = callback }, - handlingStack: createHandlingStack(2), + handlingStack: computeHandlingStack ? createHandlingStack() : undefined, }, ]) diff --git a/packages/core/src/tools/stackTrace/handlingStack.spec.ts b/packages/core/src/tools/stackTrace/handlingStack.spec.ts index f08583d6f7..1a06175815 100644 --- a/packages/core/src/tools/stackTrace/handlingStack.spec.ts +++ b/packages/core/src/tools/stackTrace/handlingStack.spec.ts @@ -3,7 +3,7 @@ import { createHandlingStack } from './handlingStack' describe('createHandlingStack', () => { let handlingStack: string function internalCall() { - handlingStack = createHandlingStack(2) + handlingStack = createHandlingStack() } function userCallTwo() { internalCall() diff --git a/packages/core/src/tools/stackTrace/handlingStack.ts b/packages/core/src/tools/stackTrace/handlingStack.ts index e7fc397238..ea175c97c0 100644 --- a/packages/core/src/tools/stackTrace/handlingStack.ts +++ b/packages/core/src/tools/stackTrace/handlingStack.ts @@ -9,13 +9,14 @@ import { computeStackTrace } from './computeStackTrace' * - Has to be called at the utmost position of the call stack. * - No monitored function should encapsulate it, that is why we need to use callMonitored inside it. */ -export function createHandlingStack(framesToSkip = 0): string { +export function createHandlingStack(): string { /** * Skip the two internal frames: * - SDK API (console.error, ...) * - this function * in order to keep only the user calls */ + const internalFramesToSkip = 2 const error = new Error() let formattedStack: string @@ -30,7 +31,7 @@ export function createHandlingStack(framesToSkip = 0): string { callMonitored(() => { const stackTrace = computeStackTrace(error) - stackTrace.stack = stackTrace.stack.slice(framesToSkip) + stackTrace.stack = stackTrace.stack.slice(internalFramesToSkip) formattedStack = toStackTraceString(stackTrace) }) diff --git a/packages/rum-core/src/boot/rumPublicApi.ts b/packages/rum-core/src/boot/rumPublicApi.ts index 0d999bb0e5..0b4b531a85 100644 --- a/packages/rum-core/src/boot/rumPublicApi.ts +++ b/packages/rum-core/src/boot/rumPublicApi.ts @@ -232,7 +232,7 @@ export function makeRumPublicApi(startRumImpl: StartRum, recorderApi: RecorderAp getInitConfiguration: monitor(() => deepClone(strategy.initConfiguration)), addAction: (name: string, context?: object) => { - const handlingStack = createHandlingStack(2) + const handlingStack = createHandlingStack() callMonitored(() => { strategy.addAction({ @@ -247,7 +247,7 @@ export function makeRumPublicApi(startRumImpl: StartRum, recorderApi: RecorderAp }, addError: (error: unknown, context?: object) => { - const handlingStack = createHandlingStack(2) + const handlingStack = createHandlingStack() callMonitored(() => { strategy.addError({ error, // Do not sanitize error here, it is needed unserialized by computeRawError() diff --git a/packages/rum-core/src/domain/requestCollection.ts b/packages/rum-core/src/domain/requestCollection.ts index 3ebb02da6d..120fc3eb84 100644 --- a/packages/rum-core/src/domain/requestCollection.ts +++ b/packages/rum-core/src/domain/requestCollection.ts @@ -56,7 +56,7 @@ export interface RequestCompleteEvent { init?: RequestInit error?: Error isAborted: boolean - handlingStack: string + handlingStack?: string } let nextRequestIndex = 1