From 78c5acd1e1b5b3248b2c7b92415bac5b178ad4b8 Mon Sep 17 00:00:00 2001 From: Gitlab staging reset job Date: Wed, 17 Nov 2021 15:10:54 +0100 Subject: [PATCH 01/11] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20Move=20startMonitori?= =?UTF-8?q?ngBatch=20in=20its=20own=20file?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../core/src/domain/internalMonitoring.ts | 46 ++----------------- .../startMonitoringBatch.ts | 41 +++++++++++++++++ 2 files changed, 45 insertions(+), 42 deletions(-) create mode 100644 packages/core/src/domain/internalMonitoring/startMonitoringBatch.ts diff --git a/packages/core/src/domain/internalMonitoring.ts b/packages/core/src/domain/internalMonitoring.ts index c76b17dfac..24c4035c64 100644 --- a/packages/core/src/domain/internalMonitoring.ts +++ b/packages/core/src/domain/internalMonitoring.ts @@ -1,11 +1,11 @@ import { Context } from '../tools/context' import { display } from '../tools/display' import { toStackTraceString } from '../tools/error' -import { assign, combine, jsonStringify, Parameters, ThisParameterType } from '../tools/utils' -import { Batch, HttpRequest } from '../transport' +import { assign, jsonStringify, Parameters, ThisParameterType } from '../tools/utils' +import { Batch } from '../transport' import { Configuration } from './configuration' -import { EndpointBuilder } from './configuration/endpointBuilder' import { computeStackTrace } from './tracekit' +import { startMonitoringBatch } from './internalMonitoring/startMonitoringBatch' enum StatusType { info = 'info', @@ -32,7 +32,7 @@ const monitoringConfiguration: { sentMessageCount: number } = { maxMessagesPerPage: 0, sentMessageCount: 0 } -let externalContextProvider: () => Context +export let externalContextProvider: () => Context export function startInternalMonitoring(configuration: Configuration): InternalMonitoring { if (configuration.internalMonitoringEndpointBuilder) { @@ -51,44 +51,6 @@ export function startInternalMonitoring(configuration: Configuration): InternalM } } -function startMonitoringBatch(configuration: Configuration) { - const primaryBatch = createMonitoringBatch(configuration.internalMonitoringEndpointBuilder!) - let replicaBatch: Batch | undefined - if (configuration.replica !== undefined) { - replicaBatch = createMonitoringBatch(configuration.replica.internalMonitoringEndpointBuilder) - } - - function createMonitoringBatch(endpointBuilder: EndpointBuilder) { - return new Batch( - new HttpRequest(endpointBuilder, configuration.batchBytesLimit), - configuration.maxBatchSize, - configuration.batchBytesLimit, - configuration.maxMessageSize, - configuration.flushTimeout - ) - } - - function withContext(message: MonitoringMessage) { - return combine( - { - date: new Date().getTime(), - }, - externalContextProvider !== undefined ? externalContextProvider() : {}, - message - ) - } - - return { - add(message: MonitoringMessage) { - const contextualizedMessage = withContext(message) - primaryBatch.add(contextualizedMessage) - if (replicaBatch) { - replicaBatch.add(contextualizedMessage) - } - }, - } -} - export function startFakeInternalMonitoring() { const messages: MonitoringMessage[] = [] assign(monitoringConfiguration, { diff --git a/packages/core/src/domain/internalMonitoring/startMonitoringBatch.ts b/packages/core/src/domain/internalMonitoring/startMonitoringBatch.ts new file mode 100644 index 0000000000..f32e22e106 --- /dev/null +++ b/packages/core/src/domain/internalMonitoring/startMonitoringBatch.ts @@ -0,0 +1,41 @@ +import { Batch, HttpRequest } from '../../transport' +import { combine, Configuration, EndpointBuilder, MonitoringMessage } from '../..' +import { externalContextProvider } from '../internalMonitoring' + +export function startMonitoringBatch(configuration: Configuration) { + const primaryBatch = createMonitoringBatch(configuration.internalMonitoringEndpointBuilder!) + let replicaBatch: Batch | undefined + if (configuration.replica !== undefined) { + replicaBatch = createMonitoringBatch(configuration.replica.internalMonitoringEndpointBuilder) + } + + function createMonitoringBatch(endpointBuilder: EndpointBuilder) { + return new Batch( + new HttpRequest(endpointBuilder, configuration.batchBytesLimit), + configuration.maxBatchSize, + configuration.batchBytesLimit, + configuration.maxMessageSize, + configuration.flushTimeout + ) + } + + function withContext(message: MonitoringMessage) { + return combine( + { + date: new Date().getTime(), + }, + externalContextProvider !== undefined ? externalContextProvider() : {}, + message + ) + } + + return { + add(message: MonitoringMessage) { + const contextualizedMessage = withContext(message) + primaryBatch.add(contextualizedMessage) + if (replicaBatch) { + replicaBatch.add(contextualizedMessage) + } + }, + } +} From 8bebcf623660d776308a74387f031576c30a8954 Mon Sep 17 00:00:00 2001 From: Gitlab staging reset job Date: Wed, 17 Nov 2021 17:44:14 +0100 Subject: [PATCH 02/11] =?UTF-8?q?=E2=9C=A8=20Forward=20monitoring=20to=20e?= =?UTF-8?q?vent=20bridge?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/domain/internalMonitoring.spec.ts | 31 +++++++++-- .../core/src/domain/internalMonitoring.ts | 52 ++++++++++--------- packages/core/src/index.ts | 2 +- packages/core/src/transport/eventBridge.ts | 1 + packages/core/src/transport/httpRequest.ts | 4 +- .../segmentCollection/startDeflateWorker.ts | 6 +-- 6 files changed, 63 insertions(+), 33 deletions(-) diff --git a/packages/core/src/domain/internalMonitoring.spec.ts b/packages/core/src/domain/internalMonitoring.spec.ts index 3b7d1f814f..8b432e0d34 100644 --- a/packages/core/src/domain/internalMonitoring.spec.ts +++ b/packages/core/src/domain/internalMonitoring.spec.ts @@ -1,5 +1,12 @@ import sinon from 'sinon' -import { Clock, mockClock, stubEndpointBuilder } from '../../test/specHelper' +import { resetExperimentalFeatures, updateExperimentalFeatures } from '..' +import { + Clock, + deleteEventBridgeStub, + initEventBridgeStub, + mockClock, + stubEndpointBuilder, +} from '../../test/specHelper' import { Configuration } from './configuration' import { @@ -171,13 +178,12 @@ describe('internal monitoring', () => { }) }) - describe('request', () => { + describe('transport', () => { const FAKE_DATE = 123456 let server: sinon.SinonFakeServer let clock: Clock beforeEach(() => { - startInternalMonitoring(configuration as Configuration) server = sinon.fakeServer.create() clock = mockClock(new Date(FAKE_DATE)) }) @@ -186,9 +192,13 @@ describe('internal monitoring', () => { resetInternalMonitoring() server.restore() clock.cleanup() + resetExperimentalFeatures() + deleteEventBridgeStub() }) it('should send the needed data', () => { + startInternalMonitoring(configuration as Configuration) + callMonitored(() => { throw new Error('message') }) @@ -205,6 +215,8 @@ describe('internal monitoring', () => { }) it('should cap the data sent', () => { + startInternalMonitoring(configuration as Configuration) + const max = configuration.maxInternalMonitoringMessagesPerPage! for (let i = 0; i < max + 3; i += 1) { callMonitored(() => { @@ -214,6 +226,19 @@ describe('internal monitoring', () => { expect(server.requests.length).toEqual(max) }) + + it('should send bridge event when bridge is present', () => { + updateExperimentalFeatures(['event-bridge']) + const sendSpy = spyOn(initEventBridgeStub(), 'send') + startInternalMonitoring(configuration as Configuration) + + callMonitored(() => { + throw new Error('message') + }) + + expect(server.requests.length).toEqual(0) + expect(sendSpy).toHaveBeenCalled() + }) }) describe('external context', () => { diff --git a/packages/core/src/domain/internalMonitoring.ts b/packages/core/src/domain/internalMonitoring.ts index 24c4035c64..fca40efd21 100644 --- a/packages/core/src/domain/internalMonitoring.ts +++ b/packages/core/src/domain/internalMonitoring.ts @@ -2,7 +2,7 @@ import { Context } from '../tools/context' import { display } from '../tools/display' import { toStackTraceString } from '../tools/error' import { assign, jsonStringify, Parameters, ThisParameterType } from '../tools/utils' -import { Batch } from '../transport' +import { canUseEventBridge, getEventBridge } from '../transport' import { Configuration } from './configuration' import { computeStackTrace } from './tracekit' import { startMonitoringBatch } from './internalMonitoring/startMonitoringBatch' @@ -26,7 +26,6 @@ export interface MonitoringMessage extends Context { } const monitoringConfiguration: { - batch?: Batch debugMode?: boolean maxMessagesPerPage: number sentMessageCount: number @@ -34,16 +33,22 @@ const monitoringConfiguration: { export let externalContextProvider: () => Context +let onInternalMonitoringEventCollected: ((message: MonitoringMessage) => void) | undefined + export function startInternalMonitoring(configuration: Configuration): InternalMonitoring { - if (configuration.internalMonitoringEndpointBuilder) { + if (canUseEventBridge()) { + const bridge = getEventBridge()! + onInternalMonitoringEventCollected = (message: MonitoringMessage) => bridge.send('internalMonitoring', message) + } else if (configuration.internalMonitoringEndpointBuilder) { const batch = startMonitoringBatch(configuration) - - assign(monitoringConfiguration, { - batch, - maxMessagesPerPage: configuration.maxInternalMonitoringMessagesPerPage, - sentMessageCount: 0, - }) + onInternalMonitoringEventCollected = (message: MonitoringMessage) => batch.add(message) } + + assign(monitoringConfiguration, { + maxMessagesPerPage: configuration.maxInternalMonitoringMessagesPerPage, + sentMessageCount: 0, + }) + return { setExternalContextProvider: (provider: () => Context) => { externalContextProvider = provider @@ -54,19 +59,19 @@ export function startInternalMonitoring(configuration: Configuration): InternalM export function startFakeInternalMonitoring() { const messages: MonitoringMessage[] = [] assign(monitoringConfiguration, { - batch: { - add(message: MonitoringMessage) { - messages.push(message) - }, - }, maxMessagesPerPage: Infinity, sentMessageCount: 0, }) + + onInternalMonitoringEventCollected = (message: MonitoringMessage) => { + messages.push(message) + } + return messages } export function resetInternalMonitoring() { - monitoringConfiguration.batch = undefined + onInternalMonitoringEventCollected = undefined } export function monitored unknown>( @@ -76,7 +81,7 @@ export function monitored unknown>( ) { const originalMethod = descriptor.value! descriptor.value = function (this: any, ...args: Parameters) { - const decorated = monitoringConfiguration.batch ? monitor(originalMethod) : originalMethod + const decorated = onInternalMonitoringEventCollected ? monitor(originalMethod) : originalMethod return decorated.apply(this, args) as ReturnType } as T } @@ -105,7 +110,7 @@ export function callMonitored any>( } catch (e) { logErrorIfDebug(e) try { - addErrorToMonitoringBatch(e) + addErrorToMonitoring(e) } catch (e) { logErrorIfDebug(e) } @@ -114,28 +119,27 @@ export function callMonitored any>( export function addMonitoringMessage(message: string, context?: Context) { logMessageIfDebug(message, context) - addToMonitoringBatch({ + addToMonitoring({ message, ...context, status: StatusType.info, }) } -export function addErrorToMonitoringBatch(e: unknown) { - addToMonitoringBatch({ +export function addErrorToMonitoring(e: unknown) { + addToMonitoring({ ...formatError(e), status: StatusType.error, }) } -function addToMonitoringBatch(message: MonitoringMessage) { +function addToMonitoring(message: MonitoringMessage) { if ( - monitoringConfiguration.batch && + onInternalMonitoringEventCollected && monitoringConfiguration.sentMessageCount < monitoringConfiguration.maxMessagesPerPage ) { monitoringConfiguration.sentMessageCount += 1 - - monitoringConfiguration.batch.add(message) + onInternalMonitoringEventCollected(message) } } diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts index b9e2fe592c..82105e28ec 100644 --- a/packages/core/src/index.ts +++ b/packages/core/src/index.ts @@ -22,7 +22,7 @@ export { monitor, callMonitored, addMonitoringMessage, - addErrorToMonitoringBatch, + addErrorToMonitoring, startFakeInternalMonitoring, resetInternalMonitoring, setDebugMode, diff --git a/packages/core/src/transport/eventBridge.ts b/packages/core/src/transport/eventBridge.ts index bd4344bdc8..b2a02fac1a 100644 --- a/packages/core/src/transport/eventBridge.ts +++ b/packages/core/src/transport/eventBridge.ts @@ -28,6 +28,7 @@ export function getEventBridge() { export function canUseEventBridge(): boolean { const bridge = getEventBridge() + return !!bridge && includes(bridge.getAllowedWebViewHosts(), window.location.hostname) } diff --git a/packages/core/src/transport/httpRequest.ts b/packages/core/src/transport/httpRequest.ts index debe426e9f..5e2273779d 100644 --- a/packages/core/src/transport/httpRequest.ts +++ b/packages/core/src/transport/httpRequest.ts @@ -1,5 +1,5 @@ import { EndpointBuilder } from '../domain/configuration/endpointBuilder' -import { monitor, addErrorToMonitoringBatch, addMonitoringMessage } from '../domain/internalMonitoring' +import { monitor, addErrorToMonitoring, addMonitoringMessage } from '../domain/internalMonitoring' let hasReportedXhrError = false @@ -69,6 +69,6 @@ let hasReportedBeaconError = false function reportBeaconError(e: unknown) { if (!hasReportedBeaconError) { hasReportedBeaconError = true - addErrorToMonitoringBatch(e) + addErrorToMonitoring(e) } } diff --git a/packages/rum/src/domain/segmentCollection/startDeflateWorker.ts b/packages/rum/src/domain/segmentCollection/startDeflateWorker.ts index 274844c9b9..75958373df 100644 --- a/packages/rum/src/domain/segmentCollection/startDeflateWorker.ts +++ b/packages/rum/src/domain/segmentCollection/startDeflateWorker.ts @@ -1,4 +1,4 @@ -import { addErrorToMonitoringBatch, display, includes, monitor } from '@datadog/browser-core' +import { addErrorToMonitoring, display, includes, monitor } from '@datadog/browser-core' import { createDeflateWorker, DeflateWorker } from './deflateWorker' /** @@ -102,11 +102,11 @@ function onError(error: ErrorEvent | Error | string) { 'https://docs.datadoghq.com/real_user_monitoring/faq/content_security_policy' ) } else { - addErrorToMonitoringBatch(error) + addErrorToMonitoring(error) } state.callbacks.forEach((callback) => callback()) state = { status: DeflateWorkerStatus.Error } } else { - addErrorToMonitoringBatch(error) + addErrorToMonitoring(error) } } From 1a666c11e8162c341918f802e5d1b19e74d1491e Mon Sep 17 00:00:00 2001 From: Gitlab staging reset job Date: Wed, 17 Nov 2021 17:45:06 +0100 Subject: [PATCH 03/11] =?UTF-8?q?=E2=9C=85=20=20Add=20e2e=20forward=20moni?= =?UTF-8?q?toring=20to=20event=20bridge?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- test/e2e/lib/framework/pageSetups.ts | 6 ++++-- test/e2e/scenario/rum/eventBridge.scenario.ts | 19 +++++++++++++++++++ 2 files changed, 23 insertions(+), 2 deletions(-) diff --git a/test/e2e/lib/framework/pageSetups.ts b/test/e2e/lib/framework/pageSetups.ts index bf49579ae9..e096d56d8b 100644 --- a/test/e2e/lib/framework/pageSetups.ts +++ b/test/e2e/lib/framework/pageSetups.ts @@ -166,9 +166,11 @@ function setupEventBridge(servers: Servers) { return '["${baseHostname}"]' }, send(e) { - const { event } = JSON.parse(e) + const { eventType, event } = JSON.parse(e) const request = new XMLHttpRequest() - request.open('POST', '${servers.intake.url}/v1/input/rum?bridge=1', true) + const internalMonitoring + const endpoints === 'internalMonitoring' ? 'internalMonitoring' : 'rum' + request.open('POST', \`${servers.intake.url}/v1/input/\${endpoints}?bridge=1\`, true) request.send(JSON.stringify(event)) }, } diff --git a/test/e2e/scenario/rum/eventBridge.scenario.ts b/test/e2e/scenario/rum/eventBridge.scenario.ts index 8b7e905e41..59c8b9eebc 100644 --- a/test/e2e/scenario/rum/eventBridge.scenario.ts +++ b/test/e2e/scenario/rum/eventBridge.scenario.ts @@ -60,4 +60,23 @@ describe('bridge present', () => { expect(serverEvents.rumViews.length).toEqual(0) expect(bridgeEvents.rumViews.length).toBeGreaterThan(0) }) + + createTest('forward internal monitoring to the bridge') + .withLogs({ enableExperimentalFeatures: ['event-bridge'], internalMonitoringApiKey: 'xxx' }) + .withEventBridge() + .run(async ({ serverEvents, bridgeEvents }) => { + await browserExecute(() => { + const context = { + get foo() { + throw new window.Error('bar') + }, + } + // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion + window.DD_LOGS!.logger.log('hop', context as any) + }) + + await flushEvents() + expect(serverEvents.internalMonitoring.length).toBe(0) + expect(bridgeEvents.internalMonitoring.length).toBe(1) + }) }) From 7c4f39e67f11cf2fd31d70b47d11bc5fdcd544aa Mon Sep 17 00:00:00 2001 From: Gitlab staging reset job Date: Tue, 23 Nov 2021 16:56:26 +0100 Subject: [PATCH 04/11] =?UTF-8?q?=F0=9F=8E=A8=20Move=20internal=20monitori?= =?UTF-8?q?ng=20into=20its=20folder?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/domain/internalMonitoring/index.ts | 15 +++++++++++++++ .../internalMonitoring.spec.ts | 6 +++--- .../internalMonitoring.ts | 18 +++++++++--------- 3 files changed, 27 insertions(+), 12 deletions(-) create mode 100644 packages/core/src/domain/internalMonitoring/index.ts rename packages/core/src/domain/{ => internalMonitoring}/internalMonitoring.spec.ts (98%) rename packages/core/src/domain/{ => internalMonitoring}/internalMonitoring.ts (90%) diff --git a/packages/core/src/domain/internalMonitoring/index.ts b/packages/core/src/domain/internalMonitoring/index.ts new file mode 100644 index 0000000000..e12cf3b730 --- /dev/null +++ b/packages/core/src/domain/internalMonitoring/index.ts @@ -0,0 +1,15 @@ +export { + InternalMonitoring, + MonitoringMessage, + monitored, + monitor, + callMonitored, + addMonitoringMessage, + addErrorToMonitoring, + startFakeInternalMonitoring, + resetInternalMonitoring, + setDebugMode, + startInternalMonitoring, + externalContextProvider, +} from './internalMonitoring' +export { startMonitoringBatch } from './startMonitoringBatch' diff --git a/packages/core/src/domain/internalMonitoring.spec.ts b/packages/core/src/domain/internalMonitoring/internalMonitoring.spec.ts similarity index 98% rename from packages/core/src/domain/internalMonitoring.spec.ts rename to packages/core/src/domain/internalMonitoring/internalMonitoring.spec.ts index 8b432e0d34..8d94c093a0 100644 --- a/packages/core/src/domain/internalMonitoring.spec.ts +++ b/packages/core/src/domain/internalMonitoring/internalMonitoring.spec.ts @@ -1,14 +1,14 @@ import sinon from 'sinon' -import { resetExperimentalFeatures, updateExperimentalFeatures } from '..' +import { resetExperimentalFeatures, updateExperimentalFeatures } from '../..' import { Clock, deleteEventBridgeStub, initEventBridgeStub, mockClock, stubEndpointBuilder, -} from '../../test/specHelper' +} from '../../../test/specHelper' -import { Configuration } from './configuration' +import { Configuration } from '../configuration' import { InternalMonitoring, monitor, diff --git a/packages/core/src/domain/internalMonitoring.ts b/packages/core/src/domain/internalMonitoring/internalMonitoring.ts similarity index 90% rename from packages/core/src/domain/internalMonitoring.ts rename to packages/core/src/domain/internalMonitoring/internalMonitoring.ts index fca40efd21..72331bfb23 100644 --- a/packages/core/src/domain/internalMonitoring.ts +++ b/packages/core/src/domain/internalMonitoring/internalMonitoring.ts @@ -1,11 +1,11 @@ -import { Context } from '../tools/context' -import { display } from '../tools/display' -import { toStackTraceString } from '../tools/error' -import { assign, jsonStringify, Parameters, ThisParameterType } from '../tools/utils' -import { canUseEventBridge, getEventBridge } from '../transport' -import { Configuration } from './configuration' -import { computeStackTrace } from './tracekit' -import { startMonitoringBatch } from './internalMonitoring/startMonitoringBatch' +import { Context } from '../../tools/context' +import { display } from '../../tools/display' +import { toStackTraceString } from '../../tools/error' +import { assign, jsonStringify, Parameters, ThisParameterType } from '../../tools/utils' +import { canUseEventBridge, getEventBridge } from '../../transport' +import { Configuration } from '../configuration' +import { computeStackTrace } from '../tracekit' +import { startMonitoringBatch } from '../internalMonitoring/startMonitoringBatch' enum StatusType { info = 'info', @@ -38,7 +38,7 @@ let onInternalMonitoringEventCollected: ((message: MonitoringMessage) => void) | export function startInternalMonitoring(configuration: Configuration): InternalMonitoring { if (canUseEventBridge()) { const bridge = getEventBridge()! - onInternalMonitoringEventCollected = (message: MonitoringMessage) => bridge.send('internalMonitoring', message) + onInternalMonitoringEventCollected = (message: MonitoringMessage) => bridge.send('internal_log', message) } else if (configuration.internalMonitoringEndpointBuilder) { const batch = startMonitoringBatch(configuration) onInternalMonitoringEventCollected = (message: MonitoringMessage) => batch.add(message) From 82dc14640dd019ed1c8525d47ac866b6f1fee791 Mon Sep 17 00:00:00 2001 From: Gitlab staging reset job Date: Wed, 24 Nov 2021 10:16:45 +0100 Subject: [PATCH 05/11] fix e2e bridge page setup --- test/e2e/lib/framework/pageSetups.ts | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/test/e2e/lib/framework/pageSetups.ts b/test/e2e/lib/framework/pageSetups.ts index e096d56d8b..7e6a87120d 100644 --- a/test/e2e/lib/framework/pageSetups.ts +++ b/test/e2e/lib/framework/pageSetups.ts @@ -168,9 +168,8 @@ function setupEventBridge(servers: Servers) { send(e) { const { eventType, event } = JSON.parse(e) const request = new XMLHttpRequest() - const internalMonitoring - const endpoints === 'internalMonitoring' ? 'internalMonitoring' : 'rum' - request.open('POST', \`${servers.intake.url}/v1/input/\${endpoints}?bridge=1\`, true) + const endpoint = eventType === 'internal_log' ? 'internalMonitoring' : 'rum' + request.open('POST', \`${servers.intake.url}/v1/input/\${endpoint}?bridge=1\`, true) request.send(JSON.stringify(event)) }, } From 6ad084900f1ed6b09d60c6944ab107d1f6a79776 Mon Sep 17 00:00:00 2001 From: Gitlab staging reset job Date: Thu, 25 Nov 2021 11:30:01 +0100 Subject: [PATCH 06/11] =?UTF-8?q?=F0=9F=91=8C=20Rename=20addErrorToMonitor?= =?UTF-8?q?ing=20to=20addMonitoringError?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/core/src/domain/internalMonitoring/index.ts | 2 +- .../src/domain/internalMonitoring/internalMonitoring.ts | 4 ++-- packages/core/src/index.ts | 2 +- packages/core/src/transport/httpRequest.ts | 4 ++-- .../rum/src/domain/segmentCollection/startDeflateWorker.ts | 6 +++--- 5 files changed, 9 insertions(+), 9 deletions(-) diff --git a/packages/core/src/domain/internalMonitoring/index.ts b/packages/core/src/domain/internalMonitoring/index.ts index e12cf3b730..d7c0a74787 100644 --- a/packages/core/src/domain/internalMonitoring/index.ts +++ b/packages/core/src/domain/internalMonitoring/index.ts @@ -5,7 +5,7 @@ export { monitor, callMonitored, addMonitoringMessage, - addErrorToMonitoring, + addMonitoringError, startFakeInternalMonitoring, resetInternalMonitoring, setDebugMode, diff --git a/packages/core/src/domain/internalMonitoring/internalMonitoring.ts b/packages/core/src/domain/internalMonitoring/internalMonitoring.ts index 72331bfb23..99c431e3e3 100644 --- a/packages/core/src/domain/internalMonitoring/internalMonitoring.ts +++ b/packages/core/src/domain/internalMonitoring/internalMonitoring.ts @@ -110,7 +110,7 @@ export function callMonitored any>( } catch (e) { logErrorIfDebug(e) try { - addErrorToMonitoring(e) + addMonitoringError(e) } catch (e) { logErrorIfDebug(e) } @@ -126,7 +126,7 @@ export function addMonitoringMessage(message: string, context?: Context) { }) } -export function addErrorToMonitoring(e: unknown) { +export function addMonitoringError(e: unknown) { addToMonitoring({ ...formatError(e), status: StatusType.error, diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts index d9f5777bcf..8e02b6904e 100644 --- a/packages/core/src/index.ts +++ b/packages/core/src/index.ts @@ -22,7 +22,7 @@ export { monitor, callMonitored, addMonitoringMessage, - addErrorToMonitoring, + addMonitoringError, startFakeInternalMonitoring, resetInternalMonitoring, setDebugMode, diff --git a/packages/core/src/transport/httpRequest.ts b/packages/core/src/transport/httpRequest.ts index 5e2273779d..24a1dfada5 100644 --- a/packages/core/src/transport/httpRequest.ts +++ b/packages/core/src/transport/httpRequest.ts @@ -1,5 +1,5 @@ import { EndpointBuilder } from '../domain/configuration/endpointBuilder' -import { monitor, addErrorToMonitoring, addMonitoringMessage } from '../domain/internalMonitoring' +import { monitor, addMonitoringError, addMonitoringMessage } from '../domain/internalMonitoring' let hasReportedXhrError = false @@ -69,6 +69,6 @@ let hasReportedBeaconError = false function reportBeaconError(e: unknown) { if (!hasReportedBeaconError) { hasReportedBeaconError = true - addErrorToMonitoring(e) + addMonitoringError(e) } } diff --git a/packages/rum/src/domain/segmentCollection/startDeflateWorker.ts b/packages/rum/src/domain/segmentCollection/startDeflateWorker.ts index 75958373df..06dd528399 100644 --- a/packages/rum/src/domain/segmentCollection/startDeflateWorker.ts +++ b/packages/rum/src/domain/segmentCollection/startDeflateWorker.ts @@ -1,4 +1,4 @@ -import { addErrorToMonitoring, display, includes, monitor } from '@datadog/browser-core' +import { addMonitoringError, display, includes, monitor } from '@datadog/browser-core' import { createDeflateWorker, DeflateWorker } from './deflateWorker' /** @@ -102,11 +102,11 @@ function onError(error: ErrorEvent | Error | string) { 'https://docs.datadoghq.com/real_user_monitoring/faq/content_security_policy' ) } else { - addErrorToMonitoring(error) + addMonitoringError(error) } state.callbacks.forEach((callback) => callback()) state = { status: DeflateWorkerStatus.Error } } else { - addErrorToMonitoring(error) + addMonitoringError(error) } } From 384a5b9123c6b15c46e0c465d18eaa88fcb9174b Mon Sep 17 00:00:00 2001 From: Gitlab staging reset job Date: Thu, 25 Nov 2021 11:53:46 +0100 Subject: [PATCH 07/11] Set external context when bridge detected --- .../src/domain/internalMonitoring/index.ts | 1 - .../internalMonitoring/internalMonitoring.ts | 19 ++++++++++++++----- .../startMonitoringBatch.ts | 18 +++--------------- 3 files changed, 17 insertions(+), 21 deletions(-) diff --git a/packages/core/src/domain/internalMonitoring/index.ts b/packages/core/src/domain/internalMonitoring/index.ts index d7c0a74787..34037b230c 100644 --- a/packages/core/src/domain/internalMonitoring/index.ts +++ b/packages/core/src/domain/internalMonitoring/index.ts @@ -10,6 +10,5 @@ export { resetInternalMonitoring, setDebugMode, startInternalMonitoring, - externalContextProvider, } from './internalMonitoring' export { startMonitoringBatch } from './startMonitoringBatch' diff --git a/packages/core/src/domain/internalMonitoring/internalMonitoring.ts b/packages/core/src/domain/internalMonitoring/internalMonitoring.ts index 99c431e3e3..bf6661c982 100644 --- a/packages/core/src/domain/internalMonitoring/internalMonitoring.ts +++ b/packages/core/src/domain/internalMonitoring/internalMonitoring.ts @@ -1,7 +1,7 @@ import { Context } from '../../tools/context' import { display } from '../../tools/display' import { toStackTraceString } from '../../tools/error' -import { assign, jsonStringify, Parameters, ThisParameterType } from '../../tools/utils' +import { assign, combine, jsonStringify, Parameters, ThisParameterType } from '../../tools/utils' import { canUseEventBridge, getEventBridge } from '../../transport' import { Configuration } from '../configuration' import { computeStackTrace } from '../tracekit' @@ -31,17 +31,18 @@ const monitoringConfiguration: { sentMessageCount: number } = { maxMessagesPerPage: 0, sentMessageCount: 0 } -export let externalContextProvider: () => Context - let onInternalMonitoringEventCollected: ((message: MonitoringMessage) => void) | undefined export function startInternalMonitoring(configuration: Configuration): InternalMonitoring { + let externalContextProvider: () => Context + if (canUseEventBridge()) { const bridge = getEventBridge()! - onInternalMonitoringEventCollected = (message: MonitoringMessage) => bridge.send('internal_log', message) + onInternalMonitoringEventCollected = (message: MonitoringMessage) => + bridge.send('internal_log', withContext(message)) } else if (configuration.internalMonitoringEndpointBuilder) { const batch = startMonitoringBatch(configuration) - onInternalMonitoringEventCollected = (message: MonitoringMessage) => batch.add(message) + onInternalMonitoringEventCollected = (message: MonitoringMessage) => batch.add(withContext(message)) } assign(monitoringConfiguration, { @@ -49,6 +50,14 @@ export function startInternalMonitoring(configuration: Configuration): InternalM sentMessageCount: 0, }) + function withContext(message: MonitoringMessage) { + return combine( + { date: new Date().getTime() }, + externalContextProvider !== undefined ? externalContextProvider() : {}, + message + ) + } + return { setExternalContextProvider: (provider: () => Context) => { externalContextProvider = provider diff --git a/packages/core/src/domain/internalMonitoring/startMonitoringBatch.ts b/packages/core/src/domain/internalMonitoring/startMonitoringBatch.ts index f32e22e106..98274d80dc 100644 --- a/packages/core/src/domain/internalMonitoring/startMonitoringBatch.ts +++ b/packages/core/src/domain/internalMonitoring/startMonitoringBatch.ts @@ -1,6 +1,5 @@ import { Batch, HttpRequest } from '../../transport' -import { combine, Configuration, EndpointBuilder, MonitoringMessage } from '../..' -import { externalContextProvider } from '../internalMonitoring' +import { Configuration, EndpointBuilder, MonitoringMessage } from '../..' export function startMonitoringBatch(configuration: Configuration) { const primaryBatch = createMonitoringBatch(configuration.internalMonitoringEndpointBuilder!) @@ -19,22 +18,11 @@ export function startMonitoringBatch(configuration: Configuration) { ) } - function withContext(message: MonitoringMessage) { - return combine( - { - date: new Date().getTime(), - }, - externalContextProvider !== undefined ? externalContextProvider() : {}, - message - ) - } - return { add(message: MonitoringMessage) { - const contextualizedMessage = withContext(message) - primaryBatch.add(contextualizedMessage) + primaryBatch.add(message) if (replicaBatch) { - replicaBatch.add(contextualizedMessage) + replicaBatch.add(message) } }, } From 3d4ba514e6dd393ddff97a0d9769579f273d2109 Mon Sep 17 00:00:00 2001 From: Gitlab staging reset job Date: Mon, 29 Nov 2021 10:57:44 +0100 Subject: [PATCH 08/11] =?UTF-8?q?=F0=9F=91=8C=20rename=20onInternalMonitor?= =?UTF-8?q?ingEventCollected?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../internalMonitoring/internalMonitoring.ts | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/packages/core/src/domain/internalMonitoring/internalMonitoring.ts b/packages/core/src/domain/internalMonitoring/internalMonitoring.ts index bf6661c982..cb798662b8 100644 --- a/packages/core/src/domain/internalMonitoring/internalMonitoring.ts +++ b/packages/core/src/domain/internalMonitoring/internalMonitoring.ts @@ -5,7 +5,7 @@ import { assign, combine, jsonStringify, Parameters, ThisParameterType } from '. import { canUseEventBridge, getEventBridge } from '../../transport' import { Configuration } from '../configuration' import { computeStackTrace } from '../tracekit' -import { startMonitoringBatch } from '../internalMonitoring/startMonitoringBatch' +import { startMonitoringBatch } from './startMonitoringBatch' enum StatusType { info = 'info', @@ -31,18 +31,18 @@ const monitoringConfiguration: { sentMessageCount: number } = { maxMessagesPerPage: 0, sentMessageCount: 0 } -let onInternalMonitoringEventCollected: ((message: MonitoringMessage) => void) | undefined +let onInternalMonitoringMessageCollected: ((message: MonitoringMessage) => void) | undefined export function startInternalMonitoring(configuration: Configuration): InternalMonitoring { let externalContextProvider: () => Context if (canUseEventBridge()) { const bridge = getEventBridge()! - onInternalMonitoringEventCollected = (message: MonitoringMessage) => + onInternalMonitoringMessageCollected = (message: MonitoringMessage) => bridge.send('internal_log', withContext(message)) } else if (configuration.internalMonitoringEndpointBuilder) { const batch = startMonitoringBatch(configuration) - onInternalMonitoringEventCollected = (message: MonitoringMessage) => batch.add(withContext(message)) + onInternalMonitoringMessageCollected = (message: MonitoringMessage) => batch.add(withContext(message)) } assign(monitoringConfiguration, { @@ -72,7 +72,7 @@ export function startFakeInternalMonitoring() { sentMessageCount: 0, }) - onInternalMonitoringEventCollected = (message: MonitoringMessage) => { + onInternalMonitoringMessageCollected = (message: MonitoringMessage) => { messages.push(message) } @@ -80,7 +80,7 @@ export function startFakeInternalMonitoring() { } export function resetInternalMonitoring() { - onInternalMonitoringEventCollected = undefined + onInternalMonitoringMessageCollected = undefined } export function monitored unknown>( @@ -90,7 +90,7 @@ export function monitored unknown>( ) { const originalMethod = descriptor.value! descriptor.value = function (this: any, ...args: Parameters) { - const decorated = onInternalMonitoringEventCollected ? monitor(originalMethod) : originalMethod + const decorated = onInternalMonitoringMessageCollected ? monitor(originalMethod) : originalMethod return decorated.apply(this, args) as ReturnType } as T } @@ -144,11 +144,11 @@ export function addMonitoringError(e: unknown) { function addToMonitoring(message: MonitoringMessage) { if ( - onInternalMonitoringEventCollected && + onInternalMonitoringMessageCollected && monitoringConfiguration.sentMessageCount < monitoringConfiguration.maxMessagesPerPage ) { monitoringConfiguration.sentMessageCount += 1 - onInternalMonitoringEventCollected(message) + onInternalMonitoringMessageCollected(message) } } From eeda134fb0bf7a2432605b362196a729e6328a7a Mon Sep 17 00:00:00 2001 From: Gitlab staging reset job Date: Mon, 29 Nov 2021 10:58:27 +0100 Subject: [PATCH 09/11] =?UTF-8?q?=F0=9F=91=8C=20Move=20envent=20bridge=20e?= =?UTF-8?q?2e=20test=20outside=20rum=20folder?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- test/e2e/scenario/{rum => }/eventBridge.scenario.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) rename test/e2e/scenario/{rum => }/eventBridge.scenario.ts (92%) diff --git a/test/e2e/scenario/rum/eventBridge.scenario.ts b/test/e2e/scenario/eventBridge.scenario.ts similarity index 92% rename from test/e2e/scenario/rum/eventBridge.scenario.ts rename to test/e2e/scenario/eventBridge.scenario.ts index 59c8b9eebc..8eff8a839a 100644 --- a/test/e2e/scenario/rum/eventBridge.scenario.ts +++ b/test/e2e/scenario/eventBridge.scenario.ts @@ -1,6 +1,6 @@ -import { browserExecute, flushBrowserLogs } from '../../lib/helpers/browser' -import { createTest, html } from '../../lib/framework' -import { flushEvents } from '../../lib/helpers/flushEvents' +import { browserExecute, flushBrowserLogs } from '../lib/helpers/browser' +import { createTest, html } from '../lib/framework' +import { flushEvents } from '../lib/helpers/flushEvents' describe('bridge present', () => { createTest('send action') From 053c3d7ce326bb953aa4ce935d114d44269cda20 Mon Sep 17 00:00:00 2001 From: Gitlab staging reset job Date: Mon, 29 Nov 2021 10:58:46 +0100 Subject: [PATCH 10/11] =?UTF-8?q?=F0=9F=91=8C=20Clean=20internal=20monitor?= =?UTF-8?q?ing=20index?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/core/src/domain/internalMonitoring/index.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/core/src/domain/internalMonitoring/index.ts b/packages/core/src/domain/internalMonitoring/index.ts index 34037b230c..f8d907cee3 100644 --- a/packages/core/src/domain/internalMonitoring/index.ts +++ b/packages/core/src/domain/internalMonitoring/index.ts @@ -11,4 +11,3 @@ export { setDebugMode, startInternalMonitoring, } from './internalMonitoring' -export { startMonitoringBatch } from './startMonitoringBatch' From 934cf2f553cdcd1a865fc39f9291fda08450099f Mon Sep 17 00:00:00 2001 From: Gitlab staging reset job Date: Mon, 6 Dec 2021 14:34:20 +0100 Subject: [PATCH 11/11] Merge branch 'main' into aymeric/forward-logs-event-to-bridge --- .github/CODEOWNERS | 9 ++-- .gitlab-ci.yml | 2 +- developer-extension/README.md | 2 + developer-extension/manifest.json | 11 ++-- .../src/background/domain/endSession.ts | 1 - .../src/background/domain/getConfig.ts | 36 +++++++++++++ developer-extension/src/background/index.ts | 1 + developer-extension/src/background/store.ts | 24 +++++++-- developer-extension/src/background/utils.ts | 8 +-- developer-extension/src/common/actions.ts | 6 +-- developer-extension/src/common/types.ts | 10 ++++ .../contentscript/domEventToActionMessage.ts | 8 +++ .../src/contentscript/index.ts | 1 + developer-extension/src/devtools/index.tsx | 1 + .../src/{popup => panel}/actions.ts | 0 .../src/{popup => panel}/app.tsx | 4 +- .../src/{popup => panel}/index.tsx | 0 developer-extension/src/panel/panel.tsx | 35 ++++++++++++ .../panel.tsx => panel/tabs/actionsTab.tsx} | 8 +-- .../src/panel/tabs/configTab.tsx | 27 ++++++++++ .../src/{popup => panel}/useStore.ts | 0 developer-extension/webpack.config.js | 23 ++++++-- packages/logs/src/boot/logs.entry.spec.ts | 50 ++++++++++++----- packages/logs/src/boot/logs.entry.ts | 11 ++++ packages/logs/src/boot/startLogs.spec.ts | 44 ++++++++++++++- packages/logs/src/boot/startLogs.ts | 54 +++++++------------ .../logs/src/domain/loggerSession.spec.ts | 19 ++++--- packages/logs/src/domain/loggerSession.ts | 17 +++--- .../logs/src/transport/startLoggerBatch.ts | 29 ++++++++++ packages/rum-core/src/boot/startRum.ts | 2 +- packages/rum-core/src/domain/assembly.spec.ts | 28 +++++++++- packages/rum-core/src/domain/assembly.ts | 4 ++ packages/rum-core/src/rawRumEvent.types.ts | 1 + packages/rum-core/src/rumEvent.types.ts | 25 ++++++++- .../transport/{batch.ts => startRumBatch.ts} | 0 packages/rum/README.md | 44 ++++++++++++++- rum-events-format | 2 +- test/e2e/lib/framework/pageSetups.ts | 12 ++++- test/e2e/scenario/eventBridge.scenario.ts | 14 +++++ 39 files changed, 470 insertions(+), 103 deletions(-) create mode 100644 developer-extension/src/background/domain/getConfig.ts create mode 100644 developer-extension/src/contentscript/domEventToActionMessage.ts create mode 100644 developer-extension/src/contentscript/index.ts create mode 100644 developer-extension/src/devtools/index.tsx rename developer-extension/src/{popup => panel}/actions.ts (100%) rename developer-extension/src/{popup => panel}/app.tsx (83%) rename developer-extension/src/{popup => panel}/index.tsx (100%) create mode 100644 developer-extension/src/panel/panel.tsx rename developer-extension/src/{popup/panel.tsx => panel/tabs/actionsTab.tsx} (90%) create mode 100644 developer-extension/src/panel/tabs/configTab.tsx rename developer-extension/src/{popup => panel}/useStore.ts (100%) create mode 100644 packages/logs/src/transport/startLoggerBatch.ts rename packages/rum-core/src/transport/{batch.ts => startRumBatch.ts} (100%) diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 69ec9861ad..4874c4f121 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -1,7 +1,10 @@ -* @Datadog/rum-browser +# Order is important, the last matching pattern takes the most precedence. -# Docs -*README.md @Datadog/rum-browser @DataDog/documentation +# Global +* @Datadog/rum-browser # Replay packages/rum @Datadog/rum-browser @DataDog/rum-session-replay + +# Docs +*README.md @Datadog/rum-browser @DataDog/documentation diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 6ad9184b85..4adb828281 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -1,5 +1,5 @@ variables: - CURRENT_STAGING: staging-48 + CURRENT_STAGING: staging-50 APP: 'browser-sdk' CURRENT_CI_IMAGE: 27 BUILD_STABLE_REGISTRY: '486234852809.dkr.ecr.us-east-1.amazonaws.com' diff --git a/developer-extension/README.md b/developer-extension/README.md index 2e228ebd2a..1bf3314c3d 100644 --- a/developer-extension/README.md +++ b/developer-extension/README.md @@ -21,6 +21,7 @@ Then, in Google Chrome: - Enable _Developer Mode_ by clicking the toggle switch next to _Developer mode_. - Click the _LOAD UNPACKED_ button and select the `browser-sdk/developer-extension/dist` directory. +- Open devtools and the extension features are located on the `Browser SDK` panel. ## Features @@ -29,6 +30,7 @@ Then, in Google Chrome: - End current session - Load the SDK development bundles instead of production ones - Switch between `datadog-rum-v3.js` and `datadog-rum-slim-v3.js` bundles +- Retrieve Logs/RUM configuration ## Browser compatibility diff --git a/developer-extension/manifest.json b/developer-extension/manifest.json index 1dd88cb250..2e4f3aef36 100644 --- a/developer-extension/manifest.json +++ b/developer-extension/manifest.json @@ -9,8 +9,11 @@ "scripts": ["background.js"], "persistent": true }, - "browser_action": { - "default_title": "Browser SDK extension", - "default_popup": "popup.html" - } + "content_scripts": [ + { + "js": ["contentscript.js"], + "matches": [""] + } + ], + "devtools_page": "devtools.html" } diff --git a/developer-extension/src/background/domain/endSession.ts b/developer-extension/src/background/domain/endSession.ts index ca0a9261b8..96c7465013 100644 --- a/developer-extension/src/background/domain/endSession.ts +++ b/developer-extension/src/background/domain/endSession.ts @@ -3,7 +3,6 @@ import { evaluateCodeInActiveTab } from '../utils' listenAction('endSession', () => { evaluateCodeInActiveTab(() => { - console.log('plop') document.cookie = '_dd_s=; expires=Thu, 01 Jan 1970 00:00:00 GMT; path=/' }) }) diff --git a/developer-extension/src/background/domain/getConfig.ts b/developer-extension/src/background/domain/getConfig.ts new file mode 100644 index 0000000000..2fa0a5c845 --- /dev/null +++ b/developer-extension/src/background/domain/getConfig.ts @@ -0,0 +1,36 @@ +import { evaluateCodeInActiveTab } from '../utils' +import { listenAction } from '../actions' +import { setLocalStore } from '../store' + +interface BrowserWindow { + DD_RUM?: { + getInitConfiguration: () => any + } + DD_LOGS?: { + getInitConfiguration: () => any + } +} + +listenAction('getConfig', (type) => { + evaluateCodeInActiveTab((type) => { + sendActionAsDomEvent('configReceived', { + type, + config: + type === 'rum' + ? (window as BrowserWindow).DD_RUM?.getInitConfiguration() + : (window as BrowserWindow).DD_LOGS?.getInitConfiguration(), + }) + + function sendActionAsDomEvent(action: string, payload: any) { + document.documentElement.dispatchEvent( + new CustomEvent('extension', { + detail: { action, payload }, + } as any) + ) + } + }, type) +}) + +listenAction('configReceived', ({ type, config }, tabId) => { + setLocalStore(type === 'rum' ? { rumConfig: config } : { logsConfig: config }, tabId) +}) diff --git a/developer-extension/src/background/index.ts b/developer-extension/src/background/index.ts index 999369ed99..eea524c40d 100644 --- a/developer-extension/src/background/index.ts +++ b/developer-extension/src/background/index.ts @@ -3,3 +3,4 @@ import './domain/logEventsFromRequests' import './domain/endSession' import './domain/replaceBundles' import './domain/blockIntakeRequests' +import './domain/getConfig' diff --git a/developer-extension/src/background/store.ts b/developer-extension/src/background/store.ts index c262cd2cc6..6855b58b94 100644 --- a/developer-extension/src/background/store.ts +++ b/developer-extension/src/background/store.ts @@ -1,4 +1,4 @@ -import { Store } from '../common/types' +import { Store, LocalStore } from '../common/types' import { listenAction, sendAction } from './actions' export const store: Store = { @@ -7,16 +7,32 @@ export const store: Store = { useDevBundles: false, useRumSlim: false, blockIntakeRequests: false, + local: {}, } export function setStore(newStore: Partial) { - if (wouldModifyStore(newStore)) { + if (wouldModifyStore(newStore, store)) { Object.assign(store, newStore) sendAction('newStore', store) chrome.storage.local.set({ store }) } } +export function setLocalStore(newStore: Partial, tabId: number) { + if (!store.local[tabId]) { + store.local[tabId] = { + rumConfig: {}, + logsConfig: {}, + } + } + const localStore = store.local[tabId] + if (wouldModifyStore(newStore, localStore)) { + Object.assign(localStore, newStore) + sendAction('newStore', store) + chrome.storage.local.set({ store }) + } +} + listenAction('getStore', () => sendAction('newStore', store)) listenAction('setStore', (newStore) => setStore(newStore)) @@ -26,6 +42,6 @@ chrome.storage.local.get((storage) => { } }) -function wouldModifyStore(newStore: Partial) { - return (Object.entries(newStore) as Array<[keyof Store, unknown]>).some(([key, value]) => store[key] !== value) +function wouldModifyStore(newStore: Partial, targetStore: S) { + return (Object.entries(newStore) as Array<[keyof S, unknown]>).some(([key, value]) => targetStore[key] !== value) } diff --git a/developer-extension/src/background/utils.ts b/developer-extension/src/background/utils.ts index ca1cd72156..ee4cce3582 100644 --- a/developer-extension/src/background/utils.ts +++ b/developer-extension/src/background/utils.ts @@ -1,19 +1,19 @@ -export function evaluateCodeInActiveTab(code: () => void) { +export function evaluateCodeInActiveTab(code: (arg?: string) => void, arg?: string) { chrome.tabs.query({ currentWindow: true, active: true }, (tabs) => { for (const tab of tabs) { if (tab.id) { - evaluateCodeInline(tab.id, code) + evaluateCodeInline(tab.id, code, arg) } } }) } -function evaluateCodeInline(tabId: number, code: () => void) { +function evaluateCodeInline(tabId: number, code: (arg?: string) => void, arg?: string) { chrome.tabs.executeScript(tabId, { code: `{ const script = document.createElement('script') script.setAttribute("type", "module") - script.textContent = ${JSON.stringify(`(${String(code)})()`)} + script.textContent = ${JSON.stringify(`(${String(code)})(${JSON.stringify(arg)})`)} document.body.appendChild(script) script.remove() }`, diff --git a/developer-extension/src/common/actions.ts b/developer-extension/src/common/actions.ts index 6539ccfcd0..9d1d4b24a6 100644 --- a/developer-extension/src/common/actions.ts +++ b/developer-extension/src/common/actions.ts @@ -10,10 +10,10 @@ type Message = ValueOf< > export function createListenAction() { - function listenAction(action: K, callback: (payload: Actions[K]) => void) { - const listener = (message: Message) => { + function listenAction(action: K, callback: (payload: Actions[K], tabId: number) => void) { + const listener = (message: Message, sender: chrome.runtime.MessageSender) => { if (message.action === action) { - callback((message as Message>).payload) + callback((message as Message>).payload, sender?.tab?.id) } } chrome.runtime.onMessage.addListener(listener) diff --git a/developer-extension/src/common/types.ts b/developer-extension/src/common/types.ts index 5614cc514c..f6df92c4e2 100644 --- a/developer-extension/src/common/types.ts +++ b/developer-extension/src/common/types.ts @@ -3,6 +3,8 @@ export interface BackgroundActions { setStore: Partial flushEvents: void endSession: void + getConfig: 'rum' | 'logs' + configReceived: any } export interface PopupActions { @@ -15,4 +17,12 @@ export interface Store { useRumSlim: boolean logEventsFromRequests: boolean blockIntakeRequests: boolean + local: { + [tabId: number]: LocalStore + } +} + +export interface LocalStore { + rumConfig: any + logsConfig: any } diff --git a/developer-extension/src/contentscript/domEventToActionMessage.ts b/developer-extension/src/contentscript/domEventToActionMessage.ts new file mode 100644 index 0000000000..47766d2129 --- /dev/null +++ b/developer-extension/src/contentscript/domEventToActionMessage.ts @@ -0,0 +1,8 @@ +/** + * Receive an HTML event from the page + * and forward it as an action message to extension logic + */ + +document.documentElement.addEventListener('extension', (event: any) => { + chrome.runtime.sendMessage({ action: event.detail.action, payload: event.detail.payload }) +}) diff --git a/developer-extension/src/contentscript/index.ts b/developer-extension/src/contentscript/index.ts new file mode 100644 index 0000000000..38a162e0af --- /dev/null +++ b/developer-extension/src/contentscript/index.ts @@ -0,0 +1 @@ +import './domEventToActionMessage' diff --git a/developer-extension/src/devtools/index.tsx b/developer-extension/src/devtools/index.tsx new file mode 100644 index 0000000000..7f3db6edfe --- /dev/null +++ b/developer-extension/src/devtools/index.tsx @@ -0,0 +1 @@ +chrome.devtools.panels.create('Browser SDK', 'icon.png', 'panel.html') diff --git a/developer-extension/src/popup/actions.ts b/developer-extension/src/panel/actions.ts similarity index 100% rename from developer-extension/src/popup/actions.ts rename to developer-extension/src/panel/actions.ts diff --git a/developer-extension/src/popup/app.tsx b/developer-extension/src/panel/app.tsx similarity index 83% rename from developer-extension/src/popup/app.tsx rename to developer-extension/src/panel/app.tsx index afd01e7b5e..861cb68116 100644 --- a/developer-extension/src/popup/app.tsx +++ b/developer-extension/src/panel/app.tsx @@ -1,4 +1,4 @@ -import { Provider as BumbagProvider, css, Box } from 'bumbag' +import { Provider as BumbagProvider, Box, css } from 'bumbag' import React, { Suspense } from 'react' import { Panel } from './panel' @@ -9,7 +9,7 @@ const theme = { styles: { base: css` body { - width: 300px; + min-height: 100vh; } `, }, diff --git a/developer-extension/src/popup/index.tsx b/developer-extension/src/panel/index.tsx similarity index 100% rename from developer-extension/src/popup/index.tsx rename to developer-extension/src/panel/index.tsx diff --git a/developer-extension/src/panel/panel.tsx b/developer-extension/src/panel/panel.tsx new file mode 100644 index 0000000000..bf011cad97 --- /dev/null +++ b/developer-extension/src/panel/panel.tsx @@ -0,0 +1,35 @@ +import { Tabs } from 'bumbag' +import React from 'react' +import { ActionsTab } from './tabs/actionsTab' +import { ConfigTab } from './tabs/configTab' +import { sendAction } from './actions' + +export function Panel() { + setInterval(() => { + sendAction('getConfig', 'rum') + sendAction('getConfig', 'logs') + }, 2000) + + chrome.devtools.network.onNavigated.addListener(() => { + sendAction('getConfig', 'rum') + sendAction('getConfig', 'logs') + }) + return ( + + + Actions + RUM Config + Logs Config + + + + + + + + + + + + ) +} diff --git a/developer-extension/src/popup/panel.tsx b/developer-extension/src/panel/tabs/actionsTab.tsx similarity index 90% rename from developer-extension/src/popup/panel.tsx rename to developer-extension/src/panel/tabs/actionsTab.tsx index 19388ecd16..b6571f0386 100644 --- a/developer-extension/src/popup/panel.tsx +++ b/developer-extension/src/panel/tabs/actionsTab.tsx @@ -1,9 +1,9 @@ -import { Checkbox, Stack, Button, Badge } from 'bumbag' +import { Stack, Checkbox, Badge, Button } from 'bumbag' import React from 'react' -import { sendAction } from './actions' -import { useStore } from './useStore' +import { sendAction } from '../actions' +import { useStore } from '../useStore' -export function Panel() { +export function ActionsTab() { const [ { useDevBundles, useRumSlim, logEventsFromRequests, devServerStatus, blockIntakeRequests }, setStore, diff --git a/developer-extension/src/panel/tabs/configTab.tsx b/developer-extension/src/panel/tabs/configTab.tsx new file mode 100644 index 0000000000..0344fa826e --- /dev/null +++ b/developer-extension/src/panel/tabs/configTab.tsx @@ -0,0 +1,27 @@ +import { Table } from 'bumbag' +import React from 'react' +import { useStore } from '../useStore' + +export function ConfigTab(props: { product: string }) { + const [{ local }] = useStore() + const currentTabStore = local[chrome.devtools.inspectedWindow.tabId] + const config = props.product === 'rum' ? currentTabStore?.rumConfig : currentTabStore?.logsConfig + return config ? ( + + + + Attribute + Value + + + + {Object.entries(config).map(([attribute, value]) => ( + + {attribute} + {JSON.stringify(value)} + + ))} + +
+ ) : null +} diff --git a/developer-extension/src/popup/useStore.ts b/developer-extension/src/panel/useStore.ts similarity index 100% rename from developer-extension/src/popup/useStore.ts rename to developer-extension/src/panel/useStore.ts diff --git a/developer-extension/webpack.config.js b/developer-extension/webpack.config.js index 5d9be31db0..8f8a0f613c 100644 --- a/developer-extension/webpack.config.js +++ b/developer-extension/webpack.config.js @@ -22,19 +22,36 @@ module.exports = (_env, argv) => { ], }), baseConfig({ - entry: './src/popup', + entry: './src/panel', output: { - filename: 'popup.js', + filename: 'panel.js', }, plugins: [ new HtmlWebpackPlugin({ - filename: 'popup.html', + filename: 'panel.html', }), new DefinePlugin({ 'process.env.BUMBAG_ENV': JSON.stringify('production'), }), ], }), + baseConfig({ + entry: './src/contentscript', + output: { + filename: 'contentscript.js', + }, + }), + baseConfig({ + entry: './src/devtools', + output: { + filename: 'devtools.js', + }, + plugins: [ + new HtmlWebpackPlugin({ + filename: 'devtools.html', + }), + ], + }), ] function baseConfig({ entry, output, plugins }) { diff --git a/packages/logs/src/boot/logs.entry.spec.ts b/packages/logs/src/boot/logs.entry.spec.ts index 5c3cad0dcd..42e835e9dd 100644 --- a/packages/logs/src/boot/logs.entry.spec.ts +++ b/packages/logs/src/boot/logs.entry.spec.ts @@ -1,8 +1,16 @@ -import { Context, monitor, ONE_SECOND, display } from '@datadog/browser-core' -import { Clock, mockClock } from '../../../core/test/specHelper' +import { + Context, + monitor, + ONE_SECOND, + display, + updateExperimentalFeatures, + resetExperimentalFeatures, +} from '@datadog/browser-core' +import { LogsInitConfiguration } from '..' +import { Clock, deleteEventBridgeStub, initEventBridgeStub, mockClock } from '../../../core/test/specHelper' import { HandlerType, LogsMessage, StatusType } from '../domain/logger' -import { LogsPublicApi, makeLogsPublicApi, StartLogs } from './logs.entry' +import { HybridInitConfiguration, LogsPublicApi, makeLogsPublicApi, StartLogs } from './logs.entry' const DEFAULT_INIT_CONFIGURATION = { clientToken: 'xxx' } @@ -32,19 +40,19 @@ describe('logs entry', () => { describe('configuration validation', () => { let LOGS: LogsPublicApi + let displaySpy: jasmine.Spy beforeEach(() => { + displaySpy = spyOn(display, 'error') LOGS = makeLogsPublicApi(startLogs) }) it('init should log an error with no public api key', () => { - const displaySpy = spyOn(display, 'error') - LOGS.init(undefined as any) - expect(display.error).toHaveBeenCalledTimes(1) + expect(displaySpy).toHaveBeenCalledTimes(1) LOGS.init({ stillNoApiKey: true } as any) - expect(display.error).toHaveBeenCalledTimes(2) + expect(displaySpy).toHaveBeenCalledTimes(2) LOGS.init({ clientToken: 'yeah' }) expect(displaySpy).toHaveBeenCalledTimes(2) @@ -54,23 +62,21 @@ describe('logs entry', () => { const setDebug: (debug: boolean) => void = (LOGS as any)._setDebug expect(!!setDebug).toEqual(true) - spyOn(display, 'error') monitor(() => { throw new Error() })() - expect(display.error).toHaveBeenCalledTimes(0) + expect(displaySpy).toHaveBeenCalledTimes(0) setDebug(true) monitor(() => { throw new Error() })() - expect(display.error).toHaveBeenCalledTimes(1) + expect(displaySpy).toHaveBeenCalledTimes(1) setDebug(false) }) it('init should log an error if sampleRate is invalid', () => { - const displaySpy = spyOn(display, 'error') LOGS.init({ clientToken: 'yes', sampleRate: 'foo' as any }) expect(displaySpy).toHaveBeenCalledTimes(1) @@ -79,7 +85,6 @@ describe('logs entry', () => { }) it('should log an error if init is called several times', () => { - const displaySpy = spyOn(display, 'error') LOGS.init({ clientToken: 'yes', sampleRate: 1 }) expect(displaySpy).toHaveBeenCalledTimes(0) @@ -88,7 +93,6 @@ describe('logs entry', () => { }) it('should not log an error if init is called several times and silentMultipleInit is true', () => { - const displaySpy = spyOn(display, 'error') LOGS.init({ clientToken: 'yes', sampleRate: 1, @@ -105,10 +109,28 @@ describe('logs entry', () => { }) it("shouldn't trigger any console.error if the configuration is correct", () => { - const displaySpy = spyOn(display, 'error') LOGS.init({ clientToken: 'yes', sampleRate: 1 }) expect(displaySpy).toHaveBeenCalledTimes(0) }) + + describe('if event bridge present', () => { + beforeEach(() => { + updateExperimentalFeatures(['event-bridge']) + initEventBridgeStub() + }) + + afterEach(() => { + resetExperimentalFeatures() + deleteEventBridgeStub() + }) + + it('init should accept empty client token', () => { + const hybridInitConfiguration: HybridInitConfiguration = {} + LOGS.init(hybridInitConfiguration as LogsInitConfiguration) + + expect(displaySpy).not.toHaveBeenCalled() + }) + }) }) describe('pre-init API usages', () => { diff --git a/packages/logs/src/boot/logs.entry.ts b/packages/logs/src/boot/logs.entry.ts index 45e871e356..7c6185fa6d 100644 --- a/packages/logs/src/boot/logs.entry.ts +++ b/packages/logs/src/boot/logs.entry.ts @@ -11,6 +11,7 @@ import { display, deepClone, InitConfiguration, + canUseEventBridge, } from '@datadog/browser-core' import { HandlerType, Logger, LogsMessage, StatusType } from '../domain/logger' import { startLogs, LogsInitConfiguration } from './startLogs' @@ -21,6 +22,8 @@ export interface LoggerConfiguration { context?: object } +export type HybridInitConfiguration = Omit + export type LogsPublicApi = ReturnType export const datadogLogs = makeLogsPublicApi(startLogs) @@ -49,6 +52,10 @@ export function makeLogsPublicApi(startLogsImpl: StartLogs) { logger, init: monitor((initConfiguration: LogsInitConfiguration) => { + if (canUseEventBridge()) { + initConfiguration = overrideInitConfigurationForBridge(initConfiguration) + } + if (!canInitLogs(initConfiguration)) { return } @@ -80,6 +87,10 @@ export function makeLogsPublicApi(startLogsImpl: StartLogs) { getInitConfiguration: monitor(() => getInitConfigurationStrategy()), }) + function overrideInitConfigurationForBridge(initConfiguration: C): C { + return { ...initConfiguration, clientToken: 'empty' } + } + function canInitLogs(initConfiguration: LogsInitConfiguration) { if (isAlreadyInitialized) { if (!initConfiguration.silentMultipleInit) { diff --git a/packages/logs/src/boot/startLogs.spec.ts b/packages/logs/src/boot/startLogs.spec.ts index 4587884cf3..cea898e50a 100644 --- a/packages/logs/src/boot/startLogs.spec.ts +++ b/packages/logs/src/boot/startLogs.spec.ts @@ -8,14 +8,22 @@ import { ONE_MINUTE, RawError, RelativeTime, + resetExperimentalFeatures, TimeStamp, + updateExperimentalFeatures, } from '@datadog/browser-core' import sinon from 'sinon' -import { Clock, mockClock, stubEndpointBuilder } from '../../../core/test/specHelper' +import { + Clock, + deleteEventBridgeStub, + initEventBridgeStub, + mockClock, + stubEndpointBuilder, +} from '../../../core/test/specHelper' import { Logger, LogsMessage, StatusType } from '../domain/logger' import { LogsEvent } from '../logsEvent.types' -import { buildAssemble, doStartLogs } from './startLogs' +import { buildAssemble, doStartLogs, LogsInitConfiguration, startLogs as originalStartLogs } from './startLogs' interface SentMessage extends LogsMessage { logger?: { name: string } @@ -75,6 +83,8 @@ describe('logs', () => { afterEach(() => { server.restore() delete window.DD_RUM + resetExperimentalFeatures() + deleteEventBridgeStub() }) describe('request', () => { @@ -127,6 +137,36 @@ describe('logs', () => { expect(server.requests.length).toEqual(1) }) + + it('should send bridge event when bridge is present', () => { + updateExperimentalFeatures(['event-bridge']) + const sendSpy = spyOn(initEventBridgeStub(), 'send') + + const sendLog = startLogs() + sendLog(DEFAULT_MESSAGE, {}) + + expect(server.requests.length).toEqual(0) + expect(sendSpy).toHaveBeenCalled() + }) + }) + + describe('sampling', () => { + it('should be applied when event bridge is present', () => { + updateExperimentalFeatures(['event-bridge']) + const sendSpy = spyOn(initEventBridgeStub(), 'send') + + let configuration = { ...baseConfiguration, ...{ sampleRate: 0 } } as LogsInitConfiguration + let sendLog = originalStartLogs(configuration, new Logger(noop)) + sendLog(DEFAULT_MESSAGE, {}) + + expect(sendSpy).not.toHaveBeenCalled() + + configuration = { ...baseConfiguration, ...{ sampleRate: 100 } } as LogsInitConfiguration + sendLog = originalStartLogs(configuration, new Logger(noop)) + sendLog(DEFAULT_MESSAGE, {}) + + expect(sendSpy).toHaveBeenCalled() + }) }) describe('assemble', () => { diff --git a/packages/logs/src/boot/startLogs.ts b/packages/logs/src/boot/startLogs.ts index 9f8c8c0bc0..ef581683b0 100644 --- a/packages/logs/src/boot/startLogs.ts +++ b/packages/logs/src/boot/startLogs.ts @@ -1,12 +1,10 @@ import { areCookiesAuthorized, - Batch, combine, commonInit, Configuration, Context, createEventRateLimiter, - HttpRequest, InternalMonitoring, Observable, RawError, @@ -14,12 +12,14 @@ import { InitConfiguration, trackRuntimeError, trackConsoleError, - EndpointBuilder, + canUseEventBridge, + getEventBridge, } from '@datadog/browser-core' import { trackNetworkError } from '../domain/trackNetworkError' import { Logger, LogsMessage, StatusType } from '../domain/logger' -import { LoggerSession, startLoggerSession } from '../domain/loggerSession' +import { LoggerSession, startLoggerSession, startLoggerSessionStub } from '../domain/loggerSession' import { LogsEvent } from '../logsEvent.types' +import { startLoggerBatch } from '../transport/startLoggerBatch' import { buildEnv } from './buildEnv' export interface LogsInitConfiguration extends InitConfiguration { @@ -37,7 +37,11 @@ export function startLogs(initConfiguration: LogsInitConfiguration, errorLogger: trackNetworkError(configuration, errorObservable) } - const session = startLoggerSession(configuration, areCookiesAuthorized(configuration.cookieOptions)) + const session = + areCookiesAuthorized(configuration.cookieOptions) && !canUseEventBridge() + ? startLoggerSession(configuration) + : startLoggerSessionStub(configuration) + return doStartLogs(configuration, errorObservable, internalMonitoring, session, errorLogger) } @@ -55,7 +59,15 @@ export function doStartLogs( ) const assemble = buildAssemble(session, configuration, reportError) - const batch = startLoggerBatch(configuration) + + let onLogEventCollected: (message: Context) => void + if (canUseEventBridge()) { + const bridge = getEventBridge()! + onLogEventCollected = (message) => bridge.send('log', message) + } else { + const batch = startLoggerBatch(configuration) + onLogEventCollected = (message) => batch.add(message) + } function reportError(error: RawError) { errorLogger.error( @@ -87,39 +99,11 @@ export function doStartLogs( return (message: LogsMessage, currentContext: Context) => { const contextualizedMessage = assemble(message, currentContext) if (contextualizedMessage) { - batch.add(contextualizedMessage) + onLogEventCollected(contextualizedMessage) } } } -function startLoggerBatch(configuration: Configuration) { - const primaryBatch = createLoggerBatch(configuration.logsEndpointBuilder) - - let replicaBatch: Batch | undefined - if (configuration.replica !== undefined) { - replicaBatch = createLoggerBatch(configuration.replica.logsEndpointBuilder) - } - - function createLoggerBatch(endpointBuilder: EndpointBuilder) { - return new Batch( - new HttpRequest(endpointBuilder, configuration.batchBytesLimit), - configuration.maxBatchSize, - configuration.batchBytesLimit, - configuration.maxMessageSize, - configuration.flushTimeout - ) - } - - return { - add(message: Context) { - primaryBatch.add(message) - if (replicaBatch) { - replicaBatch.add(message) - } - }, - } -} - export function buildAssemble( session: LoggerSession, configuration: Configuration, diff --git a/packages/logs/src/domain/loggerSession.spec.ts b/packages/logs/src/domain/loggerSession.spec.ts index 5fa8ec6b0b..5e218e4b38 100644 --- a/packages/logs/src/domain/loggerSession.spec.ts +++ b/packages/logs/src/domain/loggerSession.spec.ts @@ -8,7 +8,7 @@ import { } from '@datadog/browser-core' import { Clock, mockClock } from '../../../core/test/specHelper' -import { LOGGER_SESSION_KEY, LoggerTrackingType, startLoggerSession } from './loggerSession' +import { LOGGER_SESSION_KEY, LoggerTrackingType, startLoggerSession, startLoggerSessionStub } from './loggerSession' describe('logger session', () => { const DURATION = 123456 @@ -32,7 +32,7 @@ describe('logger session', () => { it('when tracked should store tracking type and session id', () => { tracked = true - startLoggerSession(configuration as Configuration, true) + startLoggerSession(configuration as Configuration) expect(getCookie(SESSION_COOKIE_NAME)).toContain(`${LOGGER_SESSION_KEY}=${LoggerTrackingType.TRACKED}`) expect(getCookie(SESSION_COOKIE_NAME)).toMatch(/id=[a-f0-9-]+/) @@ -41,7 +41,7 @@ describe('logger session', () => { it('when not tracked should store tracking type', () => { tracked = false - startLoggerSession(configuration as Configuration, true) + startLoggerSession(configuration as Configuration) expect(getCookie(SESSION_COOKIE_NAME)).toContain(`${LOGGER_SESSION_KEY}=${LoggerTrackingType.NOT_TRACKED}`) expect(getCookie(SESSION_COOKIE_NAME)).not.toContain('id=') @@ -50,7 +50,7 @@ describe('logger session', () => { it('when tracked should keep existing tracking type and session id', () => { setCookie(SESSION_COOKIE_NAME, 'id=abcdef&logs=1', DURATION) - startLoggerSession(configuration as Configuration, true) + startLoggerSession(configuration as Configuration) expect(getCookie(SESSION_COOKIE_NAME)).toContain(`${LOGGER_SESSION_KEY}=${LoggerTrackingType.TRACKED}`) expect(getCookie(SESSION_COOKIE_NAME)).toContain('id=abcdef') @@ -59,13 +59,13 @@ describe('logger session', () => { it('when not tracked should keep existing tracking type', () => { setCookie(SESSION_COOKIE_NAME, 'logs=0', DURATION) - startLoggerSession(configuration as Configuration, true) + startLoggerSession(configuration as Configuration) expect(getCookie(SESSION_COOKIE_NAME)).toContain(`${LOGGER_SESSION_KEY}=${LoggerTrackingType.NOT_TRACKED}`) }) it('should renew on activity after expiration', () => { - startLoggerSession(configuration as Configuration, true) + startLoggerSession(configuration as Configuration) setCookie(SESSION_COOKIE_NAME, '', DURATION) expect(getCookie(SESSION_COOKIE_NAME)).toBeUndefined() @@ -77,9 +77,12 @@ describe('logger session', () => { expect(getCookie(SESSION_COOKIE_NAME)).toMatch(/id=[a-f0-9-]+/) expect(getCookie(SESSION_COOKIE_NAME)).toContain(`${LOGGER_SESSION_KEY}=${LoggerTrackingType.TRACKED}`) }) +}) - it('when no cookies available, isTracked is computed at each call and getId is undefined', () => { - const session = startLoggerSession(configuration as Configuration, false) +describe('logger session stub', () => { + it('isTracked is computed at each call and getId is undefined', () => { + const configuration: Partial = { sampleRate: 0.5 } + const session = startLoggerSessionStub(configuration as Configuration) expect(session.getId()).toBeUndefined() expect(session.isTracked()).toMatch(/true|false/) diff --git a/packages/logs/src/domain/loggerSession.ts b/packages/logs/src/domain/loggerSession.ts index eab4925cc9..0d6bef447b 100644 --- a/packages/logs/src/domain/loggerSession.ts +++ b/packages/logs/src/domain/loggerSession.ts @@ -12,14 +12,7 @@ export enum LoggerTrackingType { TRACKED = '1', } -export function startLoggerSession(configuration: Configuration, areCookieAuthorized: boolean): LoggerSession { - if (!areCookieAuthorized) { - const isTracked = computeTrackingType(configuration) === LoggerTrackingType.TRACKED - return { - getId: () => undefined, - isTracked: () => isTracked, - } - } +export function startLoggerSession(configuration: Configuration): LoggerSession { const session = startSessionManagement(configuration.cookieOptions, LOGGER_SESSION_KEY, (rawTrackingType) => computeSessionState(configuration, rawTrackingType) ) @@ -29,6 +22,14 @@ export function startLoggerSession(configuration: Configuration, areCookieAuthor } } +export function startLoggerSessionStub(configuration: Configuration) { + const isTracked = computeTrackingType(configuration) === LoggerTrackingType.TRACKED + return { + getId: () => undefined, + isTracked: () => isTracked, + } +} + function computeTrackingType(configuration: Configuration) { if (!performDraw(configuration.sampleRate)) { return LoggerTrackingType.NOT_TRACKED diff --git a/packages/logs/src/transport/startLoggerBatch.ts b/packages/logs/src/transport/startLoggerBatch.ts new file mode 100644 index 0000000000..089054aa5d --- /dev/null +++ b/packages/logs/src/transport/startLoggerBatch.ts @@ -0,0 +1,29 @@ +import { Batch, Configuration, Context, HttpRequest, EndpointBuilder } from '@datadog/browser-core' + +export function startLoggerBatch(configuration: Configuration) { + const primaryBatch = createLoggerBatch(configuration.logsEndpointBuilder) + + let replicaBatch: Batch | undefined + if (configuration.replica !== undefined) { + replicaBatch = createLoggerBatch(configuration.replica.logsEndpointBuilder) + } + + function createLoggerBatch(endpointBuilder: EndpointBuilder) { + return new Batch( + new HttpRequest(endpointBuilder, configuration.batchBytesLimit), + configuration.maxBatchSize, + configuration.batchBytesLimit, + configuration.maxMessageSize, + configuration.flushTimeout + ) + } + + return { + add(message: Context) { + primaryBatch.add(message) + if (replicaBatch) { + replicaBatch.add(message) + } + }, + } +} diff --git a/packages/rum-core/src/boot/startRum.ts b/packages/rum-core/src/boot/startRum.ts index fbae1f97a4..5b2ea7e945 100644 --- a/packages/rum-core/src/boot/startRum.ts +++ b/packages/rum-core/src/boot/startRum.ts @@ -14,7 +14,7 @@ import { startResourceCollection } from '../domain/rumEventsCollection/resource/ import { startViewCollection } from '../domain/rumEventsCollection/view/viewCollection' import { RumSession, startRumSession, startRumSessionStub } from '../domain/rumSession' import { CommonContext } from '../rawRumEvent.types' -import { startRumBatch } from '../transport/batch' +import { startRumBatch } from '../transport/startRumBatch' import { startRumEventBridge } from '../transport/startRumEventBridge' import { startUrlContexts } from '../domain/urlContexts' import { createLocationChangeObservable, LocationChange } from '../browser/locationChangeObservable' diff --git a/packages/rum-core/src/domain/assembly.spec.ts b/packages/rum-core/src/domain/assembly.spec.ts index d4b116cfd4..d04cdb36a4 100644 --- a/packages/rum-core/src/domain/assembly.spec.ts +++ b/packages/rum-core/src/domain/assembly.spec.ts @@ -1,4 +1,12 @@ -import { ErrorSource, ONE_MINUTE, RawError, RelativeTime, display } from '@datadog/browser-core' +import { + ErrorSource, + ONE_MINUTE, + RawError, + RelativeTime, + display, + updateExperimentalFeatures, + resetExperimentalFeatures, +} from '@datadog/browser-core' import { createRumSessionMock } from '../../test/mockRumSession' import { createRawRumEvent } from '../../test/fixtures' import { @@ -10,6 +18,7 @@ import { import { RumEventDomainContext } from '../domainContext.types' import { CommonContext, RawRumActionEvent, RawRumErrorEvent, RawRumEvent, RumEventType } from '../rawRumEvent.types' import { RumActionEvent, RumErrorEvent, RumEvent } from '../rumEvent.types' +import { initEventBridgeStub, deleteEventBridgeStub } from '../../../core/test/specHelper' import { startRumAssembly } from './assembly' import { LifeCycle, LifeCycleEventType, RawRumEventCollectedData } from './lifeCycle' import { RumSessionPlan } from './rumSession' @@ -60,6 +69,8 @@ describe('rum assembly', () => { }) afterEach(() => { + resetExperimentalFeatures() + deleteEventBridgeStub() setupBuilder.cleanup() cleanupSyntheticsWorkerValues() }) @@ -566,6 +577,21 @@ describe('rum assembly', () => { }) }) + describe('if event bridge detected', () => { + it('includes the browser sdk version', () => { + const { lifeCycle } = setupBuilder.build() + notifyRawRumEvent(lifeCycle, { rawRumEvent: createRawRumEvent(RumEventType.VIEW) }) + + updateExperimentalFeatures(['event-bridge']) + initEventBridgeStub() + + notifyRawRumEvent(lifeCycle, { rawRumEvent: createRawRumEvent(RumEventType.VIEW) }) + + expect(serverRumEvents[0]._dd.browser_sdk_version).not.toBeDefined() + expect(serverRumEvents[1]._dd.browser_sdk_version).toBeDefined() + }) + }) + describe('error events limitation', () => { const notifiedRawErrors: RawError[] = [] diff --git a/packages/rum-core/src/domain/assembly.ts b/packages/rum-core/src/domain/assembly.ts index 6d88bac094..960be29642 100644 --- a/packages/rum-core/src/domain/assembly.ts +++ b/packages/rum-core/src/domain/assembly.ts @@ -11,6 +11,7 @@ import { RawError, createEventRateLimiter, EventRateLimiter, + canUseEventBridge, } from '@datadog/browser-core' import { RumEventDomainContext } from '../domainContext.types' import { @@ -24,6 +25,7 @@ import { User, } from '../rawRumEvent.types' import { RumEvent } from '../rumEvent.types' +import { buildEnv } from '../boot/buildEnv' import { getSyntheticsContext } from './syntheticsContext' import { LifeCycle, LifeCycleEventType } from './lifeCycle' import { ParentContexts } from './parentContexts' @@ -89,6 +91,7 @@ export function startRumAssembly( session: { plan: session.hasReplayPlan() ? RumSessionPlan.REPLAY : RumSessionPlan.LITE, }, + browser_sdk_version: canUseEventBridge() ? buildEnv.sdkVersion : undefined, }, application: { id: applicationId, @@ -113,6 +116,7 @@ export function startRumAssembly( if (!isEmptyObject(commonContext.user)) { ;(serverRumEvent.usr as Mutable) = commonContext.user as User & Context } + if (shouldSend(serverRumEvent, configuration.beforeSend, domainContext, eventRateLimiters)) { if (isEmptyObject(serverRumEvent.context)) { delete serverRumEvent.context diff --git a/packages/rum-core/src/rawRumEvent.types.ts b/packages/rum-core/src/rawRumEvent.types.ts index 7891ae09b7..e9ee101e80 100644 --- a/packages/rum-core/src/rawRumEvent.types.ts +++ b/packages/rum-core/src/rawRumEvent.types.ts @@ -188,6 +188,7 @@ export interface RumContext { session: { plan: RumSessionPlan } + browser_sdk_version?: string } } diff --git a/packages/rum-core/src/rumEvent.types.ts b/packages/rum-core/src/rumEvent.types.ts index f5a35add65..e50f48bfd7 100644 --- a/packages/rum-core/src/rumEvent.types.ts +++ b/packages/rum-core/src/rumEvent.types.ts @@ -139,6 +139,10 @@ export type RumErrorEvent = CommonProperties & { * Handling call stack */ readonly handling_stack?: string + /** + * Source type of the error (the language or platform impacting the error stacktrace format) + */ + readonly source_type?: 'android' | 'browser' | 'ios' | 'react-native' /** * Resource properties of the error */ @@ -270,7 +274,18 @@ export type RumResourceEvent = CommonProperties & { /** * Resource type */ - readonly type: 'document' | 'xhr' | 'beacon' | 'fetch' | 'css' | 'js' | 'image' | 'font' | 'media' | 'other' + readonly type: + | 'document' + | 'xhr' + | 'beacon' + | 'fetch' + | 'css' + | 'js' + | 'image' + | 'font' + | 'media' + | 'other' + | 'native' /** * HTTP method of the resource */ @@ -760,6 +775,10 @@ export interface CommonProperties { * The identifier of the current Synthetics test results */ readonly result_id: string + /** + * Whether the event comes from a SDK instance injected by Synthetics + */ + readonly injected?: boolean [k: string]: unknown } /** @@ -780,6 +799,10 @@ export interface CommonProperties { plan: 1 | 2 [k: string]: unknown } + /** + * Browser SDK version + */ + readonly browser_sdk_version?: string [k: string]: unknown } /** diff --git a/packages/rum-core/src/transport/batch.ts b/packages/rum-core/src/transport/startRumBatch.ts similarity index 100% rename from packages/rum-core/src/transport/batch.ts rename to packages/rum-core/src/transport/startRumBatch.ts diff --git a/packages/rum/README.md b/packages/rum/README.md index a0a31d121a..07ac475d66 100644 --- a/packages/rum/README.md +++ b/packages/rum/README.md @@ -187,13 +187,13 @@ Specify your own attribute to be used to [name actions][9]. : Optional
**Type**: Number
**Default**: `100`
-The percentage of sessions to track: `100` for all, `0` for none. Only tracked sessions send rum events. +The percentage of sessions to track: `100` for all, `0` for none. Only tracked sessions send RUM events. For more details about `sampleRate`, see the [sampling configuration](#browser-and-session-replay-sampling-configuration). `replaySampleRate` : Optional
**Type**: Number
**Default**: `100`
-The percentage of tracked sessions with session replay pricing features: `100` for all, `0` for none. See [rum pricing][11] for more information. +The percentage of tracked sessions with [Session Replay pricing][11] features: `100` for all, `0` for none. For more details about `replaySampleRate`, see the [sampling configuration](#browser-and-session-replay-sampling-configuration). `silentMultipleInit` : Optional
@@ -253,6 +253,46 @@ init(configuration: { }) ``` +### Browser and Session Replay sampling configuration + +The `sampleRate` option controls the overall sample rate of RUM data collection. The `replaySampleRate` option controls the percentage of Session Replay data collection of the overall rate (the collection of **Resources**, **Long Tasks**, and **Replay** recordings). + +For example, to collect 100% of your sessions using only the Browser RUM option: + +``` +datadogRum.init({ + .... + sampleRate: 100, + replaySampleRate: 0 +}); +``` + +For example, to collect 100% of your sessions using only the Session Replay RUM option without recording a replay: + +``` +datadogRum.init({ + .... + sampleRate: 100, + replaySampleRate: 100 // Note: if this is not included it will default to 100% +}); +``` + +For example, to collect 25% of your sessions using the Browser RUM option and 25% of your sessions using the Session Replay RUM option: + +``` +datadogRum.init({ + .... + sampleRate: 50, + replaySampleRate: 50 +}); +``` + +In the example above, 50% of all sessions are collected. The Session Replay RUM option tracks half of these sessions while the Browser RUM option tracks the remaining half of these sessions. + +## Further Reading + +{{< partial name="whats-next/whats-next.html" >}} + [1]: https://app.datadoghq.com/rum/list [2]: https://docs.datadoghq.com/real_user_monitoring/data_collected/ [3]: https://docs.datadoghq.com/real_user_monitoring/dashboards/ diff --git a/rum-events-format b/rum-events-format index cdf9a70e6b..00fa446544 160000 --- a/rum-events-format +++ b/rum-events-format @@ -1 +1 @@ -Subproject commit cdf9a70e6be9cfec5e9524c58abfe79a9fea1f64 +Subproject commit 00fa4465444e122e9c904f5531a701eeca4ceb0e diff --git a/test/e2e/lib/framework/pageSetups.ts b/test/e2e/lib/framework/pageSetups.ts index 7e6a87120d..8640a636aa 100644 --- a/test/e2e/lib/framework/pageSetups.ts +++ b/test/e2e/lib/framework/pageSetups.ts @@ -168,7 +168,17 @@ function setupEventBridge(servers: Servers) { send(e) { const { eventType, event } = JSON.parse(e) const request = new XMLHttpRequest() - const endpoint = eventType === 'internal_log' ? 'internalMonitoring' : 'rum' + let endpoint + switch (eventType) { + case 'internal_log': + endpoint = 'internalMonitoring' + break + case 'log': + endpoint = 'logs' + break + default: + endpoint = 'rum' + } request.open('POST', \`${servers.intake.url}/v1/input/\${endpoint}?bridge=1\`, true) request.send(JSON.stringify(event)) }, diff --git a/test/e2e/scenario/eventBridge.scenario.ts b/test/e2e/scenario/eventBridge.scenario.ts index 8eff8a839a..cb00bb0fe8 100644 --- a/test/e2e/scenario/eventBridge.scenario.ts +++ b/test/e2e/scenario/eventBridge.scenario.ts @@ -79,4 +79,18 @@ describe('bridge present', () => { expect(serverEvents.internalMonitoring.length).toBe(0) expect(bridgeEvents.internalMonitoring.length).toBe(1) }) + + createTest('forward logs to the bridge') + .withLogs({ enableExperimentalFeatures: ['event-bridge'] }) + .withEventBridge() + .run(async ({ serverEvents, bridgeEvents }) => { + await browserExecute(() => { + // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion + window.DD_LOGS!.logger.log('hello') + }) + await flushEvents() + + expect(serverEvents.logs.length).toBe(0) + expect(bridgeEvents.logs.length).toBe(1) + }) })