diff --git a/packages/core/src/browser/fetchProxy.spec.ts b/packages/core/src/browser/fetchProxy.spec.ts index 8e1049e065..dec1b3bf6d 100644 --- a/packages/core/src/browser/fetchProxy.spec.ts +++ b/packages/core/src/browser/fetchProxy.spec.ts @@ -36,7 +36,7 @@ describe('fetch proxy', () => { expect(request.method).toEqual('GET') expect(request.url).toEqual(FAKE_URL) expect(request.status).toEqual(500) - expect(request.response).toEqual('fetch error') + expect(request.responseText).toEqual('fetch error') expect(request.isAborted).toBe(false) done() }) @@ -50,8 +50,9 @@ describe('fetch proxy', () => { expect(request.method).toEqual('GET') expect(request.url).toEqual(FAKE_URL) expect(request.status).toEqual(0) - expect(request.response).toMatch(/Error: fetch error/) + expect(request.responseText).toMatch(/Error: fetch error/) expect(request.isAborted).toBe(false) + expect(request.error).toEqual(new Error('fetch error')) done() }) }) @@ -64,8 +65,9 @@ describe('fetch proxy', () => { expect(request.method).toEqual('GET') expect(request.url).toEqual(FAKE_URL) expect(request.status).toEqual(0) - expect(request.response).toContain('AbortError: The user aborted a request') + expect(request.responseText).toContain('AbortError: The user aborted a request') expect(request.isAborted).toBe(true) + expect(request.error).toEqual(new DOMException('The user aborted a request', 'AbortError')) done() }) }) @@ -79,8 +81,9 @@ describe('fetch proxy', () => { expect(request.method).toEqual('GET') expect(request.url).toEqual(FAKE_URL) expect(request.status).toEqual(200) - expect(request.response).toMatch(/Error: locked/) + expect(request.responseText).toMatch(/Error: locked/) expect(request.isAborted).toBe(false) + expect(request.error).toBeUndefined() done() }) }) @@ -107,7 +110,7 @@ describe('fetch proxy', () => { expect(request.method).toEqual('GET') expect(request.url).toEqual(FAKE_URL) expect(request.status).toEqual(400) - expect(request.response).toEqual('Not found') + expect(request.responseText).toEqual('Not found') expect(request.isAborted).toBe(false) done() }) diff --git a/packages/core/src/browser/fetchProxy.ts b/packages/core/src/browser/fetchProxy.ts index fd9ec84fe2..93aac7f941 100644 --- a/packages/core/src/browser/fetchProxy.ts +++ b/packages/core/src/browser/fetchProxy.ts @@ -28,9 +28,11 @@ export interface FetchStartContext { export interface FetchCompleteContext extends FetchStartContext { duration: Duration status: number - response: string + response?: Response + responseText: string responseType?: string isAborted: boolean + error?: Error } let fetchProxySingleton: FetchProxy | undefined @@ -111,8 +113,9 @@ function afterSend(responsePromise: Promise, context: FetchStartContex if ('stack' in response || response instanceof Error) { context.status = 0 - context.response = toStackTraceString(computeStackTrace(response)) + context.responseText = toStackTraceString(computeStackTrace(response)) context.isAborted = response instanceof DOMException && response.code === DOMException.ABORT_ERR + context.error = response onRequestCompleteCallbacks.forEach((callback) => callback(context as FetchCompleteContext)) } else if ('status' in response) { @@ -122,7 +125,8 @@ function afterSend(responsePromise: Promise, context: FetchStartContex } catch (e) { text = `Unable to retrieve response: ${e as string}` } - context.response = text + context.response = response + context.responseText = text context.responseType = response.type context.status = response.status context.isAborted = false diff --git a/packages/core/src/browser/xhrProxy.spec.ts b/packages/core/src/browser/xhrProxy.spec.ts index b2cf132aba..88ef522d75 100644 --- a/packages/core/src/browser/xhrProxy.spec.ts +++ b/packages/core/src/browser/xhrProxy.spec.ts @@ -33,7 +33,7 @@ describe('xhr proxy', () => { const request = getRequest(0) expect(request.method).toBe('GET') expect(request.url).toContain('/ok') - expect(request.response).toBe('ok') + expect(request.responseText).toBe('ok') expect(request.status).toBe(200) expect(request.isAborted).toBe(false) expect(request.startTime).toEqual(jasmine.any(Number)) @@ -54,7 +54,7 @@ describe('xhr proxy', () => { const request = getRequest(0) expect(request.method).toBe('GET') expect(request.url).toContain('/expected-404') - expect(request.response).toBe('NOT FOUND') + expect(request.responseText).toBe('NOT FOUND') expect(request.status).toBe(404) expect(request.isAborted).toBe(false) expect(request.startTime).toEqual(jasmine.any(Number)) @@ -75,7 +75,7 @@ describe('xhr proxy', () => { const request = getRequest(0) expect(request.method).toBe('GET') expect(request.url).toContain('/throw') - expect(request.response).toEqual('expected server error') + expect(request.responseText).toEqual('expected server error') expect(request.status).toBe(500) expect(request.isAborted).toBe(false) expect(request.startTime).toEqual(jasmine.any(Number)) @@ -96,7 +96,7 @@ describe('xhr proxy', () => { const request = getRequest(0) expect(request.method).toBe('GET') expect(request.url).toBe('http://foo.bar/qux') - expect(request.response).toBe('') + expect(request.responseText).toBe('') expect(request.status).toBe(0) expect(request.isAborted).toBe(false) expect(request.startTime).toEqual(jasmine.any(Number)) @@ -120,7 +120,7 @@ describe('xhr proxy', () => { const request = getRequest(0) expect(request.method).toBe('GET') expect(request.url).toContain('/ok') - expect(request.response).toBe('ok') + expect(request.responseText).toBe('ok') expect(request.status).toBe(200) expect(request.startTime).toEqual(jasmine.any(Number)) expect(request.duration).toEqual(jasmine.any(Number)) @@ -143,7 +143,7 @@ describe('xhr proxy', () => { const request = getRequest(0) expect(request.method).toBe('GET') expect(request.url).toContain('/ok') - expect(request.response).toBeUndefined() + expect(request.responseText).toBeUndefined() expect(request.status).toBe(0) expect(request.startTime).toEqual(jasmine.any(Number)) expect(request.duration).toEqual(jasmine.any(Number)) @@ -166,7 +166,7 @@ describe('xhr proxy', () => { const request = getRequest(0) expect(request.method).toBe('GET') expect(request.url).toContain('/ok') - expect(request.response).toBe('ok') + expect(request.responseText).toBe('ok') expect(request.status).toBe(200) expect(request.isAborted).toBe(false) expect(request.startTime).toEqual(jasmine.any(Number)) diff --git a/packages/core/src/browser/xhrProxy.ts b/packages/core/src/browser/xhrProxy.ts index 0afb30a84b..1a08ad2194 100644 --- a/packages/core/src/browser/xhrProxy.ts +++ b/packages/core/src/browser/xhrProxy.ts @@ -29,8 +29,9 @@ export interface XhrStartContext { export interface XhrCompleteContext extends XhrStartContext { duration: Duration status: number - response: string | undefined + responseText: string | undefined isAborted: boolean + xhr: XMLHttpRequest } let xhrProxySingleton: XhrProxy | undefined @@ -126,8 +127,9 @@ function proxyXhr() { const xhrCompleteContext: XhrCompleteContext = { ...xhrPendingContext, duration: elapsed(xhrPendingContext.startClocks.timeStamp, timeStampNow()), - response: this.response as string | undefined, + responseText: this.response as string | undefined, status: this.status, + xhr: this, } onRequestCompleteCallbacks.forEach((callback) => callback(xhrCompleteContext)) diff --git a/packages/core/src/domain/automaticErrorCollection.spec.ts b/packages/core/src/domain/automaticErrorCollection.spec.ts index 3d35976a4e..dc25e8a70f 100644 --- a/packages/core/src/domain/automaticErrorCollection.spec.ts +++ b/packages/core/src/domain/automaticErrorCollection.spec.ts @@ -238,7 +238,7 @@ describe('network error tracker', () => { }) }) - it('should add a default error response', (done) => { + it('should add a default error response text', (done) => { fetchStub(FAKE_URL).resolveWith({ ...DEFAULT_REQUEST, responseText: undefined }) fetchStubManager.whenAllComplete(() => { @@ -249,7 +249,7 @@ describe('network error tracker', () => { }) }) - it('should truncate error response', (done) => { + it('should truncate error response text', (done) => { fetchStub(FAKE_URL).resolveWith({ ...DEFAULT_REQUEST, responseText: 'Lorem ipsum dolor sit amet orci aliquam.', diff --git a/packages/core/src/domain/automaticErrorCollection.ts b/packages/core/src/domain/automaticErrorCollection.ts index 030244840c..74fc26e1a1 100644 --- a/packages/core/src/domain/automaticErrorCollection.ts +++ b/packages/core/src/domain/automaticErrorCollection.ts @@ -70,6 +70,7 @@ export function startRuntimeErrorTracking(errorObservable: ErrorObservable) { type, source: ErrorSource.SOURCE, startClocks: clocksNow(), + originalError: errorObject, }) } subscribe(traceKitReportHandler) @@ -97,7 +98,7 @@ export function trackNetworkError(configuration: Configuration, errorObservable: url: request.url, }, source: ErrorSource.NETWORK, - stack: truncateResponse(request.response, configuration) || 'Failed to load', + stack: truncateResponseText(request.responseText, configuration) || 'Failed to load', startClocks: request.startClocks, }) } @@ -119,11 +120,11 @@ function isServerError(request: { status: number }) { return request.status >= 500 } -function truncateResponse(response: string | undefined, configuration: Configuration) { - if (response && response.length > configuration.requestErrorResponseLengthLimit) { - return `${response.substring(0, configuration.requestErrorResponseLengthLimit)}...` +function truncateResponseText(responseText: string | undefined, configuration: Configuration) { + if (responseText && responseText.length > configuration.requestErrorResponseLengthLimit) { + return `${responseText.substring(0, configuration.requestErrorResponseLengthLimit)}...` } - return response + return responseText } function format(type: RequestType) { diff --git a/packages/core/src/domain/configuration.spec.ts b/packages/core/src/domain/configuration.spec.ts index f12909ef74..beb0878c16 100644 --- a/packages/core/src/domain/configuration.spec.ts +++ b/packages/core/src/domain/configuration.spec.ts @@ -47,8 +47,8 @@ describe('configuration', () => { } } const configuration = buildConfiguration({ clientToken, beforeSend }, usEnv) - expect(configuration.beforeSend!({ view: { url: '/foo' } })).toBeFalse() - expect(configuration.beforeSend!({ view: { url: '/bar' } })).toBeUndefined() + expect(configuration.beforeSend!({ view: { url: '/foo' } }, {})).toBeFalse() + expect(configuration.beforeSend!({ view: { url: '/bar' } }, {})).toBeUndefined() }) it('should catch errors and log them', () => { @@ -58,7 +58,7 @@ describe('configuration', () => { } const configuration = buildConfiguration({ clientToken, beforeSend }, usEnv) const displaySpy = spyOn(display, 'error') - expect(configuration.beforeSend!(null)).toBeUndefined() + expect(configuration.beforeSend!(null, {})).toBeUndefined() expect(displaySpy).toHaveBeenCalledWith('beforeSend threw an error:', myError) }) }) diff --git a/packages/core/src/domain/configuration.ts b/packages/core/src/domain/configuration.ts index aba52a73a0..33aebe4ddc 100644 --- a/packages/core/src/domain/configuration.ts +++ b/packages/core/src/domain/configuration.ts @@ -53,7 +53,7 @@ export interface UserConfiguration { trackInteractions?: boolean trackViewsManually?: boolean proxyHost?: string - beforeSend?: (event: any) => void + beforeSend?: BeforeSendCallback service?: string env?: string @@ -68,6 +68,8 @@ export interface UserConfiguration { replica?: ReplicaUserConfiguration } +export type BeforeSendCallback = (event: any, context?: any) => unknown + interface ReplicaUserConfiguration { applicationId?: string clientToken: string @@ -78,7 +80,7 @@ export type Configuration = typeof DEFAULT_CONFIGURATION & cookieOptions: CookieOptions service?: string - beforeSend?: (event: any) => unknown + beforeSend?: BeforeSendCallback isEnabled: (feature: string) => boolean } diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts index ab7487cf75..41624a74c7 100644 --- a/packages/core/src/index.ts +++ b/packages/core/src/index.ts @@ -1,4 +1,10 @@ -export { DEFAULT_CONFIGURATION, Configuration, UserConfiguration, buildCookieOptions } from './domain/configuration' +export { + DEFAULT_CONFIGURATION, + Configuration, + UserConfiguration, + buildCookieOptions, + BeforeSendCallback, +} from './domain/configuration' export { startAutomaticErrorCollection, ErrorObservable } from './domain/automaticErrorCollection' export { computeStackTrace } from './domain/tracekit' export { diff --git a/packages/core/src/tools/error.ts b/packages/core/src/tools/error.ts index db2eb492f7..5526f7534f 100644 --- a/packages/core/src/tools/error.ts +++ b/packages/core/src/tools/error.ts @@ -13,6 +13,7 @@ export interface RawError { statusCode: number method: string } + originalError?: unknown } export const ErrorSource = { diff --git a/packages/rum-core/src/boot/rumPublicApi.ts b/packages/rum-core/src/boot/rumPublicApi.ts index c2f6e42100..f43cfe0584 100644 --- a/packages/rum-core/src/boot/rumPublicApi.ts +++ b/packages/rum-core/src/boot/rumPublicApi.ts @@ -19,14 +19,14 @@ import { InternalMonitoring, } from '@datadog/browser-core' import { ProvidedSource } from '../domain/rumEventsCollection/error/errorCollection' -import { CommonContext, User, ActionType } from '../rawRumEvent.types' +import { CommonContext, User, ActionType, RumEventDomainContext } from '../rawRumEvent.types' import { RumEvent } from '../rumEvent.types' import { buildEnv } from './buildEnv' import { startRum } from './startRum' export interface RumUserConfiguration extends UserConfiguration { applicationId: string - beforeSend?: (event: RumEvent) => void | boolean + beforeSend?: (event: RumEvent, context: RumEventDomainContext) => void | boolean } export type RumPublicApi = ReturnType diff --git a/packages/rum-core/src/browser/performanceCollection.ts b/packages/rum-core/src/browser/performanceCollection.ts index 65b66544aa..5b121c89d4 100644 --- a/packages/rum-core/src/browser/performanceCollection.ts +++ b/packages/rum-core/src/browser/performanceCollection.ts @@ -16,6 +16,7 @@ import { LifeCycle, LifeCycleEventType } from '../domain/lifeCycle' import { FAKE_INITIAL_DOCUMENT, isAllowedRequestUrl } from '../domain/rumEventsCollection/resource/resourceUtils' import { getDocumentTraceId } from '../domain/tracing/getDocumentTraceId' +import { PerformanceEntryRepresentation } from '../rawRumEvent.types' export interface RumPerformanceResourceTiming { entryType: 'resource' @@ -42,6 +43,7 @@ export interface RumPerformanceLongTaskTiming { entryType: 'longtask' startTime: RelativeTime duration: Duration + toJSON(): PerformanceEntryRepresentation } export interface RumPerformancePaintTiming { diff --git a/packages/rum-core/src/domain/assembly.spec.ts b/packages/rum-core/src/domain/assembly.spec.ts index 1d2a0762a9..4df38d0f9d 100644 --- a/packages/rum-core/src/domain/assembly.spec.ts +++ b/packages/rum-core/src/domain/assembly.spec.ts @@ -1,10 +1,10 @@ import { ErrorSource, ONE_MINUTE, RawError, RelativeTime, display } from '@datadog/browser-core' import { createRawRumEvent } from '../../test/fixtures' import { setup, TestSetupBuilder } from '../../test/specHelper' -import { CommonContext, RawRumErrorEvent, RumEventType } from '../rawRumEvent.types' +import { CommonContext, RawRumErrorEvent, RawRumEvent, RumEventDomainContext, RumEventType } from '../rawRumEvent.types' import { RumActionEvent, RumErrorEvent, RumEvent } from '../rumEvent.types' import { startRumAssembly } from './assembly' -import { LifeCycle, LifeCycleEventType } from './lifeCycle' +import { LifeCycle, LifeCycleEventType, RawRumEventCollectedData } from './lifeCycle' describe('rum assembly', () => { let setupBuilder: TestSetupBuilder @@ -59,9 +59,8 @@ describe('rum assembly', () => { }) .build() - lifeCycle.notify(LifeCycleEventType.RAW_RUM_EVENT_COLLECTED, { + notifyRawRumEvent(lifeCycle, { rawRumEvent: createRawRumEvent(RumEventType.LONG_TASK, { view: { url: '/path?foo=bar' } }), - startTime: 0 as RelativeTime, }) expect(serverRumEvents[0].view.url).toBe('modified') @@ -78,9 +77,8 @@ describe('rum assembly', () => { }) .build() - lifeCycle.notify(LifeCycleEventType.RAW_RUM_EVENT_COLLECTED, { + notifyRawRumEvent(lifeCycle, { rawRumEvent: createRawRumEvent(RumEventType.LONG_TASK), - startTime: 0 as RelativeTime, }) expect(serverRumEvents[0].context!.foo).toBe('bar') @@ -95,9 +93,8 @@ describe('rum assembly', () => { }) .build() - lifeCycle.notify(LifeCycleEventType.RAW_RUM_EVENT_COLLECTED, { + notifyRawRumEvent(lifeCycle, { rawRumEvent: createRawRumEvent(RumEventType.LONG_TASK), - startTime: 0 as RelativeTime, }) expect(serverRumEvents[0].context!.foo).toBe('bar') @@ -112,9 +109,8 @@ describe('rum assembly', () => { }) .build() - lifeCycle.notify(LifeCycleEventType.RAW_RUM_EVENT_COLLECTED, { + notifyRawRumEvent(lifeCycle, { rawRumEvent: createRawRumEvent(RumEventType.LONG_TASK), - startTime: 0 as RelativeTime, customerContext: { foo: 'bar' }, }) @@ -130,9 +126,8 @@ describe('rum assembly', () => { }) .build() - lifeCycle.notify(LifeCycleEventType.RAW_RUM_EVENT_COLLECTED, { + notifyRawRumEvent(lifeCycle, { rawRumEvent: createRawRumEvent(RumEventType.LONG_TASK), - startTime: 0 as RelativeTime, customerContext: { foo: 'bar' }, }) @@ -148,9 +143,8 @@ describe('rum assembly', () => { }) .build() - lifeCycle.notify(LifeCycleEventType.RAW_RUM_EVENT_COLLECTED, { + notifyRawRumEvent(lifeCycle, { rawRumEvent: createRawRumEvent(RumEventType.LONG_TASK), - startTime: 0 as RelativeTime, customerContext: { foo: 'bar' }, }) @@ -166,9 +160,8 @@ describe('rum assembly', () => { }) .build() - lifeCycle.notify(LifeCycleEventType.RAW_RUM_EVENT_COLLECTED, { + notifyRawRumEvent(lifeCycle, { rawRumEvent: createRawRumEvent(RumEventType.LONG_TASK), - startTime: 0 as RelativeTime, }) }) @@ -181,9 +174,8 @@ describe('rum assembly', () => { }) .build() - lifeCycle.notify(LifeCycleEventType.RAW_RUM_EVENT_COLLECTED, { + notifyRawRumEvent(lifeCycle, { rawRumEvent: createRawRumEvent(RumEventType.VIEW), - startTime: 0 as RelativeTime, }) expect(serverRumEvents[0].context).toBeUndefined() @@ -198,9 +190,8 @@ describe('rum assembly', () => { }) .build() - lifeCycle.notify(LifeCycleEventType.RAW_RUM_EVENT_COLLECTED, { + notifyRawRumEvent(lifeCycle, { rawRumEvent: createRawRumEvent(RumEventType.LONG_TASK), - startTime: 0 as RelativeTime, customerContext: { foo: 'bar' }, }) @@ -215,11 +206,10 @@ describe('rum assembly', () => { }) .build() - lifeCycle.notify(LifeCycleEventType.RAW_RUM_EVENT_COLLECTED, { + notifyRawRumEvent(lifeCycle, { rawRumEvent: createRawRumEvent(RumEventType.LONG_TASK, { view: { id: 'aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee' }, }), - startTime: 0 as RelativeTime, }) expect(serverRumEvents[0].view.id).toBe('aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee') @@ -234,32 +224,28 @@ describe('rum assembly', () => { }) .build() - lifeCycle.notify(LifeCycleEventType.RAW_RUM_EVENT_COLLECTED, { + notifyRawRumEvent(lifeCycle, { rawRumEvent: createRawRumEvent(RumEventType.ACTION, { view: { id: 'aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee' }, }), - startTime: 0 as RelativeTime, }) - lifeCycle.notify(LifeCycleEventType.RAW_RUM_EVENT_COLLECTED, { + notifyRawRumEvent(lifeCycle, { rawRumEvent: createRawRumEvent(RumEventType.ERROR, { view: { id: 'aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee' }, }), - startTime: 0 as RelativeTime, }) - lifeCycle.notify(LifeCycleEventType.RAW_RUM_EVENT_COLLECTED, { + notifyRawRumEvent(lifeCycle, { rawRumEvent: createRawRumEvent(RumEventType.LONG_TASK, { view: { id: 'aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee' }, }), - startTime: 0 as RelativeTime, }) - lifeCycle.notify(LifeCycleEventType.RAW_RUM_EVENT_COLLECTED, { + notifyRawRumEvent(lifeCycle, { rawRumEvent: createRawRumEvent(RumEventType.RESOURCE, { view: { id: 'aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee' }, }), - startTime: 0 as RelativeTime, }) expect(serverRumEvents.length).toBe(0) @@ -273,11 +259,10 @@ describe('rum assembly', () => { .build() const displaySpy = spyOn(display, 'warn') - lifeCycle.notify(LifeCycleEventType.RAW_RUM_EVENT_COLLECTED, { + notifyRawRumEvent(lifeCycle, { rawRumEvent: createRawRumEvent(RumEventType.VIEW, { view: { id: 'aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee' }, }), - startTime: 0 as RelativeTime, }) expect(serverRumEvents[0].view.id).toBe('aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee') @@ -289,9 +274,8 @@ describe('rum assembly', () => { describe('rum context', () => { it('should be merged with event attributes', () => { const { lifeCycle } = setupBuilder.build() - lifeCycle.notify(LifeCycleEventType.RAW_RUM_EVENT_COLLECTED, { + notifyRawRumEvent(lifeCycle, { rawRumEvent: createRawRumEvent(RumEventType.VIEW, undefined), - startTime: 0 as RelativeTime, }) expect(serverRumEvents[0].view.id).toBeDefined() @@ -300,9 +284,8 @@ describe('rum assembly', () => { it('should be overwritten by event attributes', () => { const { lifeCycle } = setupBuilder.build() - lifeCycle.notify(LifeCycleEventType.RAW_RUM_EVENT_COLLECTED, { + notifyRawRumEvent(lifeCycle, { rawRumEvent: createRawRumEvent(RumEventType.VIEW, { date: 10 }), - startTime: 0 as RelativeTime, }) expect(serverRumEvents[0].date).toBe(10) @@ -313,9 +296,8 @@ describe('rum assembly', () => { it('should be merged with event attributes', () => { const { lifeCycle } = setupBuilder.build() commonContext.context = { bar: 'foo' } - lifeCycle.notify(LifeCycleEventType.RAW_RUM_EVENT_COLLECTED, { + notifyRawRumEvent(lifeCycle, { rawRumEvent: createRawRumEvent(RumEventType.VIEW), - startTime: 0 as RelativeTime, }) expect((serverRumEvents[0].context as any).bar).toEqual('foo') @@ -324,9 +306,8 @@ describe('rum assembly', () => { it('should not be included if empty', () => { const { lifeCycle } = setupBuilder.build() commonContext.context = {} - lifeCycle.notify(LifeCycleEventType.RAW_RUM_EVENT_COLLECTED, { + notifyRawRumEvent(lifeCycle, { rawRumEvent: createRawRumEvent(RumEventType.VIEW), - startTime: 0 as RelativeTime, }) expect(serverRumEvents[0].context).toBe(undefined) @@ -335,14 +316,12 @@ describe('rum assembly', () => { it('should ignore subsequent context mutation', () => { const { lifeCycle } = setupBuilder.build() commonContext.context = { bar: 'foo', baz: 'foz' } - lifeCycle.notify(LifeCycleEventType.RAW_RUM_EVENT_COLLECTED, { + notifyRawRumEvent(lifeCycle, { rawRumEvent: createRawRumEvent(RumEventType.VIEW), - startTime: 0 as RelativeTime, }) delete commonContext.context.bar - lifeCycle.notify(LifeCycleEventType.RAW_RUM_EVENT_COLLECTED, { + notifyRawRumEvent(lifeCycle, { rawRumEvent: createRawRumEvent(RumEventType.VIEW), - startTime: 0 as RelativeTime, }) expect((serverRumEvents[0].context as any).bar).toEqual('foo') @@ -353,13 +332,12 @@ describe('rum assembly', () => { const { lifeCycle } = setupBuilder.build() commonContext.context = { replacedContext: 'b', addedContext: 'x' } - lifeCycle.notify(LifeCycleEventType.RAW_RUM_EVENT_COLLECTED, { + notifyRawRumEvent(lifeCycle, { rawRumEvent: createRawRumEvent(RumEventType.VIEW), savedCommonContext: { context: { replacedContext: 'a' }, user: {}, }, - startTime: 0 as RelativeTime, }) expect((serverRumEvents[0].context as any).replacedContext).toEqual('a') @@ -371,9 +349,8 @@ describe('rum assembly', () => { it('should be included in event attributes', () => { const { lifeCycle } = setupBuilder.build() commonContext.user = { id: 'foo' } - lifeCycle.notify(LifeCycleEventType.RAW_RUM_EVENT_COLLECTED, { + notifyRawRumEvent(lifeCycle, { rawRumEvent: createRawRumEvent(RumEventType.VIEW), - startTime: 0 as RelativeTime, }) expect(serverRumEvents[0].usr!.id).toEqual('foo') @@ -382,9 +359,8 @@ describe('rum assembly', () => { it('should not be included if empty', () => { const { lifeCycle } = setupBuilder.build() commonContext.user = {} - lifeCycle.notify(LifeCycleEventType.RAW_RUM_EVENT_COLLECTED, { + notifyRawRumEvent(lifeCycle, { rawRumEvent: createRawRumEvent(RumEventType.VIEW), - startTime: 0 as RelativeTime, }) expect(serverRumEvents[0].usr).toBe(undefined) @@ -394,13 +370,12 @@ describe('rum assembly', () => { const { lifeCycle } = setupBuilder.build() commonContext.user = { replacedAttribute: 'b', addedAttribute: 'x' } - lifeCycle.notify(LifeCycleEventType.RAW_RUM_EVENT_COLLECTED, { + notifyRawRumEvent(lifeCycle, { rawRumEvent: createRawRumEvent(RumEventType.VIEW), savedCommonContext: { context: {}, user: { replacedAttribute: 'a' }, }, - startTime: 0 as RelativeTime, }) expect(serverRumEvents[0].usr!.replacedAttribute).toEqual('a') @@ -411,10 +386,9 @@ describe('rum assembly', () => { describe('customer context', () => { it('should be merged with event attributes', () => { const { lifeCycle } = setupBuilder.build() - lifeCycle.notify(LifeCycleEventType.RAW_RUM_EVENT_COLLECTED, { + notifyRawRumEvent(lifeCycle, { customerContext: { foo: 'bar' }, rawRumEvent: createRawRumEvent(RumEventType.VIEW), - startTime: 0 as RelativeTime, }) expect((serverRumEvents[0].context as any).foo).toEqual('bar') @@ -425,24 +399,21 @@ describe('rum assembly', () => { it('should be added on some event categories', () => { const { lifeCycle } = setupBuilder.build() ;[RumEventType.RESOURCE, RumEventType.LONG_TASK, RumEventType.ERROR].forEach((category) => { - lifeCycle.notify(LifeCycleEventType.RAW_RUM_EVENT_COLLECTED, { + notifyRawRumEvent(lifeCycle, { rawRumEvent: createRawRumEvent(category), - startTime: 0 as RelativeTime, }) expect(serverRumEvents[0].action).toEqual({ id: '7890' }) serverRumEvents = [] }) - lifeCycle.notify(LifeCycleEventType.RAW_RUM_EVENT_COLLECTED, { + notifyRawRumEvent(lifeCycle, { rawRumEvent: createRawRumEvent(RumEventType.VIEW), - startTime: 0 as RelativeTime, }) expect(serverRumEvents[0].action).not.toBeDefined() serverRumEvents = [] - lifeCycle.notify(LifeCycleEventType.RAW_RUM_EVENT_COLLECTED, { + notifyRawRumEvent(lifeCycle, { rawRumEvent: createRawRumEvent(RumEventType.ACTION), - startTime: 0 as RelativeTime, }) expect((serverRumEvents[0] as RumActionEvent).action.id).not.toEqual('7890') serverRumEvents = [] @@ -452,9 +423,8 @@ describe('rum assembly', () => { describe('view context', () => { it('should be merged with event attributes', () => { const { lifeCycle } = setupBuilder.build() - lifeCycle.notify(LifeCycleEventType.RAW_RUM_EVENT_COLLECTED, { + notifyRawRumEvent(lifeCycle, { rawRumEvent: createRawRumEvent(RumEventType.ACTION), - startTime: 0 as RelativeTime, }) expect(serverRumEvents[0].view).toEqual({ id: 'abcde', @@ -468,9 +438,8 @@ describe('rum assembly', () => { describe('event generation condition', () => { it('when tracked, it should generate event', () => { const { lifeCycle } = setupBuilder.build() - lifeCycle.notify(LifeCycleEventType.RAW_RUM_EVENT_COLLECTED, { + notifyRawRumEvent(lifeCycle, { rawRumEvent: createRawRumEvent(RumEventType.VIEW), - startTime: 0 as RelativeTime, }) expect(serverRumEvents.length).toBe(1) }) @@ -483,9 +452,8 @@ describe('rum assembly', () => { isTrackedWithResource: () => false, }) .build() - lifeCycle.notify(LifeCycleEventType.RAW_RUM_EVENT_COLLECTED, { + notifyRawRumEvent(lifeCycle, { rawRumEvent: createRawRumEvent(RumEventType.VIEW), - startTime: 0 as RelativeTime, }) expect(serverRumEvents.length).toBe(0) }) @@ -494,9 +462,8 @@ describe('rum assembly', () => { const { lifeCycle } = setupBuilder.build() viewSessionId = '1234' - lifeCycle.notify(LifeCycleEventType.RAW_RUM_EVENT_COLLECTED, { + notifyRawRumEvent(lifeCycle, { rawRumEvent: createRawRumEvent(RumEventType.VIEW), - startTime: 0 as RelativeTime, }) expect(serverRumEvents.length).toBe(1) }) @@ -505,9 +472,8 @@ describe('rum assembly', () => { const { lifeCycle } = setupBuilder.build() viewSessionId = '6789' - lifeCycle.notify(LifeCycleEventType.RAW_RUM_EVENT_COLLECTED, { + notifyRawRumEvent(lifeCycle, { rawRumEvent: createRawRumEvent(RumEventType.VIEW), - startTime: 0 as RelativeTime, }) expect(serverRumEvents.length).toBe(0) }) @@ -516,9 +482,8 @@ describe('rum assembly', () => { const { lifeCycle } = setupBuilder.build() viewSessionId = undefined - lifeCycle.notify(LifeCycleEventType.RAW_RUM_EVENT_COLLECTED, { + notifyRawRumEvent(lifeCycle, { rawRumEvent: createRawRumEvent(RumEventType.VIEW), - startTime: 0 as RelativeTime, }) expect(serverRumEvents.length).toBe(0) }) @@ -527,9 +492,8 @@ describe('rum assembly', () => { describe('session context', () => { it('should include the session type and id', () => { const { lifeCycle } = setupBuilder.build() - lifeCycle.notify(LifeCycleEventType.RAW_RUM_EVENT_COLLECTED, { + notifyRawRumEvent(lifeCycle, { rawRumEvent: createRawRumEvent(RumEventType.VIEW), - startTime: 0 as RelativeTime, }) expect(serverRumEvents[0].session).toEqual({ has_replay: undefined, @@ -542,9 +506,8 @@ describe('rum assembly', () => { const { lifeCycle } = setupBuilder.build() commonContext.hasReplay = true - lifeCycle.notify(LifeCycleEventType.RAW_RUM_EVENT_COLLECTED, { + notifyRawRumEvent(lifeCycle, { rawRumEvent: createRawRumEvent(RumEventType.ERROR), - startTime: 0 as RelativeTime, }) expect(serverRumEvents[0].session.has_replay).toBe(true) }) @@ -553,9 +516,8 @@ describe('rum assembly', () => { const { lifeCycle } = setupBuilder.build() commonContext.hasReplay = true - lifeCycle.notify(LifeCycleEventType.RAW_RUM_EVENT_COLLECTED, { + notifyRawRumEvent(lifeCycle, { rawRumEvent: createRawRumEvent(RumEventType.VIEW), - startTime: 0 as RelativeTime, }) expect(serverRumEvents[0].session.has_replay).toBe(undefined) }) @@ -622,10 +584,22 @@ describe('rum assembly', () => { function notifyRawRumErrorEvent(lifeCycle: LifeCycle, message = 'oh snap') { const rawRumEvent = createRawRumEvent(RumEventType.ERROR) as RawRumErrorEvent rawRumEvent.error.message = message - lifeCycle.notify(LifeCycleEventType.RAW_RUM_EVENT_COLLECTED, { + notifyRawRumEvent(lifeCycle, { rawRumEvent, - startTime: 0 as RelativeTime, }) } }) }) + +function notifyRawRumEvent( + lifeCycle: LifeCycle, + partialData: Omit, 'startTime' | 'domainContext'> & + Partial, 'startTime' | 'domainContext'>> +) { + const fullData = { + startTime: 0 as RelativeTime, + domainContext: {} as RumEventDomainContext, + ...partialData, + } + lifeCycle.notify(LifeCycleEventType.RAW_RUM_EVENT_COLLECTED, fullData) +} diff --git a/packages/rum-core/src/domain/assembly.ts b/packages/rum-core/src/domain/assembly.ts index 5219d2def8..56ba585e24 100644 --- a/packages/rum-core/src/domain/assembly.ts +++ b/packages/rum-core/src/domain/assembly.ts @@ -11,6 +11,7 @@ import { display, addMonitoringMessage, relativeNow, + BeforeSendCallback, } from '@datadog/browser-core' import { CommonContext, @@ -19,6 +20,7 @@ import { RawRumLongTaskEvent, RawRumResourceEvent, RumContext, + RumEventDomainContext, RumEventType, User, } from '../rawRumEvent.types' @@ -69,7 +71,7 @@ export function startRumAssembly( lifeCycle.subscribe( LifeCycleEventType.RAW_RUM_EVENT_COLLECTED, - ({ startTime, rawRumEvent, savedCommonContext, customerContext }) => { + ({ startTime, rawRumEvent, domainContext, savedCommonContext, customerContext }) => { const viewContext = parentContexts.findView(startTime) if (session.isTracked() && viewContext && viewContext.session.id === session.getId()) { const actionContext = parentContexts.findAction(startTime) @@ -103,7 +105,7 @@ export function startRumAssembly( if (!isEmptyObject(commonContext.user)) { ;(serverRumEvent.usr as Mutable) = commonContext.user as User & Context } - if (shouldSend(serverRumEvent, configuration.beforeSend, errorFilter)) { + if (shouldSend(serverRumEvent, configuration.beforeSend, domainContext, errorFilter)) { if (isEmptyObject(serverRumEvent.context)) { delete serverRumEvent.context } @@ -128,14 +130,15 @@ export function startRumAssembly( function shouldSend( event: RumEvent & Context, - beforeSend: ((event: any) => unknown) | undefined, + beforeSend: BeforeSendCallback | undefined, + domainContext: RumEventDomainContext, errorFilter: ErrorFilter ) { if (beforeSend) { const result = limitModification( event, event.type === RumEventType.VIEW ? VIEW_EVENTS_MODIFIABLE_FIELD_PATHS : OTHER_EVENTS_MODIFIABLE_FIELD_PATHS, - beforeSend + (event) => beforeSend(event, domainContext) ) if (result === false && event.type !== RumEventType.VIEW) { return false diff --git a/packages/rum-core/src/domain/lifeCycle.ts b/packages/rum-core/src/domain/lifeCycle.ts index 8b47bc93aa..9993488f66 100644 --- a/packages/rum-core/src/domain/lifeCycle.ts +++ b/packages/rum-core/src/domain/lifeCycle.ts @@ -1,6 +1,6 @@ import { Context, RawError, RelativeTime, Subscription } from '@datadog/browser-core' import { RumPerformanceEntry } from '../browser/performanceCollection' -import { CommonContext, RawRumEvent } from '../rawRumEvent.types' +import { CommonContext, RawRumEvent, RumEventDomainContext } from '../rawRumEvent.types' import { RumEvent } from '../rumEvent.types' import { RequestCompleteEvent, RequestStartEvent } from './requestCollection' import { AutoAction, AutoActionCreatedEvent } from './rumEventsCollection/action/trackActions' @@ -48,15 +48,7 @@ export class LifeCycle { | LifeCycleEventType.RECORD_STARTED | LifeCycleEventType.RECORD_STOPPED ): void - notify( - eventType: LifeCycleEventType.RAW_RUM_EVENT_COLLECTED, - data: { - startTime: RelativeTime - rawRumEvent: RawRumEvent - savedCommonContext?: CommonContext - customerContext?: Context - } - ): void + notify(eventType: LifeCycleEventType.RAW_RUM_EVENT_COLLECTED, data: RawRumEventCollectedData): void notify(eventType: LifeCycleEventType.RUM_EVENT_COLLECTED, data: RumEvent & Context): void notify(eventType: LifeCycleEventType, data?: any) { const eventCallbacks = this.callbacks[eventType] @@ -97,12 +89,7 @@ export class LifeCycle { ): Subscription subscribe( eventType: LifeCycleEventType.RAW_RUM_EVENT_COLLECTED, - callback: (data: { - startTime: RelativeTime - rawRumEvent: RawRumEvent - savedCommonContext?: CommonContext - customerContext?: Context - }) => void + callback: (data: RawRumEventCollectedData) => void ): Subscription subscribe( eventType: LifeCycleEventType.RUM_EVENT_COLLECTED, @@ -120,3 +107,11 @@ export class LifeCycle { } } } + +export interface RawRumEventCollectedData { + startTime: RelativeTime + savedCommonContext?: CommonContext + customerContext?: Context + rawRumEvent: E + domainContext: RumEventDomainContext +} diff --git a/packages/rum-core/src/domain/requestCollection.spec.ts b/packages/rum-core/src/domain/requestCollection.spec.ts index 67396850bc..338ebcb058 100644 --- a/packages/rum-core/src/domain/requestCollection.spec.ts +++ b/packages/rum-core/src/domain/requestCollection.spec.ts @@ -79,7 +79,7 @@ describe('collect fetch', () => { expect(request.method).toEqual('GET') expect(request.url).toEqual(FAKE_URL) expect(request.status).toEqual(500) - expect(request.response).toEqual('fetch error') + expect(request.responseText).toEqual('fetch error') done() }) }) @@ -174,7 +174,7 @@ describe('collect xhr', () => { expect(request.method).toEqual('GET') expect(request.url).toContain('/ok') expect(request.status).toEqual(200) - expect(request.response).toEqual('ok') + expect(request.responseText).toEqual('ok') done() }, }) diff --git a/packages/rum-core/src/domain/requestCollection.ts b/packages/rum-core/src/domain/requestCollection.ts index 7ea2d50b8a..fb02b860d7 100644 --- a/packages/rum-core/src/domain/requestCollection.ts +++ b/packages/rum-core/src/domain/requestCollection.ts @@ -34,12 +34,17 @@ export interface RequestCompleteEvent { method: string url: string status: number - response?: string + responseText?: string responseType?: string startClocks: ClocksState duration: Duration spanId?: TraceIdentifier traceId?: TraceIdentifier + xhr?: XMLHttpRequest + response?: Response + input?: RequestInfo + init?: RequestInit + error?: Error } let nextRequestIndex = 1 @@ -69,13 +74,14 @@ export function trackXhr(lifeCycle: LifeCycle, configuration: Configuration, tra duration: context.duration, method: context.method, requestIndex: context.requestIndex, - response: context.response, + responseText: context.responseText, spanId: context.spanId, startClocks: context.startClocks, status: context.status, traceId: context.traceId, type: RequestType.XHR, url: context.url, + xhr: context.xhr, }) } }) @@ -101,7 +107,7 @@ export function trackFetch(lifeCycle: LifeCycle, configuration: Configuration, t duration: context.duration, method: context.method, requestIndex: context.requestIndex, - response: context.response, + responseText: context.responseText, responseType: context.responseType, spanId: context.spanId, startClocks: context.startClocks, @@ -109,6 +115,9 @@ export function trackFetch(lifeCycle: LifeCycle, configuration: Configuration, t traceId: context.traceId, type: RequestType.FETCH, url: context.url, + response: context.response, + init: context.init, + input: context.input, }) } }) diff --git a/packages/rum-core/src/domain/rumEventsCollection/action/actionCollection.spec.ts b/packages/rum-core/src/domain/rumEventsCollection/action/actionCollection.spec.ts index 622602f4ba..85aaf7c556 100644 --- a/packages/rum-core/src/domain/rumEventsCollection/action/actionCollection.spec.ts +++ b/packages/rum-core/src/domain/rumEventsCollection/action/actionCollection.spec.ts @@ -1,4 +1,5 @@ import { Duration, RelativeTime, ServerDuration, TimeStamp } from '@datadog/browser-core' +import { createNewEvent } from '../../../../../core/test/specHelper' import { setup, TestSetupBuilder } from '../../../../test/specHelper' import { RumEventType, ActionType } from '../../../rawRumEvent.types' import { LifeCycleEventType } from '../../lifeCycle' @@ -26,6 +27,7 @@ describe('actionCollection', () => { }) it('should create action from auto action', () => { const { lifeCycle, rawRumEvents } = setupBuilder.build() + const event = createNewEvent('click') lifeCycle.notify(LifeCycleEventType.AUTO_ACTION_COMPLETED, { counts: { errorCount: 10, @@ -37,9 +39,10 @@ describe('actionCollection', () => { name: 'foo', startClocks: { relative: 1234 as RelativeTime, timeStamp: 123456789 as TimeStamp }, type: ActionType.CLICK, + event, }) - expect(rawRumEvents[0].startTime).toBe(1234) + expect(rawRumEvents[0].startTime).toBe(1234 as RelativeTime) expect(rawRumEvents[0].rawRumEvent).toEqual({ action: { error: { @@ -64,6 +67,9 @@ describe('actionCollection', () => { in_foreground: true, }, }) + expect(rawRumEvents[0].domainContext).toEqual({ + event, + }) }) it('should create action from custom action', () => { @@ -74,7 +80,7 @@ describe('actionCollection', () => { type: ActionType.CUSTOM, }) - expect(rawRumEvents[0].startTime).toBe(1234) + expect(rawRumEvents[0].startTime).toBe(1234 as RelativeTime) expect(rawRumEvents[0].rawRumEvent).toEqual({ action: { id: jasmine.any(String), @@ -89,5 +95,6 @@ describe('actionCollection', () => { in_foreground: true, }, }) + expect(rawRumEvents[0].domainContext).toEqual({}) }) }) diff --git a/packages/rum-core/src/domain/rumEventsCollection/action/actionCollection.ts b/packages/rum-core/src/domain/rumEventsCollection/action/actionCollection.ts index 4ecde2f578..12f846dce3 100644 --- a/packages/rum-core/src/domain/rumEventsCollection/action/actionCollection.ts +++ b/packages/rum-core/src/domain/rumEventsCollection/action/actionCollection.ts @@ -1,6 +1,6 @@ import { combine, Configuration, toServerDuration, generateUUID } from '@datadog/browser-core' import { ActionType, CommonContext, RumEventType, RawRumActionEvent } from '../../../rawRumEvent.types' -import { LifeCycle, LifeCycleEventType } from '../../lifeCycle' +import { LifeCycle, LifeCycleEventType, RawRumEventCollectedData } from '../../lifeCycle' import { DOMMutationObservable } from '../../../browser/domMutationObservable' import { ForegroundContexts } from '../../foregroundContexts' import { AutoAction, CustomAction, trackActions } from './trackActions' @@ -29,7 +29,10 @@ export function startActionCollection( } } -function processAction(action: AutoAction | CustomAction, foregroundContexts: ForegroundContexts) { +function processAction( + action: AutoAction | CustomAction, + foregroundContexts: ForegroundContexts +): RawRumEventCollectedData { const autoActionProperties = isAutoAction(action) ? { action: { @@ -70,6 +73,7 @@ function processAction(action: AutoAction | CustomAction, foregroundContexts: Fo customerContext, rawRumEvent: actionEvent, startTime: action.startClocks.relative, + domainContext: isAutoAction(action) ? { event: action.event } : {}, } } diff --git a/packages/rum-core/src/domain/rumEventsCollection/action/trackActions.spec.ts b/packages/rum-core/src/domain/rumEventsCollection/action/trackActions.spec.ts index 0452836edb..c0a8595fcc 100644 --- a/packages/rum-core/src/domain/rumEventsCollection/action/trackActions.spec.ts +++ b/packages/rum-core/src/domain/rumEventsCollection/action/trackActions.spec.ts @@ -1,5 +1,5 @@ import { Context, DOM_EVENT, ClocksState, Observable } from '@datadog/browser-core' -import { Clock } from '../../../../../core/test/specHelper' +import { Clock, createNewEvent } from '../../../../../core/test/specHelper' import { RumEvent } from '../../../../../rum/src' import { setup, TestSetupBuilder } from '../../../../test/specHelper' import { RumEventType, ActionType } from '../../../rawRumEvent.types' @@ -43,7 +43,7 @@ describe('trackActions', () => { }) clock.tick(SOME_ARBITRARY_DELAY) - target.click() + target.dispatchEvent(createNewEvent('click')) } beforeEach(() => { @@ -108,6 +108,7 @@ describe('trackActions', () => { name: 'Click me', startClocks: jasmine.any(Object), type: ActionType.CLICK, + event: createNewEvent('click'), }, ]) }) diff --git a/packages/rum-core/src/domain/rumEventsCollection/action/trackActions.ts b/packages/rum-core/src/domain/rumEventsCollection/action/trackActions.ts index 09df48c2be..85d19d29d3 100644 --- a/packages/rum-core/src/domain/rumEventsCollection/action/trackActions.ts +++ b/packages/rum-core/src/domain/rumEventsCollection/action/trackActions.ts @@ -38,6 +38,7 @@ export interface AutoAction { startClocks: ClocksState duration: Duration counts: ActionCounts + event: Event } export interface AutoActionCreatedEvent { @@ -65,7 +66,7 @@ export function trackActions(lifeCycle: LifeCycle, domMutationObservable: DOMMut return } - action.create(ActionType.CLICK, name) + action.create(ActionType.CLICK, name, event) }, { capture: true } ) @@ -83,12 +84,12 @@ function startActionManagement(lifeCycle: LifeCycle, domMutationObservable: DOMM let currentIdlePageActivitySubscription: { stop: () => void } return { - create: (type: AutoActionType, name: string) => { + create: (type: AutoActionType, name: string, event: Event) => { if (currentAction) { // Ignore any new action if another one is already occurring. return } - const pendingAutoAction = new PendingAutoAction(lifeCycle, type, name) + const pendingAutoAction = new PendingAutoAction(lifeCycle, type, name, event) currentAction = pendingAutoAction currentIdlePageActivitySubscription = waitIdlePageActivity(lifeCycle, domMutationObservable, (params) => { @@ -115,7 +116,7 @@ class PendingAutoAction { private startClocks: ClocksState private eventCountsSubscription: { eventCounts: EventCounts; stop(): void } - constructor(private lifeCycle: LifeCycle, private type: AutoActionType, private name: string) { + constructor(private lifeCycle: LifeCycle, private type: AutoActionType, private name: string, private event: Event) { this.id = generateUUID() this.startClocks = clocksNow() this.eventCountsSubscription = trackEventCounts(lifeCycle) @@ -135,6 +136,7 @@ class PendingAutoAction { name: this.name, startClocks: this.startClocks, type: this.type, + event: this.event, }) this.eventCountsSubscription.stop() } diff --git a/packages/rum-core/src/domain/rumEventsCollection/error/errorCollection.spec.ts b/packages/rum-core/src/domain/rumEventsCollection/error/errorCollection.spec.ts index 142f89fd20..80879d40af 100644 --- a/packages/rum-core/src/domain/rumEventsCollection/error/errorCollection.spec.ts +++ b/packages/rum-core/src/domain/rumEventsCollection/error/errorCollection.spec.ts @@ -28,8 +28,9 @@ describe('error collection', () => { describe('provided', () => { it('notifies a raw rum error event', () => { const { rawRumEvents } = setupBuilder.build() + const error = new Error('foo') addError({ - error: new Error('foo'), + error, source: ErrorSource.CUSTOM, startClocks: { relative: 1234 as RelativeTime, timeStamp: 123456789 as TimeStamp }, }) @@ -53,7 +54,8 @@ describe('error collection', () => { }, }, savedCommonContext: undefined, - startTime: 1234, + startTime: 1234 as RelativeTime, + domainContext: { error }, }) }) @@ -99,11 +101,24 @@ describe('error collection', () => { id: 'foo', }) }) + + it('should include non-Error values in domain context', () => { + const { rawRumEvents } = setupBuilder.build() + addError({ + error: { foo: 'bar' }, + source: ErrorSource.CUSTOM, + startClocks: { relative: 1234 as RelativeTime, timeStamp: 123456789 as TimeStamp }, + }) + expect(rawRumEvents[0].domainContext).toEqual({ + error: { foo: 'bar' }, + }) + }) }) describe('RAW_ERROR_COLLECTED LifeCycle event', () => { it('should create error event from collected error', () => { const { rawRumEvents, lifeCycle } = setupBuilder.build() + const error = new Error('hello') lifeCycle.notify(LifeCycleEventType.RAW_ERROR_COLLECTED, { error: { message: 'hello', @@ -116,10 +131,11 @@ describe('error collection', () => { stack: 'bar', startClocks: { relative: 1234 as RelativeTime, timeStamp: 123456789 as TimeStamp }, type: 'foo', + originalError: error, }, }) - expect(rawRumEvents[0].startTime).toBe(1234) + expect(rawRumEvents[0].startTime).toBe(1234 as RelativeTime) expect(rawRumEvents[0].rawRumEvent).toEqual({ date: jasmine.any(Number), error: { @@ -139,6 +155,9 @@ describe('error collection', () => { }, type: RumEventType.ERROR, }) + expect(rawRumEvents[0].domainContext).toEqual({ + error, + }) }) }) }) diff --git a/packages/rum-core/src/domain/rumEventsCollection/error/errorCollection.ts b/packages/rum-core/src/domain/rumEventsCollection/error/errorCollection.ts index 63fba6a13e..05c73a1b7d 100644 --- a/packages/rum-core/src/domain/rumEventsCollection/error/errorCollection.ts +++ b/packages/rum-core/src/domain/rumEventsCollection/error/errorCollection.ts @@ -9,7 +9,7 @@ import { generateUUID, } from '@datadog/browser-core' import { CommonContext, RawRumErrorEvent, RumEventType } from '../../../rawRumEvent.types' -import { LifeCycle, LifeCycleEventType } from '../../lifeCycle' +import { LifeCycle, LifeCycleEventType, RawRumEventCollectedData } from '../../lifeCycle' import { ForegroundContexts } from '../../foregroundContexts' export interface ProvidedError { @@ -59,10 +59,13 @@ export function doStartErrorCollection(lifeCycle: LifeCycle, foregroundContexts: function computeRawError(error: unknown, startClocks: ClocksState, source: ProvidedSource): RawError { const stackTrace = error instanceof Error ? computeStackTrace(error) : undefined - return { startClocks, source, ...formatUnknownError(stackTrace, error, 'Provided') } + return { startClocks, source, originalError: error, ...formatUnknownError(stackTrace, error, 'Provided') } } -function processError(error: RawError, foregroundContexts: ForegroundContexts) { +function processError( + error: RawError, + foregroundContexts: ForegroundContexts +): RawRumEventCollectedData { const rawRumEvent: RawRumErrorEvent = { date: error.startClocks.timeStamp, error: { @@ -89,5 +92,8 @@ function processError(error: RawError, foregroundContexts: ForegroundContexts) { return { rawRumEvent, startTime: error.startClocks.relative, + domainContext: { + error: error.originalError, + }, } } diff --git a/packages/rum-core/src/domain/rumEventsCollection/longTask/longTaskCollection.spec.ts b/packages/rum-core/src/domain/rumEventsCollection/longTask/longTaskCollection.spec.ts index abe04369b7..a7105dcb1b 100644 --- a/packages/rum-core/src/domain/rumEventsCollection/longTask/longTaskCollection.spec.ts +++ b/packages/rum-core/src/domain/rumEventsCollection/longTask/longTaskCollection.spec.ts @@ -1,10 +1,19 @@ import { Duration, RelativeTime, ServerDuration } from '@datadog/browser-core' import { setup, TestSetupBuilder } from '../../../../test/specHelper' -import { RumPerformanceEntry } from '../../../browser/performanceCollection' +import { RumPerformanceEntry, RumPerformanceLongTaskTiming } from '../../../browser/performanceCollection' import { RumEventType } from '../../../rawRumEvent.types' import { LifeCycleEventType } from '../../lifeCycle' import { startLongTaskCollection } from './longTaskCollection' +const LONG_TASK: RumPerformanceLongTaskTiming = { + duration: 100 as Duration, + entryType: 'longtask', + startTime: 1234 as RelativeTime, + toJSON() { + return { name: 'self', duration: 100, entryType: 'longtask', startTime: 1234 } + }, +} + describe('long task collection', () => { let setupBuilder: TestSetupBuilder @@ -25,7 +34,7 @@ describe('long task collection', () => { it('should only listen to long task performance entry', () => { const { lifeCycle, rawRumEvents } = setupBuilder.build() ;[ - { duration: 100 as Duration, entryType: 'longtask', startTime: 1234 }, + LONG_TASK, { duration: 100 as Duration, entryType: 'navigation', startTime: 1234 }, { duration: 100 as Duration, entryType: 'resource', startTime: 1234 }, { duration: 100 as Duration, entryType: 'paint', startTime: 1234 }, @@ -37,13 +46,9 @@ describe('long task collection', () => { it('should create raw rum event from performance entry', () => { const { lifeCycle, rawRumEvents } = setupBuilder.build() - lifeCycle.notify(LifeCycleEventType.PERFORMANCE_ENTRY_COLLECTED, { - duration: 100 as Duration, - entryType: 'longtask', - startTime: 1234 as RelativeTime, - }) + lifeCycle.notify(LifeCycleEventType.PERFORMANCE_ENTRY_COLLECTED, LONG_TASK) - expect(rawRumEvents[0].startTime).toBe(1234) + expect(rawRumEvents[0].startTime).toBe(1234 as RelativeTime) expect(rawRumEvents[0].rawRumEvent).toEqual({ date: jasmine.any(Number), long_task: { @@ -52,5 +57,8 @@ describe('long task collection', () => { }, type: RumEventType.LONG_TASK, }) + expect(rawRumEvents[0].domainContext).toEqual({ + performanceEntry: { name: 'self', duration: 100, entryType: 'longtask', startTime: 1234 }, + }) }) }) diff --git a/packages/rum-core/src/domain/rumEventsCollection/longTask/longTaskCollection.ts b/packages/rum-core/src/domain/rumEventsCollection/longTask/longTaskCollection.ts index b81982b83a..a4a2c6e2b7 100644 --- a/packages/rum-core/src/domain/rumEventsCollection/longTask/longTaskCollection.ts +++ b/packages/rum-core/src/domain/rumEventsCollection/longTask/longTaskCollection.ts @@ -19,6 +19,7 @@ export function startLongTaskCollection(lifeCycle: LifeCycle) { lifeCycle.notify(LifeCycleEventType.RAW_RUM_EVENT_COLLECTED, { rawRumEvent, startTime: startClocks.relative, + domainContext: { performanceEntry: entry.toJSON() }, }) }) } diff --git a/packages/rum-core/src/domain/rumEventsCollection/resource/resourceCollection.spec.ts b/packages/rum-core/src/domain/rumEventsCollection/resource/resourceCollection.spec.ts index 29d7befc2c..dbfd39a55c 100644 --- a/packages/rum-core/src/domain/rumEventsCollection/resource/resourceCollection.spec.ts +++ b/packages/rum-core/src/domain/rumEventsCollection/resource/resourceCollection.spec.ts @@ -1,4 +1,5 @@ import { Duration, RelativeTime, RequestType, ResourceType, ServerDuration, TimeStamp } from '@datadog/browser-core' +import { isIE } from '../../../../../core/test/specHelper' import { createResourceEntry } from '../../../../test/fixtures' import { setup, TestSetupBuilder } from '../../../../test/specHelper' import { RawRumResourceEvent, RumEventType } from '../../../rawRumEvent.types' @@ -32,16 +33,14 @@ describe('resourceCollection', () => { it('should create resource from performance entry', () => { const { lifeCycle, rawRumEvents } = setupBuilder.build() - lifeCycle.notify( - LifeCycleEventType.PERFORMANCE_ENTRY_COLLECTED, - createResourceEntry({ - duration: 100 as Duration, - name: 'https://resource.com/valid', - startTime: 1234 as RelativeTime, - }) - ) + const performanceEntry = createResourceEntry({ + duration: 100 as Duration, + name: 'https://resource.com/valid', + startTime: 1234 as RelativeTime, + }) + lifeCycle.notify(LifeCycleEventType.PERFORMANCE_ENTRY_COLLECTED, performanceEntry) - expect(rawRumEvents[0].startTime).toBe(1234) + expect(rawRumEvents[0].startTime).toBe(1234 as RelativeTime) expect(rawRumEvents[0].rawRumEvent).toEqual({ date: (jasmine.any(Number) as unknown) as TimeStamp, resource: { @@ -53,10 +52,14 @@ describe('resourceCollection', () => { }, type: RumEventType.RESOURCE, }) + expect(rawRumEvents[0].domainContext).toEqual({ + performanceEntry, + }) }) - it('should create resource from completed request', () => { + it('should create resource from completed XHR request', () => { const { lifeCycle, rawRumEvents } = setupBuilder.build() + const xhr = new XMLHttpRequest() lifeCycle.notify( LifeCycleEventType.REQUEST_COMPLETED, createCompletedRequest({ @@ -66,10 +69,11 @@ describe('resourceCollection', () => { status: 200, type: RequestType.XHR, url: 'https://resource.com/valid', + xhr, }) ) - expect(rawRumEvents[0].startTime).toBe(1234) + expect(rawRumEvents[0].startTime).toBe(1234 as RelativeTime) expect(rawRumEvents[0].rawRumEvent).toEqual({ date: jasmine.any(Number), resource: { @@ -82,6 +86,70 @@ describe('resourceCollection', () => { }, type: RumEventType.RESOURCE, }) + expect(rawRumEvents[0].domainContext).toEqual({ + xhr, + performanceEntry: undefined, + response: undefined, + requestInput: undefined, + requestInit: undefined, + error: undefined, + }) + }) + + it('should create resource 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({ + duration: 100 as Duration, + method: 'GET', + startClocks: { relative: 1234 as RelativeTime, timeStamp: 123456789 as TimeStamp }, + status: 200, + type: RequestType.FETCH, + url: 'https://resource.com/valid', + response, + input: 'https://resource.com/valid', + init: { headers: { foo: 'bar' } }, + }) + ) + + expect(rawRumEvents[0].startTime).toBe(1234 as RelativeTime) + expect(rawRumEvents[0].rawRumEvent).toEqual({ + date: jasmine.any(Number), + resource: { + id: jasmine.any(String), + duration: (100 * 1e6) as ServerDuration, + method: 'GET', + status_code: 200, + type: ResourceType.FETCH, + url: 'https://resource.com/valid', + }, + type: RumEventType.RESOURCE, + }) + expect(rawRumEvents[0].domainContext).toEqual({ + performanceEntry: undefined, + xhr: undefined, + response, + requestInput: 'https://resource.com/valid', + requestInit: { headers: { foo: 'bar' } }, + error: undefined, + }) + }) + + it('should include the error in failed fetch requests', () => { + const { lifeCycle, rawRumEvents } = setupBuilder.build() + const error = new Error() + lifeCycle.notify(LifeCycleEventType.REQUEST_COMPLETED, createCompletedRequest({ error })) + + expect(rawRumEvents[0].domainContext).toEqual( + jasmine.objectContaining({ + error, + }) + ) }) }) diff --git a/packages/rum-core/src/domain/rumEventsCollection/resource/resourceCollection.ts b/packages/rum-core/src/domain/rumEventsCollection/resource/resourceCollection.ts index a7e03da104..9aab09d5f8 100644 --- a/packages/rum-core/src/domain/rumEventsCollection/resource/resourceCollection.ts +++ b/packages/rum-core/src/domain/rumEventsCollection/resource/resourceCollection.ts @@ -8,7 +8,7 @@ import { } from '@datadog/browser-core' import { RumPerformanceResourceTiming } from '../../../browser/performanceCollection' import { RawRumResourceEvent, RumEventType } from '../../../rawRumEvent.types' -import { LifeCycle, LifeCycleEventType } from '../../lifeCycle' +import { LifeCycle, LifeCycleEventType, RawRumEventCollectedData } from '../../lifeCycle' import { RequestCompleteEvent } from '../../requestCollection' import { RumSession } from '../../rumSession' import { matchRequestTiming } from './matchRequestTiming' @@ -34,7 +34,7 @@ export function startResourceCollection(lifeCycle: LifeCycle, session: RumSessio }) } -function processRequest(request: RequestCompleteEvent) { +function processRequest(request: RequestCompleteEvent): RawRumEventCollectedData { const type = request.type === RequestType.XHR ? ResourceType.XHR : ResourceType.FETCH const matchingTiming = matchRequestTiming(request) @@ -54,15 +54,26 @@ function processRequest(request: RequestCompleteEvent) { status_code: request.status, url: request.url, }, - type: RumEventType.RESOURCE, + type: RumEventType.RESOURCE as const, }, tracingInfo, correspondingTimingOverrides ) - return { startTime: startClocks.relative, rawRumEvent: resourceEvent as RawRumResourceEvent } + return { + startTime: startClocks.relative, + rawRumEvent: resourceEvent, + domainContext: { + performanceEntry: matchingTiming instanceof PerformanceEntry ? matchingTiming.toJSON() : matchingTiming, + xhr: request.xhr, + response: request.response, + requestInput: request.input, + requestInit: request.init, + error: request.error, + }, + } } -function processResourceEntry(entry: RumPerformanceResourceTiming) { +function processResourceEntry(entry: RumPerformanceResourceTiming): RawRumEventCollectedData { const type = computeResourceKind(entry) const entryMetrics = computePerformanceEntryMetrics(entry) const tracingInfo = computeEntryTracingInfo(entry) @@ -76,12 +87,18 @@ function processResourceEntry(entry: RumPerformanceResourceTiming) { type, url: entry.name, }, - type: RumEventType.RESOURCE, + type: RumEventType.RESOURCE as const, }, tracingInfo, entryMetrics ) - return { startTime: startClocks.relative, rawRumEvent: resourceEvent as RawRumResourceEvent } + return { + startTime: startClocks.relative, + rawRumEvent: resourceEvent, + domainContext: { + performanceEntry: entry instanceof PerformanceEntry ? entry.toJSON() : entry, + }, + } } function computePerformanceEntryMetrics(timing: RumPerformanceResourceTiming) { diff --git a/packages/rum-core/src/domain/rumEventsCollection/view/trackViews.ts b/packages/rum-core/src/domain/rumEventsCollection/view/trackViews.ts index 00c3695249..f4e50915e9 100644 --- a/packages/rum-core/src/domain/rumEventsCollection/view/trackViews.ts +++ b/packages/rum-core/src/domain/rumEventsCollection/view/trackViews.ts @@ -24,7 +24,7 @@ import { trackViewMetrics } from './trackViewMetrics' export interface ViewEvent { id: string name?: string - location: Location + location: Readonly referrer: string timings: Timings customTimings: ViewCustomTimings @@ -201,7 +201,7 @@ function newView( const customTimings: ViewCustomTimings = {} let documentVersion = 0 let endClocks: ClocksState | undefined - let location: Location = { ...initialLocation } + let location = { ...initialLocation } let hasReplay = initialHasReplay lifeCycle.notify(LifeCycleEventType.VIEW_CREATED, { id, name, startClocks, location, referrer }) diff --git a/packages/rum-core/src/domain/rumEventsCollection/view/viewCollection.spec.ts b/packages/rum-core/src/domain/rumEventsCollection/view/viewCollection.spec.ts index 2a589b1ef5..d46901a825 100644 --- a/packages/rum-core/src/domain/rumEventsCollection/view/viewCollection.spec.ts +++ b/packages/rum-core/src/domain/rumEventsCollection/view/viewCollection.spec.ts @@ -64,7 +64,7 @@ describe('viewCollection', () => { } lifeCycle.notify(LifeCycleEventType.VIEW_UPDATED, view) - expect(rawRumEvents[rawRumEvents.length - 1].startTime).toBe(1234) + expect(rawRumEvents[rawRumEvents.length - 1].startTime).toBe(1234 as RelativeTime) expect(rawRumEvents[rawRumEvents.length - 1].rawRumEvent).toEqual({ _dd: { document_version: 3, diff --git a/packages/rum-core/src/domain/rumEventsCollection/view/viewCollection.ts b/packages/rum-core/src/domain/rumEventsCollection/view/viewCollection.ts index dacc284bc9..9179bcb4d3 100644 --- a/packages/rum-core/src/domain/rumEventsCollection/view/viewCollection.ts +++ b/packages/rum-core/src/domain/rumEventsCollection/view/viewCollection.ts @@ -7,7 +7,7 @@ import { Configuration, } from '@datadog/browser-core' import { RawRumViewEvent, RumEventType } from '../../../rawRumEvent.types' -import { LifeCycle, LifeCycleEventType } from '../../lifeCycle' +import { LifeCycle, LifeCycleEventType, RawRumEventCollectedData } from '../../lifeCycle' import { DOMMutationObservable } from '../../../browser/domMutationObservable' import { ForegroundContexts } from '../../foregroundContexts' import { trackViews, ViewEvent } from './trackViews' @@ -28,7 +28,10 @@ export function startViewCollection( return trackViews(location, lifeCycle, domMutationObservable, shouldTrackViewsAutomatically, initialViewName) } -function processViewUpdate(view: ViewEvent, foregroundContexts: ForegroundContexts) { +function processViewUpdate( + view: ViewEvent, + foregroundContexts: ForegroundContexts +): RawRumEventCollectedData { const viewEvent: RawRumViewEvent = { _dd: { document_version: view.documentVersion, @@ -77,5 +80,8 @@ function processViewUpdate(view: ViewEvent, foregroundContexts: ForegroundContex return { rawRumEvent: viewEvent, startTime: view.startClocks.relative, + domainContext: { + location: view.location, + }, } } diff --git a/packages/rum-core/src/index.ts b/packages/rum-core/src/index.ts index 2a2fd62aa0..a3c21da98e 100644 --- a/packages/rum-core/src/index.ts +++ b/packages/rum-core/src/index.ts @@ -9,7 +9,18 @@ export { RumResourceEvent, RumLongTaskEvent, } from './rumEvent.types' -export { ViewContext, CommonContext } from './rawRumEvent.types' +export { + ViewContext, + CommonContext, + RumEventDomainContext, + RumViewEventDomainContext, + RumErrorEventDomainContext, + RumActionEventDomainContext, + RumFetchResourceEventDomainContext, + RumXhrResourceEventDomainContext, + RumOtherResourceEventDomainContext, + RumLongTaskEventDomainContext, +} from './rawRumEvent.types' export { startRum } from './boot/startRum' export { LifeCycle, LifeCycleEventType } from './domain/lifeCycle' export { ParentContexts } from './domain/parentContexts' diff --git a/packages/rum-core/src/rawRumEvent.types.ts b/packages/rum-core/src/rawRumEvent.types.ts index 1d7cdb7dc4..777a0562ed 100644 --- a/packages/rum-core/src/rawRumEvent.types.ts +++ b/packages/rum-core/src/rawRumEvent.types.ts @@ -1,4 +1,4 @@ -import { Context, Duration, ErrorSource, ResourceType, ServerDuration, TimeStamp } from '@datadog/browser-core' +import { Context, Duration, ErrorSource, ResourceType, ServerDuration, TimeStamp, Omit } from '@datadog/browser-core' export enum RumEventType { ACTION = 'action', @@ -209,3 +209,49 @@ export interface CommonContext { context: Context hasReplay?: true } + +export type RumEventDomainContext = T extends RumEventType.VIEW + ? RumViewEventDomainContext + : T extends RumEventType.ACTION + ? RumActionEventDomainContext + : T extends RumEventType.RESOURCE + ? RumFetchResourceEventDomainContext | RumXhrResourceEventDomainContext | RumOtherResourceEventDomainContext + : T extends RumEventType.ERROR + ? RumErrorEventDomainContext + : T extends RumEventType.LONG_TASK + ? RumLongTaskEventDomainContext + : never + +export interface RumViewEventDomainContext { + location: Readonly +} +export interface RumActionEventDomainContext { + event?: Event +} +export interface RumFetchResourceEventDomainContext { + requestInit?: RequestInit + requestInput: RequestInfo + response?: Response + error?: Error + performanceEntry?: PerformanceEntryRepresentation +} +export interface RumXhrResourceEventDomainContext { + xhr: XMLHttpRequest + performanceEntry?: PerformanceEntryRepresentation +} +export interface RumOtherResourceEventDomainContext { + performanceEntry: PerformanceEntryRepresentation +} +export interface RumErrorEventDomainContext { + error: unknown +} +export interface RumLongTaskEventDomainContext { + performanceEntry: PerformanceEntryRepresentation +} + +/** + * Symbolizes the type of the value returned by performanceEntry.toJSON(). Can also be built + * manually to represent other kind of performance entries (ex: initial_document) or polyfilled + * based on `performance.timing`. + */ +export type PerformanceEntryRepresentation = Omit diff --git a/packages/rum-core/test/specHelper.ts b/packages/rum-core/test/specHelper.ts index 360a972d35..e2a88cbe06 100644 --- a/packages/rum-core/test/specHelper.ts +++ b/packages/rum-core/test/specHelper.ts @@ -3,7 +3,6 @@ import { buildUrl, combine, Configuration, - Context, DEFAULT_CONFIGURATION, Observable, TimeStamp, @@ -11,11 +10,11 @@ import { } from '@datadog/browser-core' import { SPEC_ENDPOINTS, mockClock, Clock } from '../../core/test/specHelper' import { ForegroundContexts } from '../src/domain/foregroundContexts' -import { LifeCycle, LifeCycleEventType } from '../src/domain/lifeCycle' +import { LifeCycle, LifeCycleEventType, RawRumEventCollectedData } from '../src/domain/lifeCycle' import { ParentContexts } from '../src/domain/parentContexts' import { trackViews, ViewEvent } from '../src/domain/rumEventsCollection/view/trackViews' import { RumSession } from '../src/domain/rumSession' -import { CommonContext, RawRumEvent, RumContext, ViewContext } from '../src/rawRumEvent.types' +import { RawRumEvent, RumContext, ViewContext } from '../src/rawRumEvent.types' import { validateFormat } from './formatValidation' export interface TestSetupBuilder { @@ -49,12 +48,7 @@ export interface TestIO { clock: Clock fakeLocation: Partial session: RumSession - rawRumEvents: Array<{ - startTime: number - rawRumEvent: RawRumEvent - savedCommonContext?: CommonContext - customerContext?: Context - }> + rawRumEvents: RawRumEventCollectedData[] } export function setup(): TestSetupBuilder { @@ -67,12 +61,7 @@ export function setup(): TestSetupBuilder { const domMutationObservable = new Observable() const cleanupTasks: Array<() => void> = [] const beforeBuildTasks: BeforeBuildCallback[] = [] - const rawRumEvents: Array<{ - startTime: number - rawRumEvent: RawRumEvent - savedGlobalContext?: Context - customerContext?: Context - }> = [] + const rawRumEvents: RawRumEventCollectedData[] = [] let clock: Clock let fakeLocation: Partial = location diff --git a/packages/rum-recorder/src/index.ts b/packages/rum-recorder/src/index.ts index 4406a48699..3c587427b7 100644 --- a/packages/rum-recorder/src/index.ts +++ b/packages/rum-recorder/src/index.ts @@ -11,6 +11,15 @@ export { RumLongTaskEvent, RumResourceEvent, RumViewEvent, + // Events context + RumEventDomainContext, + RumViewEventDomainContext, + RumErrorEventDomainContext, + RumActionEventDomainContext, + RumFetchResourceEventDomainContext, + RumXhrResourceEventDomainContext, + RumOtherResourceEventDomainContext, + RumLongTaskEventDomainContext, } from '@datadog/browser-rum-core' export { RumRecorderPublicApi as RumGlobal, RumRecorderUserConfiguration } from './boot/rumRecorderPublicApi' diff --git a/packages/rum/src/index.ts b/packages/rum/src/index.ts index 543c091dc0..6dad21c6c5 100644 --- a/packages/rum/src/index.ts +++ b/packages/rum/src/index.ts @@ -12,4 +12,13 @@ export { RumLongTaskEvent, RumResourceEvent, RumViewEvent, + // Events context + RumEventDomainContext, + RumViewEventDomainContext, + RumErrorEventDomainContext, + RumActionEventDomainContext, + RumFetchResourceEventDomainContext, + RumXhrResourceEventDomainContext, + RumOtherResourceEventDomainContext, + RumLongTaskEventDomainContext, } from '@datadog/browser-rum-core'