Skip to content

Commit

Permalink
♻️ Create a report collection
Browse files Browse the repository at this point in the history
  • Loading branch information
amortemousque committed Mar 28, 2022
1 parent bb5793b commit ab0a69e
Show file tree
Hide file tree
Showing 4 changed files with 140 additions and 132 deletions.
94 changes: 5 additions & 89 deletions packages/logs/src/boot/startLogs.spec.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,7 @@
import type { Context, RawReport, TimeStamp } from '@datadog/browser-core'
import {
noop,
Observable,
resetExperimentalFeatures,
updateExperimentalFeatures,
stopSessionManager,
} from '@datadog/browser-core'
import type { Context, TimeStamp } from '@datadog/browser-core'
import { noop, stopSessionManager } from '@datadog/browser-core'
import sinon from 'sinon'
import { deleteEventBridgeStub, initEventBridgeStub, stubEndpointBuilder } from '../../../core/test/specHelper'
import { stubReportingObserver } from '../../../core/test/stubReportApis'
import type { LogsConfiguration } from '../domain/configuration'
import { validateAndBuildLogsConfiguration } from '../domain/configuration'

Expand Down Expand Up @@ -50,19 +43,15 @@ describe('logs', () => {
let baseConfiguration: LogsConfiguration
let sessionIsTracked: boolean
let server: sinon.SinonFakeServer
let reportObservable: Observable<RawReport>

const sessionManager: LogsSessionManager = {
findTrackedSession: () => (sessionIsTracked ? { id: SESSION_ID } : undefined),
}
let stopLogs = noop
const startLogs = ({
sender = createSender(noop),
configuration: configurationOverrides,
}: { sender?: Sender; configuration?: Partial<LogsConfiguration> } = {}) => {
const configuration = { ...baseConfiguration, ...configurationOverrides }
const startLogs = doStartLogs(configuration, () => undefined, reportObservable, sessionManager, sender)
stopLogs = startLogs.stop
const startLogs = doStartLogs(configuration, () => undefined, sessionManager)
return startLogs.send
}

Expand All @@ -73,7 +62,6 @@ describe('logs', () => {
maxBatchSize: 1,
}
sessionIsTracked = true
reportObservable = new Observable<RawReport>()
server = sinon.fakeServer.create()
})

Expand All @@ -82,7 +70,6 @@ describe('logs', () => {
delete window.DD_RUM
deleteEventBridgeStub()
stopSessionManager()
stopLogs()
})

describe('request', () => {
Expand Down Expand Up @@ -152,92 +139,21 @@ describe('logs', () => {
})
})

describe('reports', () => {
let sender: Sender
let logErrorSpy: jasmine.Spy
let reportingObserverStub: ReturnType<typeof stubReportingObserver>

beforeEach(() => {
sender = createSender(noop)
logErrorSpy = spyOn(sender, 'sendToHttp')
reportingObserverStub = stubReportingObserver()
})

afterEach(() => {
reportingObserverStub.reset()
})

it('should send reports when ff forward-reports is enabled', () => {
updateExperimentalFeatures(['forward-reports'])
const { stop } = originalStartLogs(
validateAndBuildLogsConfiguration({ ...initConfiguration, forwardReports: ['intervention'] })!,
sender
)

reportingObserverStub.raiseReport('intervention')

expect(logErrorSpy).toHaveBeenCalled()

resetExperimentalFeatures()
stop()
})

it('should not send reports when ff forward-reports is disabled', () => {
const { stop } = originalStartLogs(
validateAndBuildLogsConfiguration({ ...initConfiguration, forwardReports: ['intervention'] })!,
sender
)
reportingObserverStub.raiseReport('intervention')

expect(logErrorSpy).not.toHaveBeenCalled()
stop()
})

it('should not send reports when forwardReports init option not specified', () => {
const { stop } = originalStartLogs(validateAndBuildLogsConfiguration({ ...initConfiguration })!, sender)
reportingObserverStub.raiseReport('intervention')

expect(logErrorSpy).not.toHaveBeenCalled()
stop()
})

it('should add the source file information to the message for non error reports', () => {
updateExperimentalFeatures(['forward-reports'])
const { stop } = originalStartLogs(
validateAndBuildLogsConfiguration({ ...initConfiguration, forwardReports: ['deprecation'] })!,
sender
)

reportingObserverStub.raiseReport('deprecation')

expect(logErrorSpy).toHaveBeenCalledOnceWith(
'deprecation: foo bar Found in http://foo.bar/index.js:20:10',
undefined,
'warn'
)

resetExperimentalFeatures()
stop()
})
})

describe('sampling', () => {
it('should be applied when event bridge is present', () => {
const sendSpy = spyOn(initEventBridgeStub(), 'send')

let configuration = { ...baseConfiguration, sampleRate: 0 }
let { send, stop } = originalStartLogs(configuration, createSender(noop))
let { send } = originalStartLogs(configuration, createSender(noop))
send(DEFAULT_MESSAGE, {})

expect(sendSpy).not.toHaveBeenCalled()
stop()

configuration = { ...baseConfiguration, sampleRate: 100 }
;({ send, stop } = originalStartLogs(configuration, createSender(noop)))
;({ send } = originalStartLogs(configuration, createSender(noop)))
send(DEFAULT_MESSAGE, {})

expect(sendSpy).toHaveBeenCalled()
stop()
})
})

Expand Down
46 changes: 3 additions & 43 deletions packages/logs/src/boot/startLogs.ts
Original file line number Diff line number Diff line change
@@ -1,33 +1,21 @@
import type { Context, RawError, MonitoringMessage, TelemetryEvent, RawReport, Observable } from '@datadog/browser-core'
import type { Context, RawError, MonitoringMessage, TelemetryEvent } from '@datadog/browser-core'
import {
areCookiesAuthorized,
combine,
canUseEventBridge,
getEventBridge,
startInternalMonitoring,
RawReportType,
initReportObservable,
ErrorSource,
getFileFromStackTraceString,
startBatchWithReplica,
} from '@datadog/browser-core'
import type { LogsMessage } from '../domain/logger'
import { StatusType } from '../domain/logger'
import type { LogsSessionManager } from '../domain/logsSessionManager'
import { startLogsSessionManager, startLogsSessionManagerStub } from '../domain/logsSessionManager'
import type { LogsConfiguration } from '../domain/configuration'
import type { LogsEvent } from '../logsEvent.types'
import { buildAssemble, getRUMInternalContext } from '../domain/assemble'
import type { Sender } from '../domain/sender'
import { startRawErrorCollection } from '../domain/logsCollection/rawErrorCollection'
import { startConsoleCollection } from '../domain/logsCollection/consoleCollection'

const LogStatusForReport = {
[RawReportType.cspViolation]: StatusType.error,
[RawReportType.intervention]: StatusType.error,
[RawReportType.deprecation]: StatusType.warn,
}

export function startLogs(configuration: LogsConfiguration, sender: Sender) {
const internalMonitoring = startLogsInternalMonitoring(configuration)
internalMonitoring.setExternalContextProvider(() =>
Expand All @@ -53,14 +41,12 @@ export function startLogs(configuration: LogsConfiguration, sender: Sender) {
const { reportRawError } = startRawErrorCollection(configuration, sender)
startConsoleCollection(configuration, sender)

const reportObservable = initReportObservable(configuration.forwardReports)

const session =
areCookiesAuthorized(configuration.cookieOptions) && !canUseEventBridge()
? startLogsSessionManager(configuration)
: startLogsSessionManagerStub(configuration)

return doStartLogs(configuration, reportRawError, reportObservable, session, sender)
return doStartLogs(configuration, reportRawError, session)
}

function startLogsInternalMonitoring(configuration: LogsConfiguration) {
Expand Down Expand Up @@ -91,9 +77,7 @@ function startLogsInternalMonitoring(configuration: LogsConfiguration) {
export function doStartLogs(
configuration: LogsConfiguration,
reportRawError: (error: RawError) => void,
reportObservable: Observable<RawReport>,
sessionManager: LogsSessionManager,
sender: Sender
sessionManager: LogsSessionManager
) {
const assemble = buildAssemble(sessionManager, configuration, reportRawError)

Expand All @@ -110,31 +94,7 @@ export function doStartLogs(
onLogEventCollected = (message) => batch.add(message)
}

function logReport(report: RawReport) {
let message = report.message
let messageContext: Partial<LogsEvent> | undefined
const logStatus = LogStatusForReport[report.type]
if (logStatus === StatusType.error) {
messageContext = {
error: {
kind: report.subtype,
origin: ErrorSource.REPORT,
stack: report.stack,
},
}
} else if (report.stack) {
message += ` Found in ${getFileFromStackTraceString(report.stack)!}`
}

sender.sendToHttp(message, messageContext, logStatus)
}

const reportSubscription = reportObservable.subscribe(logReport)

return {
stop: () => {
reportSubscription.unsubscribe()
},
send: (message: LogsMessage, currentContext: Context) => {
const contextualizedMessage = assemble(message, currentContext)
if (contextualizedMessage) {
Expand Down
83 changes: 83 additions & 0 deletions packages/logs/src/domain/logsCollection/reportCollection.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
import { ErrorSource, resetExperimentalFeatures, updateExperimentalFeatures } from '@datadog/browser-core'
import { stubReportingObserver } from '../../../../core/test/stubReportApis'
import { validateAndBuildLogsConfiguration } from '../configuration'
import { StatusType } from '../logger'
import { createSender } from '../sender'
import { startReportCollection } from './reportCollection'

describe('reports', () => {
const initConfiguration = { clientToken: 'xxx', service: 'service' }
let sendLogSpy: jasmine.Spy
let reportingObserverStub: ReturnType<typeof stubReportingObserver>

beforeEach(() => {
sendLogSpy = jasmine.createSpy('sendLogSpy')
reportingObserverStub = stubReportingObserver()
})

afterEach(() => {
reportingObserverStub.reset()
})

it('should send reports when ff forward-reports is enabled', () => {
updateExperimentalFeatures(['forward-reports'])
const { stop } = startReportCollection(
validateAndBuildLogsConfiguration({ ...initConfiguration, forwardReports: ['intervention'] })!,
createSender(sendLogSpy)
)

reportingObserverStub.raiseReport('intervention')
expect(sendLogSpy).toHaveBeenCalledOnceWith({
error: {
kind: 'NavigatorVibrate',
origin: ErrorSource.REPORT,
stack: jasmine.any(String),
},
message: 'intervention: foo bar',
status: StatusType.error,
})

resetExperimentalFeatures()
stop()
})

it('should not send reports when ff forward-reports is disabled', () => {
const { stop } = startReportCollection(
validateAndBuildLogsConfiguration({ ...initConfiguration, forwardReports: ['intervention'] })!,
createSender(sendLogSpy)
)
reportingObserverStub.raiseReport('intervention')

expect(sendLogSpy).not.toHaveBeenCalled()
stop()
})

it('should not send reports when forwardReports init option not specified', () => {
const { stop } = startReportCollection(
validateAndBuildLogsConfiguration({ ...initConfiguration })!,
createSender(sendLogSpy)
)
reportingObserverStub.raiseReport('intervention')

expect(sendLogSpy).not.toHaveBeenCalled()
stop()
})

it('should add the source file information to the message for non error reports', () => {
updateExperimentalFeatures(['forward-reports'])
const { stop } = startReportCollection(
validateAndBuildLogsConfiguration({ ...initConfiguration, forwardReports: ['deprecation'] })!,
createSender(sendLogSpy)
)

reportingObserverStub.raiseReport('deprecation')

expect(sendLogSpy).toHaveBeenCalledOnceWith({
message: 'deprecation: foo bar Found in http://foo.bar/index.js:20:10',
status: StatusType.warn,
})

resetExperimentalFeatures()
stop()
})
})
49 changes: 49 additions & 0 deletions packages/logs/src/domain/logsCollection/reportCollection.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import type { Context, ClocksState, RawReport } from '@datadog/browser-core'
import { ErrorSource, RawReportType, getFileFromStackTraceString, initReportObservable } from '@datadog/browser-core'
import type { LogsEvent } from '../../logsEvent.types'
import type { LogsConfiguration } from '../configuration'
import { StatusType } from '../logger'
import type { Sender } from '../sender'

export interface ProvidedError {
startClocks: ClocksState
error: unknown
context?: Context
handlingStack: string
}

const LogStatusForReport = {
[RawReportType.cspViolation]: StatusType.error,
[RawReportType.intervention]: StatusType.error,
[RawReportType.deprecation]: StatusType.warn,
}

export function startReportCollection(configuration: LogsConfiguration, sender: Sender) {
const reportObservable = initReportObservable(configuration.forwardReports)
const reportSubscription = reportObservable.subscribe(logReport)

function logReport(report: RawReport) {
let message = report.message
let messageContext: Partial<LogsEvent> | undefined
const logStatus = LogStatusForReport[report.type]
if (logStatus === StatusType.error) {
messageContext = {
error: {
kind: report.subtype,
origin: ErrorSource.REPORT,
stack: report.stack,
},
}
} else if (report.stack) {
message += ` Found in ${getFileFromStackTraceString(report.stack)!}`
}

sender.sendToHttp(message, messageContext, logStatus)
}

return {
stop: () => {
reportSubscription.unsubscribe()
},
}
}

0 comments on commit ab0a69e

Please sign in to comment.