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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 38 additions & 6 deletions packages/logs/src/boot/logsPublicApi.spec.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,21 @@
import type { ContextManager } from '@datadog/browser-core'
import { monitor, display, createContextManager, TrackingConsent, startTelemetry } from '@datadog/browser-core'
import {
monitor,
display,
createContextManager,
TrackingConsent,
startTelemetry,
startSessionManager,
} from '@datadog/browser-core'
import {
collectAsyncCalls,
createFakeTelemetryObject,
replaceMockable,
replaceMockableWithSpy,
createStartSessionManagerMock,
} from '@datadog/browser-core/test'
import { HandlerType } from '../domain/logger'
import { StatusType } from '../domain/logger/isAuthorized'
import { createFakeTelemetryObject, replaceMockable, replaceMockableWithSpy } from '../../../core/test'
import type { LogsPublicApi } from './logsPublicApi'
import { makeLogsPublicApi } from './logsPublicApi'
import type { StartLogs, StartLogsResult } from './startLogs'
Expand Down Expand Up @@ -49,15 +62,16 @@ describe('logs entry', () => {
let logsPublicApi: LogsPublicApi
let startLogsSpy: jasmine.Spy<StartLogs>

beforeEach(() => {
beforeEach(async () => {
;({ logsPublicApi, startLogsSpy } = makeLogsPublicApiWithDefaults())
logsPublicApi.init(DEFAULT_INIT_CONFIGURATION)
await collectAsyncCalls(startLogsSpy, 1)
})

it('should have the current date, view and global context', () => {
logsPublicApi.setGlobalContextProperty('foo', 'bar')

const getCommonContext = startLogsSpy.calls.mostRecent().args[1]
const getCommonContext = startLogsSpy.calls.mostRecent().args[2]
expect(getCommonContext()).toEqual({
view: {
referrer: document.referrer,
Expand All @@ -67,22 +81,38 @@ describe('logs entry', () => {
})
})

it('buffered calls should be replayed before microtasks scheduled after init', async () => {
const { logsPublicApi, handleLogSpy, getLoggedMessage } = makeLogsPublicApiWithDefaults()
logsPublicApi.logger.log('before_init')
logsPublicApi.init(DEFAULT_INIT_CONFIGURATION)

// Simulate user code scheduling a microtask right after init
void Promise.resolve().then(() => logsPublicApi.logger.log('after_init'))

await collectAsyncCalls(handleLogSpy, 2)

expect(getLoggedMessage(0).message.message).toBe('before_init')
expect(getLoggedMessage(1).message.message).toBe('after_init')
})

describe('post start API usages', () => {
let logsPublicApi: LogsPublicApi
let getLoggedMessage: ReturnType<typeof makeLogsPublicApiWithDefaults>['getLoggedMessage']
let startLogsSpy: jasmine.Spy<StartLogs>
let userContext: ContextManager
let accountContext: ContextManager

beforeEach(() => {
beforeEach(async () => {
userContext = createContextManager('mock')
accountContext = createContextManager('mock')
;({ logsPublicApi, getLoggedMessage } = makeLogsPublicApiWithDefaults({
;({ logsPublicApi, getLoggedMessage, startLogsSpy } = makeLogsPublicApiWithDefaults({
startLogsResult: {
userContext,
accountContext,
},
}))
logsPublicApi.init(DEFAULT_INIT_CONFIGURATION)
await collectAsyncCalls(startLogsSpy, 1)
})

it('main logger logs a message', () => {
Expand Down Expand Up @@ -240,9 +270,11 @@ function makeLogsPublicApiWithDefaults({
}

replaceMockable(startTelemetry, createFakeTelemetryObject)
replaceMockable(startSessionManager, createStartSessionManagerMock())

return {
startLogsSpy,
handleLogSpy,
logsPublicApi: makeLogsPublicApi(),
getLoggedMessage,
}
Expand Down
15 changes: 9 additions & 6 deletions packages/logs/src/boot/logsPublicApi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -274,16 +274,16 @@ export function makeLogsPublicApi(): LogsPublicApi {
let strategy = createPreStartStrategy(
buildCommonContext,
trackingConsentState,
(initConfiguration, configuration, hooks) => {
(configuration, logsSessionManager, hooks) => {
const startLogsResult = mockable(startLogs)(
configuration,
logsSessionManager,
buildCommonContext,
trackingConsentState,
bufferedDataObservable,
hooks
)

strategy = createPostStartStrategy(initConfiguration, startLogsResult)
strategy = createPostStartStrategy(strategy, startLogsResult)
return startLogsResult
}
)
Expand Down Expand Up @@ -396,12 +396,15 @@ export function makeLogsPublicApi(): LogsPublicApi {
})
}

function createPostStartStrategy(initConfiguration: LogsInitConfiguration, startLogsResult: StartLogsResult): Strategy {
function createPostStartStrategy(preStartStrategy: Strategy, startLogsResult: StartLogsResult): Strategy {
return {
...preStartStrategy,
init: (initConfiguration: LogsInitConfiguration) => {
displayAlreadyInitializedError('DD_LOGS', initConfiguration)
},
initConfiguration,
...startLogsResult,
getInternalContext: startLogsResult.getInternalContext,
globalContext: startLogsResult.globalContext,
userContext: startLogsResult.userContext,
accountContext: startLogsResult.accountContext,
}
}
79 changes: 49 additions & 30 deletions packages/logs/src/boot/preStartLogs.spec.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,23 @@
import {
callbackAddsInstrumentation,
collectAsyncCalls,
type Clock,
mockClock,
mockEventBridge,
waitNextMicrotask,
createFakeTelemetryObject,
replaceMockable,
replaceMockableWithSpy,
createStartSessionManagerMock,
} from '@datadog/browser-core/test'
import type { TrackingConsentState } from '@datadog/browser-core'
import { ONE_SECOND, TrackingConsent, createTrackingConsentState, display, startTelemetry } from '@datadog/browser-core'
import {
ONE_SECOND,
TrackingConsent,
createTrackingConsentState,
display,
startTelemetry,
startSessionManager,
} from '@datadog/browser-core'
import type { CommonContext } from '../rawLogsEvent.types'
import type { HybridInitConfiguration, LogsInitConfiguration } from '../domain/configuration'
import type { Logger } from '../domain/logger'
Expand Down Expand Up @@ -37,9 +47,10 @@ describe('preStartLogs', () => {
displaySpy = spyOn(display, 'error')
})

it('should start when the configuration is valid', () => {
it('should start when the configuration is valid', async () => {
strategy.init(DEFAULT_INIT_CONFIGURATION)
expect(displaySpy).not.toHaveBeenCalled()
await collectAsyncCalls(doStartLogsSpy, 1)
expect(doStartLogsSpy).toHaveBeenCalled()
})

Expand Down Expand Up @@ -89,17 +100,27 @@ describe('preStartLogs', () => {
mockEventBridge()
})

it('init should accept empty client token', () => {
it('init should accept empty client token', async () => {
const hybridInitConfiguration: HybridInitConfiguration = {}
strategy.init(hybridInitConfiguration as LogsInitConfiguration)

await collectAsyncCalls(doStartLogsSpy, 1)
expect(displaySpy).not.toHaveBeenCalled()
expect(doStartLogsSpy).toHaveBeenCalled()
})
})
})

it('allows sending logs', () => {
it('should not start when session manager initialization fails', async () => {
const { strategy, doStartLogsSpy } = createPreStartStrategyWithDefaults({
startSessionManagerMock: () => Promise.reject(new Error('Session init failed')),
})
strategy.init(DEFAULT_INIT_CONFIGURATION)
await collectAsyncCalls(doStartLogsSpy, 0)
expect(doStartLogsSpy).not.toHaveBeenCalled()
})

it('allows sending logs', async () => {
const { strategy, handleLogSpy, getLoggedMessage } = createPreStartStrategyWithDefaults()
strategy.handleLog(
{
Expand All @@ -111,6 +132,7 @@ describe('preStartLogs', () => {

expect(handleLogSpy).not.toHaveBeenCalled()
strategy.init(DEFAULT_INIT_CONFIGURATION)
await collectAsyncCalls(handleLogSpy, 1)

expect(handleLogSpy.calls.all().length).toBe(1)
expect(getLoggedMessage(0).message.message).toBe('message')
Expand All @@ -122,8 +144,9 @@ describe('preStartLogs', () => {
})

describe('save context when submitting a log', () => {
it('saves the date', () => {
const { strategy, getLoggedMessage } = createPreStartStrategyWithDefaults()
it('saves the date', async () => {
mockEventBridge()
const { strategy, getLoggedMessage, handleLogSpy } = createPreStartStrategyWithDefaults()
strategy.handleLog(
{
status: StatusType.info,
Expand All @@ -133,12 +156,13 @@ describe('preStartLogs', () => {
)
clock.tick(ONE_SECOND)
strategy.init(DEFAULT_INIT_CONFIGURATION)
await collectAsyncCalls(handleLogSpy, 1)

expect(getLoggedMessage(0).savedDate).toEqual(Date.now() - ONE_SECOND)
})

it('saves the URL', () => {
const { strategy, getLoggedMessage, getCommonContextSpy } = createPreStartStrategyWithDefaults()
it('saves the URL', async () => {
const { strategy, getLoggedMessage, getCommonContextSpy, handleLogSpy } = createPreStartStrategyWithDefaults()
getCommonContextSpy.and.returnValue({ view: { url: 'url' } } as unknown as CommonContext)
strategy.handleLog(
{
Expand All @@ -149,11 +173,12 @@ describe('preStartLogs', () => {
)
strategy.init(DEFAULT_INIT_CONFIGURATION)

await collectAsyncCalls(handleLogSpy, 1)
expect(getLoggedMessage(0).savedCommonContext!.view?.url).toEqual('url')
})

it('saves the log context', () => {
const { strategy, getLoggedMessage } = createPreStartStrategyWithDefaults()
it('saves the log context', async () => {
const { strategy, getLoggedMessage, handleLogSpy } = createPreStartStrategyWithDefaults()
const context = { foo: 'bar' }
strategy.handleLog(
{
Expand All @@ -166,6 +191,7 @@ describe('preStartLogs', () => {
context.foo = 'baz'

strategy.init(DEFAULT_INIT_CONFIGURATION)
await collectAsyncCalls(handleLogSpy, 1)

expect(getLoggedMessage(0).message.context!.foo).toEqual('bar')
})
Expand All @@ -188,21 +214,6 @@ describe('preStartLogs', () => {
;({ strategy, doStartLogsSpy } = createPreStartStrategyWithDefaults({ trackingConsentState }))
})

describe('basic methods instrumentation', () => {
it('should instrument fetch even if tracking consent is not granted', () => {
expect(
callbackAddsInstrumentation(() => {
strategy.init({
...DEFAULT_INIT_CONFIGURATION,
trackingConsent: TrackingConsent.NOT_GRANTED,
})
})
.toMethod(window, 'fetch')
.whenCalled()
).toBeTrue()
})
})

it('does not start logs if tracking consent is not granted at init', () => {
strategy.init({
...DEFAULT_INIT_CONFIGURATION,
Expand All @@ -211,12 +222,13 @@ describe('preStartLogs', () => {
expect(doStartLogsSpy).not.toHaveBeenCalled()
})

it('starts logs if tracking consent is granted before init', () => {
it('starts logs if tracking consent is granted before init', async () => {
trackingConsentState.update(TrackingConsent.GRANTED)
strategy.init({
...DEFAULT_INIT_CONFIGURATION,
trackingConsent: TrackingConsent.NOT_GRANTED,
})
await collectAsyncCalls(doStartLogsSpy, 1)
expect(doStartLogsSpy).toHaveBeenCalledTimes(1)
})

Expand All @@ -229,24 +241,27 @@ describe('preStartLogs', () => {
expect(doStartLogsSpy).not.toHaveBeenCalled()
})

it('do not call startLogs when tracking consent state is updated after init', () => {
it('do not call startLogs when tracking consent state is updated after init', async () => {
strategy.init(DEFAULT_INIT_CONFIGURATION)
await collectAsyncCalls(doStartLogsSpy, 1)
doStartLogsSpy.calls.reset()

trackingConsentState.update(TrackingConsent.GRANTED)
await waitNextMicrotask()

expect(doStartLogsSpy).not.toHaveBeenCalled()
})
})

describe('telemetry', () => {
it('starts telemetry during init() by default', () => {
it('starts telemetry during init() by default', async () => {
const { strategy, startTelemetrySpy } = createPreStartStrategyWithDefaults()
strategy.init(DEFAULT_INIT_CONFIGURATION)
await collectAsyncCalls(startTelemetrySpy, 1)
expect(startTelemetrySpy).toHaveBeenCalledTimes(1)
})

it('does not start telemetry until consent is granted', () => {
it('does not start telemetry until consent is granted', async () => {
const trackingConsentState = createTrackingConsentState()
const { strategy, startTelemetrySpy } = createPreStartStrategyWithDefaults({
trackingConsentState,
Expand All @@ -260,6 +275,7 @@ describe('preStartLogs', () => {
expect(startTelemetrySpy).not.toHaveBeenCalled()

trackingConsentState.update(TrackingConsent.GRANTED)
await collectAsyncCalls(startTelemetrySpy, 1)

expect(startTelemetrySpy).toHaveBeenCalledTimes(1)
})
Expand All @@ -268,15 +284,18 @@ describe('preStartLogs', () => {

function createPreStartStrategyWithDefaults({
trackingConsentState = createTrackingConsentState(),
startSessionManagerMock = createStartSessionManagerMock(),
}: {
trackingConsentState?: TrackingConsentState
startSessionManagerMock?: typeof startSessionManager
} = {}) {
const handleLogSpy = jasmine.createSpy()
const doStartLogsSpy = jasmine.createSpy<DoStartLogs>().and.returnValue({
handleLog: handleLogSpy,
} as unknown as StartLogsResult)
const getCommonContextSpy = jasmine.createSpy<() => CommonContext>()
const startTelemetrySpy = replaceMockableWithSpy(startTelemetry).and.callFake(createFakeTelemetryObject)
replaceMockable(startSessionManager, startSessionManagerMock)

return {
strategy: createPreStartStrategy(getCommonContextSpy, trackingConsentState, doStartLogsSpy),
Expand Down
Loading
Loading