Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions packages/core/src/transport/eventBridge.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ export interface BrowserWindowWithEventBridge extends Window {
export interface DatadogEventBridge {
getCapabilities?(): string
getPrivacyLevel?(): DefaultPrivacyLevel
getIsTraceSampled?(): boolean | null
getAllowedWebViewHosts(): string
send(msg: string): void
}
Expand All @@ -30,6 +31,12 @@ export function getEventBridge<T, E>() {
getPrivacyLevel() {
return eventBridgeGlobal.getPrivacyLevel?.()
},
getIsTraceSampled() {
const value = eventBridgeGlobal.getIsTraceSampled?.()
if (value === true || value === false) {
return value
}
},
getAllowedWebViewHosts() {
return JSON.parse(eventBridgeGlobal.getAllowedWebViewHosts()) as string[]
},
Expand Down
9 changes: 8 additions & 1 deletion packages/core/test/emulate/mockEventBridge.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,19 @@ export function mockEventBridge({
allowedWebViewHosts = [window.location.hostname],
privacyLevel = DefaultPrivacyLevel.MASK,
capabilities = [BridgeCapability.RECORDS],
}: { allowedWebViewHosts?: string[]; privacyLevel?: DefaultPrivacyLevel; capabilities?: BridgeCapability[] } = {}) {
isTraceSampled,
}: {
allowedWebViewHosts?: string[]
privacyLevel?: DefaultPrivacyLevel
capabilities?: BridgeCapability[]
isTraceSampled?: boolean
} = {}) {
const eventBridge: DatadogEventBridge = {
send: (_msg: string) => undefined,
getAllowedWebViewHosts: () => JSON.stringify(allowedWebViewHosts),
getCapabilities: () => JSON.stringify(capabilities),
getPrivacyLevel: () => privacyLevel,
getIsTraceSampled: isTraceSampled !== undefined ? () => isTraceSampled : undefined,
}

;(window as BrowserWindowWithEventBridge).DatadogEventBridge = eventBridge
Expand Down
21 changes: 21 additions & 0 deletions packages/rum-core/src/boot/preStartRum.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,27 @@ describe('preStartRum', () => {
)
})

it('should set traceSampleRate to 100 when the bridge reports trace is sampled', () => {
mockEventBridge({ isTraceSampled: true })
const hybridInitConfiguration: HybridInitConfiguration = {}
strategy.init(hybridInitConfiguration as RumInitConfiguration, PUBLIC_API)
expect((strategy.initConfiguration as RumInitConfiguration)?.traceSampleRate).toEqual(100)
})

it('should set traceSampleRate to 0 when the bridge reports trace is not sampled', () => {
mockEventBridge({ isTraceSampled: false })
const hybridInitConfiguration: HybridInitConfiguration = {}
strategy.init(hybridInitConfiguration as RumInitConfiguration, PUBLIC_API)
expect((strategy.initConfiguration as RumInitConfiguration)?.traceSampleRate).toEqual(0)
})

it('should preserve the original traceSampleRate when the bridge does not provide isTraceSampled', () => {
mockEventBridge()
const hybridInitConfiguration: HybridInitConfiguration = { traceSampleRate: 50 }
strategy.init(hybridInitConfiguration as RumInitConfiguration, PUBLIC_API)
expect((strategy.initConfiguration as RumInitConfiguration)?.traceSampleRate).toEqual(50)
})

it('should initialize even if session cannot be handled', () => {
mockEventBridge()
spyOnProperty(document, 'cookie', 'get').and.returnValue('')
Expand Down
5 changes: 4 additions & 1 deletion packages/rum-core/src/boot/preStartRum.ts
Original file line number Diff line number Diff line change
Expand Up @@ -326,12 +326,15 @@ export function createPreStartStrategy(
}

function overrideInitConfigurationForBridge(initConfiguration: RumInitConfiguration): RumInitConfiguration {
const bridge = getEventBridge()
const isTraceSampled = bridge?.getIsTraceSampled()
return {
...initConfiguration,
applicationId: '00000000-aaaa-0000-aaaa-000000000000',
clientToken: 'empty',
sessionSampleRate: 100,
defaultPrivacyLevel: initConfiguration.defaultPrivacyLevel ?? getEventBridge()?.getPrivacyLevel(),
defaultPrivacyLevel: initConfiguration.defaultPrivacyLevel ?? bridge?.getPrivacyLevel(),
traceSampleRate: isTraceSampled !== undefined ? (isTraceSampled ? 100 : 0) : initConfiguration.traceSampleRate,
}
}

Expand Down
8 changes: 4 additions & 4 deletions test/e2e/lib/framework/createTest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import { IntakeRegistry } from './intakeRegistry'
import { flushEvents } from './flushEvents'
import type { Servers } from './httpServers'
import { getTestServers, waitForServersIdle } from './httpServers'
import type { CallerLocation, SetupFactory, SetupOptions, UrlHook } from './pageSetups'
import type { CallerLocation, EventBridgeOptions, SetupFactory, SetupOptions, UrlHook } from './pageSetups'
import { html, DEFAULT_SETUPS, npmSetup, appSetup, formatConfiguration } from './pageSetups'
import { createIntakeServerApp } from './serverApps/intake'
import { createMockServerApp } from './serverApps/mock'
Expand Down Expand Up @@ -52,7 +52,7 @@ class TestBuilder {
private head = ''
private body = ''
private baseUrlHooks: UrlHook[] = []
private eventBridge = false
private eventBridge: EventBridgeOptions | undefined
private setups: Array<{ factory: SetupFactory; name?: string }> = DEFAULT_SETUPS
private testFixture: typeof test = test
private extension: {
Expand Down Expand Up @@ -101,8 +101,8 @@ class TestBuilder {
return this
}

withEventBridge() {
this.eventBridge = true
withEventBridge(options: EventBridgeOptions = {}) {
this.eventBridge = options
return this
}

Expand Down
26 changes: 19 additions & 7 deletions test/e2e/lib/framework/pageSetups.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ export interface SetupOptions {
logsInit: (initConfiguration: LogsInitConfiguration) => void
rumInit: (initConfiguration: RumInitConfiguration) => void
remoteConfiguration?: RemoteConfiguration
eventBridge: boolean
eventBridge?: EventBridgeOptions
head?: string
body?: string
baseUrlHooks: UrlHook[]
Expand Down Expand Up @@ -41,6 +41,10 @@ export interface WorkerOptions {
logsConfiguration?: LogsInitConfiguration
}

export interface EventBridgeOptions {
isTraceSampled?: boolean
}

export type SetupFactory = (options: SetupOptions, servers: Servers) => string
export type UrlHook = (baseUrl: URL, servers: Servers, options: SetupOptions) => void

Expand All @@ -60,7 +64,7 @@ export function asyncSetup(options: SetupOptions, servers: Servers) {
let footer = ''

if (options.eventBridge) {
header += setupEventBridge(servers)
header += setupEventBridge(servers, options.eventBridge)
}

if (options.extension) {
Expand Down Expand Up @@ -108,7 +112,7 @@ export function bundleSetup(options: SetupOptions, servers: Servers) {
let header = options.head || ''

if (options.eventBridge) {
header += setupEventBridge(servers)
header += setupEventBridge(servers, options.eventBridge)
}

if (options.extension) {
Expand Down Expand Up @@ -143,7 +147,7 @@ export function npmSetup(options: SetupOptions, servers: Servers) {
let header = options.head || ''

if (options.eventBridge) {
header += setupEventBridge(servers)
header += setupEventBridge(servers, options.eventBridge)
}

if (options.extension) {
Expand Down Expand Up @@ -180,7 +184,7 @@ export function appSetup(options: SetupOptions, servers: Servers, appName: strin
let header = options.head || ''

if (options.eventBridge) {
header += setupEventBridge(servers)
header += setupEventBridge(servers, options.eventBridge)
}

if (options.extension) {
Expand Down Expand Up @@ -238,7 +242,7 @@ export function microfrontendSetup(options: SetupOptions, servers: Servers) {
let header = options.head || ''

if (options.eventBridge) {
header += setupEventBridge(servers)
header += setupEventBridge(servers, options.eventBridge)
}

if (options.extension) {
Expand Down Expand Up @@ -278,7 +282,7 @@ function js(parts: readonly string[], ...vars: string[]) {
return parts.reduce((full, part, index) => full + vars[index - 1] + part)
}

function setupEventBridge(servers: Servers) {
function setupEventBridge(servers: Servers, options: EventBridgeOptions = {}) {
const baseHostname = new URL(servers.base.origin).hostname

// Send EventBridge events to the intake so we can inspect them in our E2E test cases. The URL
Expand All @@ -289,6 +293,13 @@ function setupEventBridge(servers: Servers) {
bridge: 'true',
}).toString()}`

const isTraceSampledMethod =
options.isTraceSampled !== undefined
? `getIsTraceSampled() {
return ${options.isTraceSampled}
},`
: ''

return html`<script type="text/javascript">
window.DatadogEventBridge = {
getCapabilities() {
Expand All @@ -297,6 +308,7 @@ function setupEventBridge(servers: Servers) {
getPrivacyLevel() {
return 'mask'
},
${isTraceSampledMethod}
getAllowedWebViewHosts() {
return '["${baseHostname}"]'
},
Expand Down
26 changes: 26 additions & 0 deletions test/e2e/scenario/eventBridge.scenario.ts
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,32 @@ test.describe('bridge present', () => {
expect(intakeRegistry.hasOnlyBridgeRequests).toBe(true)
})

createTest('override trace sample rate when bridge provides isTraceSampled true')
.withRum({ service: 'service', traceSampleRate: 0, allowedTracingUrls: ['LOCATION_ORIGIN'] })
.withEventBridge({ isTraceSampled: true })
.run(async ({ flushEvents, intakeRegistry, sendXhr }) => {
await sendXhr('/headers')
await flushEvents()

const tracedResources = intakeRegistry.rumResourceEvents.filter(
(event) => event.resource.type === 'xhr' && event._dd?.trace_id
)
expect(tracedResources).toHaveLength(1)
expect(tracedResources[0]._dd.trace_id).toMatch(/\d+/)
})

createTest('override trace sample rate when bridge provides isTraceSampled false')
.withRum({ service: 'service', traceSampleRate: 100, allowedTracingUrls: ['LOCATION_ORIGIN'] })
.withEventBridge({ isTraceSampled: false })
.run(async ({ flushEvents, intakeRegistry, sendXhr }) => {
await sendXhr('/headers')
await flushEvents()

const xhrResources = intakeRegistry.rumResourceEvents.filter((event) => event.resource.type === 'xhr')
expect(xhrResources).toHaveLength(1)
expect(xhrResources[0]._dd?.trace_id).toBeUndefined()
})

createTest('do not send records when the recording is stopped')
.withRum()
.withEventBridge()
Expand Down
Loading