Skip to content

Commit d3a4b23

Browse files
committed
🔊collect remote configuration metrics
- add new telemetry metric - collect data about results of rc fetch and dynamic options resolution
1 parent 47f5ced commit d3a4b23

File tree

4 files changed

+176
-33
lines changed

4 files changed

+176
-33
lines changed

‎packages/rum-core/src/boot/startRum.ts‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ import { startRumEventBridge } from '../transport/startRumEventBridge'
3535
import { startUrlContexts } from '../domain/contexts/urlContexts'
3636
import { createLocationChangeObservable } from '../browser/locationChangeObservable'
3737
import type { RumConfiguration } from '../domain/configuration'
38+
import { REMOTE_CONFIGURATION_METRIC_NAME } from '../domain/configuration'
3839
import type { ViewOptions } from '../domain/view/trackViews'
3940
import { startFeatureFlagContexts } from '../domain/contexts/featureFlagContext'
4041
import { startCustomerDataTelemetry, CUSTOMER_DATA_METRIC_NAME } from '../domain/startCustomerDataTelemetry'
@@ -96,6 +97,7 @@ export function startRum(
9697

9798
const sampleRateByMetric = {
9899
[CUSTOMER_DATA_METRIC_NAME]: configuration.customerDataTelemetrySampleRate,
100+
[REMOTE_CONFIGURATION_METRIC_NAME]: configuration.remoteConfigurationTelemetrySampleRate,
99101
...recorderApi.getTelemetrySampleRateByMetric(configuration),
100102
}
101103

‎packages/rum-core/src/domain/configuration/configuration.ts‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -249,6 +249,7 @@ export interface RumConfiguration extends Configuration {
249249
customerDataTelemetrySampleRate: number
250250
initialViewMetricsTelemetrySampleRate: number
251251
replayTelemetrySampleRate: number
252+
remoteConfigurationTelemetrySampleRate: number
252253
traceContextInjection: TraceContextInjection
253254
plugins: RumPlugin[]
254255
trackFeatureFlagsForEvents: FeatureFlagsForEvents[]
@@ -322,6 +323,7 @@ export function validateAndBuildRumConfiguration(
322323
customerDataTelemetrySampleRate: 1,
323324
initialViewMetricsTelemetrySampleRate: 1,
324325
replayTelemetrySampleRate: 1,
326+
remoteConfigurationTelemetrySampleRate: 1,
325327
traceContextInjection: objectHasValue(TraceContextInjection, initConfiguration.traceContextInjection)
326328
? initConfiguration.traceContextInjection
327329
: TraceContextInjection.SAMPLED,

‎packages/rum-core/src/domain/configuration/remoteConfiguration.spec.ts‎

Lines changed: 78 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import {
1010
import { interceptRequests, registerCleanupTask } from '@datadog/browser-core/test'
1111
import { appendElement } from '../../../test'
1212
import type { RumInitConfiguration } from './configuration'
13-
import type { RumRemoteConfiguration } from './remoteConfiguration'
13+
import type { RumRemoteConfiguration, RemoteConfigurationMetrics } from './remoteConfiguration'
1414
import { applyRemoteConfiguration, buildEndpoint, fetchRemoteConfiguration } from './remoteConfiguration'
1515

1616
const DEFAULT_INIT_CONFIGURATION: RumInitConfiguration = {
@@ -99,11 +99,15 @@ describe('remoteConfiguration', () => {
9999
})
100100

101101
describe('applyRemoteConfiguration', () => {
102+
const COOKIE_NAME = 'unit_rc'
103+
const root = window as any
104+
102105
let displaySpy: jasmine.Spy
103106
let supportedContextManagers: {
104107
user: ReturnType<typeof createContextManager>
105108
context: ReturnType<typeof createContextManager>
106109
}
110+
let metrics: RemoteConfigurationMetrics
107111

108112
function expectAppliedRemoteConfigurationToBe(
109113
actual: Partial<RumRemoteConfiguration>,
@@ -114,7 +118,7 @@ describe('remoteConfiguration', () => {
114118
...actual,
115119
}
116120
expect(
117-
applyRemoteConfiguration(DEFAULT_INIT_CONFIGURATION, rumRemoteConfiguration, supportedContextManagers)
121+
applyRemoteConfiguration(DEFAULT_INIT_CONFIGURATION, rumRemoteConfiguration, supportedContextManagers, metrics)
118122
).toEqual({
119123
...DEFAULT_INIT_CONFIGURATION,
120124
applicationId: 'yyy',
@@ -125,6 +129,7 @@ describe('remoteConfiguration', () => {
125129
beforeEach(() => {
126130
displaySpy = spyOn(display, 'error')
127131
supportedContextManagers = { user: createContextManager(), context: createContextManager() }
132+
metrics = { fetch: {} }
128133
})
129134

130135
it('should override the initConfiguration options with the ones from the remote configuration', () => {
@@ -151,7 +156,7 @@ describe('remoteConfiguration', () => {
151156
defaultPrivacyLevel: DefaultPrivacyLevel.ALLOW,
152157
}
153158
expect(
154-
applyRemoteConfiguration(DEFAULT_INIT_CONFIGURATION, rumRemoteConfiguration, supportedContextManagers)
159+
applyRemoteConfiguration(DEFAULT_INIT_CONFIGURATION, rumRemoteConfiguration, supportedContextManagers, metrics)
155160
).toEqual({
156161
applicationId: 'yyy',
157162
clientToken: 'xxx',
@@ -194,22 +199,22 @@ describe('remoteConfiguration', () => {
194199
})
195200

196201
describe('cookie strategy', () => {
197-
const COOKIE_NAME = 'unit_rc'
198-
199202
it('should resolve a configuration value from a cookie', () => {
200203
setCookie(COOKIE_NAME, 'my-version', ONE_MINUTE)
201204
registerCleanupTask(() => deleteCookie(COOKIE_NAME))
202205
expectAppliedRemoteConfigurationToBe(
203206
{ version: { rcSerializedType: 'dynamic', strategy: 'cookie', name: COOKIE_NAME } },
204207
{ version: 'my-version' }
205208
)
209+
expect(metrics.cookie).toEqual({ success: 1 })
206210
})
207211

208212
it('should resolve to undefined if the cookie is missing', () => {
209213
expectAppliedRemoteConfigurationToBe(
210214
{ version: { rcSerializedType: 'dynamic', strategy: 'cookie', name: COOKIE_NAME } },
211215
{ version: undefined }
212216
)
217+
expect(metrics.cookie).toEqual({ missing: 1 })
213218
})
214219
})
215220

@@ -226,6 +231,7 @@ describe('remoteConfiguration', () => {
226231
{ version: { rcSerializedType: 'dynamic', strategy: 'dom', selector: '#version1' } },
227232
{ version: 'version-123' }
228233
)
234+
expect(metrics.dom).toEqual({ success: 1 })
229235
})
230236

231237
it('should resolve a configuration value from an element text content and an extractor', () => {
@@ -255,13 +261,15 @@ describe('remoteConfiguration', () => {
255261
{ version: undefined }
256262
)
257263
expect(displaySpy).toHaveBeenCalledWith("Invalid selector in the remote configuration: ''")
264+
expect(metrics.dom).toEqual({ failure: 1 })
258265
})
259266

260267
it('should resolve to undefined if the element is missing', () => {
261268
expectAppliedRemoteConfigurationToBe(
262269
{ version: { rcSerializedType: 'dynamic', strategy: 'dom', selector: '#missing' } },
263270
{ version: undefined }
264271
)
272+
expect(metrics.dom).toEqual({ missing: 1 })
265273
})
266274

267275
it('should resolve a configuration value from an element attribute', () => {
@@ -278,6 +286,7 @@ describe('remoteConfiguration', () => {
278286
{ version: { rcSerializedType: 'dynamic', strategy: 'dom', selector: '#version2', attribute: 'missing' } },
279287
{ version: undefined }
280288
)
289+
expect(metrics.dom).toEqual({ missing: 1 })
281290
})
282291

283292
it('should resolve to undefined if trying to access a password input value attribute', () => {
@@ -286,12 +295,11 @@ describe('remoteConfiguration', () => {
286295
{ version: { rcSerializedType: 'dynamic', strategy: 'dom', selector: '#pwd', attribute: 'value' } },
287296
{ version: undefined }
288297
)
298+
expect(displaySpy).toHaveBeenCalledWith("Forbidden element selected by the remote configuration: '#pwd'")
289299
})
290300
})
291301

292302
describe('js strategy', () => {
293-
const root = window as any
294-
295303
it('should resolve a value from a variable content', () => {
296304
root.foo = 'bar'
297305
registerCleanupTask(() => {
@@ -303,6 +311,7 @@ describe('remoteConfiguration', () => {
303311
},
304312
{ version: 'bar' }
305313
)
314+
expect(metrics.js).toEqual({ success: 1 })
306315
})
307316

308317
it('should resolve a value from an object property', () => {
@@ -401,6 +410,7 @@ describe('remoteConfiguration', () => {
401410
{ version: undefined }
402411
)
403412
expect(displaySpy).toHaveBeenCalledWith("Invalid JSON path in the remote configuration: '.'")
413+
expect(metrics.js).toEqual({ failure: 1 })
404414
})
405415

406416
it('should resolve to undefined and display an error if the variable access throws', () => {
@@ -419,6 +429,7 @@ describe('remoteConfiguration', () => {
419429
{ version: undefined }
420430
)
421431
expect(displaySpy).toHaveBeenCalledWith("Error accessing: 'foo.bar'", new Error('foo'))
432+
expect(metrics.js).toEqual({ failure: 1 })
422433
})
423434

424435
it('should resolve to undefined if the variable does not exist', () => {
@@ -428,6 +439,7 @@ describe('remoteConfiguration', () => {
428439
},
429440
{ version: undefined }
430441
)
442+
expect(metrics.js).toEqual({ missing: 1 })
431443
})
432444

433445
it('should resolve to undefined if the property does not exist', () => {
@@ -442,6 +454,7 @@ describe('remoteConfiguration', () => {
442454
},
443455
{ version: undefined }
444456
)
457+
expect(metrics.js).toEqual({ missing: 1 })
445458
})
446459

447460
it('should resolve to undefined if the array index does not exist', () => {
@@ -456,12 +469,11 @@ describe('remoteConfiguration', () => {
456469
},
457470
{ version: undefined }
458471
)
472+
expect(metrics.js).toEqual({ missing: 1 })
459473
})
460474
})
461475

462476
describe('with extractor', () => {
463-
const COOKIE_NAME = 'unit_rc'
464-
465477
beforeEach(() => {
466478
setCookie(COOKIE_NAME, 'my-version-123', ONE_MINUTE)
467479
})
@@ -529,8 +541,6 @@ describe('remoteConfiguration', () => {
529541
})
530542

531543
describe('supported contexts', () => {
532-
const COOKIE_NAME = 'unit_rc'
533-
534544
beforeEach(() => {
535545
setCookie(COOKIE_NAME, 'first.second', ONE_MINUTE)
536546
})
@@ -592,6 +602,63 @@ describe('remoteConfiguration', () => {
592602
})
593603
})
594604
})
605+
606+
describe('metrics', () => {
607+
it('should report resolution stats', () => {
608+
setCookie(COOKIE_NAME, 'my-version', ONE_MINUTE)
609+
root.foo = '123'
610+
registerCleanupTask(() => {
611+
deleteCookie(COOKIE_NAME)
612+
delete root.foo
613+
})
614+
615+
expectAppliedRemoteConfigurationToBe(
616+
{
617+
context: [
618+
{
619+
key: 'missing-cookie',
620+
value: {
621+
rcSerializedType: 'dynamic',
622+
strategy: 'cookie',
623+
name: 'missing-cookie',
624+
},
625+
},
626+
{
627+
key: 'existing-cookie',
628+
value: {
629+
rcSerializedType: 'dynamic',
630+
strategy: 'cookie',
631+
name: COOKIE_NAME,
632+
},
633+
},
634+
{
635+
key: 'existing-cookie2',
636+
value: {
637+
rcSerializedType: 'dynamic',
638+
strategy: 'cookie',
639+
name: COOKIE_NAME,
640+
},
641+
},
642+
{
643+
key: 'existing-js',
644+
value: {
645+
rcSerializedType: 'dynamic',
646+
strategy: 'js',
647+
path: 'foo',
648+
},
649+
},
650+
],
651+
},
652+
{}
653+
)
654+
expect(metrics).toEqual(
655+
jasmine.objectContaining({
656+
cookie: { success: 2, missing: 1 },
657+
js: { success: 1 },
658+
})
659+
)
660+
})
661+
})
595662
})
596663

597664
describe('buildEndpoint', () => {

0 commit comments

Comments
 (0)