Skip to content

Commit

Permalink
Merge branch 'develop' into jward/RUM-2206-rum-crash-event-mappers
Browse files Browse the repository at this point in the history
# Conflicts:
#	DatadogCore/Tests/Datadog/Mocks/RUMFeatureMocks.swift
#	DatadogRUM/Sources/Feature/RUMFeature.swift
#	DatadogRUM/Sources/Integrations/CrashReportReceiver.swift
#	DatadogRUM/Tests/Mocks/RUMFeatureMocks.swift
  • Loading branch information
ncreated committed Mar 28, 2024
2 parents 5b60c64 + a3a1d7f commit 2d5d2d2
Show file tree
Hide file tree
Showing 26 changed files with 497 additions and 594 deletions.
12 changes: 7 additions & 5 deletions DatadogCore/Tests/Datadog/Mocks/RUMFeatureMocks.swift
Expand Up @@ -38,11 +38,13 @@ extension WebViewEventReceiver: AnyMockable {
}

static func mockWith(
featureScope: FeatureScope = NOPFeatureScope(),
dateProvider: DateProvider = SystemDateProvider(),
commandSubscriber: RUMCommandSubscriber = RUMCommandSubscriberMock(),
viewCache: ViewCache = ViewCache()
) -> Self {
.init(
featureScope: featureScope,
dateProvider: dateProvider,
commandSubscriber: commandSubscriber,
viewCache: viewCache
Expand All @@ -56,25 +58,25 @@ extension CrashReportReceiver: AnyMockable {
}

static func mockWith(
featureScope: FeatureScope = NOPFeatureScope(),
applicationID: String = .mockAny(),
dateProvider: DateProvider = SystemDateProvider(),
sessionSampler: Sampler = .mockKeepAll(),
trackBackgroundEvents: Bool = true,
uuidGenerator: RUMUUIDGenerator = DefaultRUMUUIDGenerator(),
ciTest: RUMCITest? = nil,
syntheticsTest: RUMSyntheticsTest? = nil,
telemetry: Telemetry = NOPTelemetry(),
eventsMapper: RUMEventsMapper = .mockNoOp()
) -> Self {
.init(
featureScope: featureScope,
applicationID: applicationID,
dateProvider: dateProvider,
sessionSampler: sessionSampler,
trackBackgroundEvents: trackBackgroundEvents,
uuidGenerator: uuidGenerator,
ciTest: ciTest,
syntheticsTest: syntheticsTest,
telemetry: telemetry,
eventsMapper: eventsMapper
)
}
Expand Down Expand Up @@ -699,7 +701,7 @@ extension RUMScopeDependencies {
}

static func mockWith(
core: DatadogCoreProtocol = NOPDatadogCore(),
featureScope: FeatureScope = NOPFeatureScope(),
rumApplicationID: String = .mockAny(),
sessionSampler: Sampler = .mockKeepAll(),
trackBackgroundEvents: Bool = .mockAny(),
Expand All @@ -714,7 +716,7 @@ extension RUMScopeDependencies {
viewCache: ViewCache = ViewCache()
) -> RUMScopeDependencies {
return RUMScopeDependencies(
core: core,
featureScope: featureScope,
rumApplicationID: rumApplicationID,
sessionSampler: sessionSampler,
trackBackgroundEvents: trackBackgroundEvents,
Expand Down Expand Up @@ -746,7 +748,7 @@ extension RUMScopeDependencies {
viewCache: ViewCache? = nil
) -> RUMScopeDependencies {
return RUMScopeDependencies(
core: self.core,
featureScope: self.featureScope,
rumApplicationID: rumApplicationID ?? self.rumApplicationID,
sessionSampler: sessionSampler ?? self.sessionSampler,
trackBackgroundEvents: trackBackgroundEvents ?? self.trackBackgroundEvents,
Expand Down
252 changes: 105 additions & 147 deletions DatadogCore/Tests/Datadog/RUM/Integrations/CrashReportReceiverTests.swift

Large diffs are not rendered by default.

Expand Up @@ -41,7 +41,7 @@ class RUMMonitorConfigurationTests: XCTestCase {
let monitor = RUMMonitor.shared(in: core).dd

let dependencies = monitor.scopes.dependencies
monitor.core?.scope(for: RUMFeature.self).eventWriteContext { context, _ in
monitor.featureScope.eventWriteContext { context, _ in
DDAssertReflectionEqual(context.userInfo, self.userIno)
XCTAssertEqual(context.networkConnectionInfo, self.networkConnectionInfo)
XCTAssertEqual(context.carrierInfo, self.carrierInfo)
Expand Down
4 changes: 4 additions & 0 deletions DatadogInternal/Sources/DatadogCoreProtocol.swift
Expand Up @@ -12,6 +12,10 @@ import Foundation
/// Any reference to `DatadogCoreProtocol` must be captured as `weak` within a Feature. This is to avoid
/// retain cycle of core holding the Feature and vice-versa.
public protocol DatadogCoreProtocol: AnyObject, MessageSending, BaggageSharing {
// TODO: RUM-3717
// Remove `DatadogCoreProtocol` conformance to `MessageSending` and `BaggageSharing` once
// all features are migrated to depend on `FeatureScope` interface.

/// Registers a Feature instance.
///
/// Feature can interact with the core and other Feature through the message bus. Some specific Features
Expand Down
Expand Up @@ -27,6 +27,9 @@ public protocol FeatureMessageReceiver {
/// - Returns: `true` if the message was processed by the receiver;`false` if it was ignored.
@discardableResult
func receive(message: FeatureMessage, from core: DatadogCoreProtocol) -> Bool
// ^ TODO: RUM-3717
// Remove `core:` parameter from this API once all features are migrated to depend on `FeatureScope` interface
// instead of depending on directly on `core`.
}

public struct NOPFeatureMessageReceiver: FeatureMessageReceiver {
Expand Down
13 changes: 9 additions & 4 deletions DatadogRUM/Sources/Feature/RUMFeature.swift
Expand Up @@ -34,8 +34,9 @@ internal final class RUMFeature: DatadogRemoteFeature {
telemetry: core.telemetry
)

let featureScope = core.scope(for: RUMFeature.self)
let dependencies = RUMScopeDependencies(
core: core,
featureScope: featureScope,
rumApplicationID: configuration.applicationID,
sessionSampler: Sampler(samplingRate: configuration.debugSDK ? 100 : configuration.sessionSampleRate),
trackBackgroundEvents: configuration.trackBackgroundEvents,
Expand Down Expand Up @@ -73,7 +74,6 @@ internal final class RUMFeature: DatadogRemoteFeature {
)

self.monitor = Monitor(
core: core,
dependencies: dependencies,
dateProvider: configuration.dateProvider
)
Expand All @@ -95,18 +95,24 @@ internal final class RUMFeature: DatadogRemoteFeature {
)
self.messageReceiver = CombinedFeatureMessageReceiver(
TelemetryReceiver(
featureScope: featureScope,
dateProvider: configuration.dateProvider,
sampler: Sampler(samplingRate: configuration.telemetrySampleRate),
configurationExtraSampler: Sampler(samplingRate: configuration.configurationTelemetrySampleRate),
metricsExtraSampler: Sampler(samplingRate: configuration.metricsTelemetrySampleRate)
),
ErrorMessageReceiver(monitor: monitor),
ErrorMessageReceiver(
featureScope: featureScope,
monitor: monitor
),
WebViewEventReceiver(
featureScope: featureScope,
dateProvider: configuration.dateProvider,
commandSubscriber: monitor,
viewCache: dependencies.viewCache
),
CrashReportReceiver(
featureScope: featureScope,
applicationID: configuration.applicationID,
dateProvider: configuration.dateProvider,
sessionSampler: Sampler(samplingRate: configuration.debugSDK ? 100 : configuration.sessionSampleRate),
Expand All @@ -120,7 +126,6 @@ internal final class RUMFeature: DatadogRemoteFeature {
return nil
}
}(),
telemetry: core.telemetry,
eventsMapper: eventsMapper
)
)
Expand Down
44 changes: 20 additions & 24 deletions DatadogRUM/Sources/Integrations/CrashReportReceiver.swift
Expand Up @@ -100,6 +100,8 @@ internal struct CrashReportReceiver: FeatureMessageReceiver {
let realDateNow: Date
}

/// RUM feature scope.
let featureScope: FeatureScope
let applicationID: String
let dateProvider: DateProvider
let sessionSampler: Sampler
Expand All @@ -109,31 +111,29 @@ internal struct CrashReportReceiver: FeatureMessageReceiver {
let ciTest: RUMCITest?
/// Integration with Synthetics tests. It contains the Synthetics test context when active.
let syntheticsTest: RUMSyntheticsTest?
/// Telemetry interface.
let telemetry: Telemetry
let eventsMapper: RUMEventsMapper

// MARK: - Initialization

init(
featureScope: FeatureScope,
applicationID: String,
dateProvider: DateProvider,
sessionSampler: Sampler,
trackBackgroundEvents: Bool,
uuidGenerator: RUMUUIDGenerator,
ciTest: RUMCITest?,
syntheticsTest: RUMSyntheticsTest?,
telemetry: Telemetry,
eventsMapper: RUMEventsMapper
) {
self.featureScope = featureScope
self.applicationID = applicationID
self.dateProvider = dateProvider
self.sessionSampler = sessionSampler
self.trackBackgroundEvents = trackBackgroundEvents
self.uuidGenerator = uuidGenerator
self.ciTest = ciTest
self.syntheticsTest = syntheticsTest
self.telemetry = telemetry
self.eventsMapper = eventsMapper
}

Expand All @@ -143,16 +143,16 @@ internal struct CrashReportReceiver: FeatureMessageReceiver {
return false
}

return send(report: crash.report, with: crash.context, to: core)
return send(report: crash.report, with: crash.context)
} catch {
core.telemetry
featureScope.telemetry
.error("Fails to decode crash from RUM", error: error)
}

return false
}

private func send(report: CrashReport, with context: CrashContext, to core: DatadogCoreProtocol) -> Bool {
private func send(report: CrashReport, with context: CrashContext) -> Bool {
// The `crashReport.crashDate` uses system `Date` collected at the moment of crash, so we need to adjust it
// to the server time before processing. Following use of the current correction is not ideal (it's not the correction
// from the moment of crash), but this is the best approximation we can get.
Expand All @@ -171,17 +171,16 @@ internal struct CrashReportReceiver: FeatureMessageReceiver {
sendCrashReportLinkedToLastViewInPreviousSession(
report,
lastRUMViewEventInPreviousSession: lastRUMViewEvent,
using: adjustedCrashTimings,
to: core
using: adjustedCrashTimings
)
} else {
DD.logger.debug("There was a crash in previous session, but it is ignored due to another crash already present in the last view.")
return false
}
} else if let lastRUMSessionState = context.lastRUMSessionState {
sendCrashReportToPreviousSession(report, crashContext: context, lastRUMSessionStateInPreviousSession: lastRUMSessionState, using: adjustedCrashTimings, to: core)
sendCrashReportToPreviousSession(report, crashContext: context, lastRUMSessionStateInPreviousSession: lastRUMSessionState, using: adjustedCrashTimings)
} else if sessionSampler.sample() { // before producing a new RUM session, we must consider sampling
sendCrashReportToNewSession(report, crashContext: context, using: adjustedCrashTimings, to: core)
sendCrashReportToNewSession(report, crashContext: context, using: adjustedCrashTimings)
} else {
DD.logger.debug("There was a crash in previous session, but it is ignored due to sampling.")
return false
Expand All @@ -195,16 +194,15 @@ internal struct CrashReportReceiver: FeatureMessageReceiver {
private func sendCrashReportLinkedToLastViewInPreviousSession(
_ crashReport: CrashReport,
lastRUMViewEventInPreviousSession lastRUMViewEvent: RUMViewEvent,
using crashTimings: AdjustedCrashTimings,
to core: DatadogCoreProtocol
using crashTimings: AdjustedCrashTimings
) {
if crashTimings.realDateNow.timeIntervalSince(crashTimings.realCrashDate) < Constants.viewEventAvailabilityThreshold {
send(crashReport: crashReport, to: lastRUMViewEvent, using: crashTimings.realCrashDate, to: core)
send(crashReport: crashReport, to: lastRUMViewEvent, using: crashTimings.realCrashDate)
} else {
// We know it is too late for sending RUM view to previous RUM session as it is now stale on backend.
// To avoid inconsistency, we only send the RUM error.
DD.logger.debug("Sending crash as RUM error.")
core.scope(for: RUMFeature.self).eventWriteContext(bypassConsent: true) { context, writer in
featureScope.eventWriteContext(bypassConsent: true) { context, writer in
let rumError = createRUMError(from: crashReport, and: lastRUMViewEvent, crashDate: crashTimings.realCrashDate, sourceType: context.nativeSourceOverride)
if let mappedError = self.eventsMapper.map(event: rumError) {
writer.write(value: mappedError)
Expand All @@ -220,8 +218,7 @@ internal struct CrashReportReceiver: FeatureMessageReceiver {
_ crashReport: CrashReport,
crashContext: CrashContext,
lastRUMSessionStateInPreviousSession lastRUMSessionState: RUMSessionState,
using crashTimings: AdjustedCrashTimings,
to core: DatadogCoreProtocol
using crashTimings: AdjustedCrashTimings
) {
let handlingRule = RUMOffViewEventsHandlingRule(
sessionState: lastRUMSessionState,
Expand Down Expand Up @@ -261,7 +258,7 @@ internal struct CrashReportReceiver: FeatureMessageReceiver {
}

if let newRUMView = newRUMView {
send(crashReport: crashReport, to: newRUMView, using: crashTimings.realCrashDate, to: core)
send(crashReport: crashReport, to: newRUMView, using: crashTimings.realCrashDate)
}
}

Expand All @@ -270,8 +267,7 @@ internal struct CrashReportReceiver: FeatureMessageReceiver {
private func sendCrashReportToNewSession(
_ crashReport: CrashReport,
crashContext: CrashContext,
using crashTimings: AdjustedCrashTimings,
to core: DatadogCoreProtocol
using crashTimings: AdjustedCrashTimings
) {
// We can ignore `sessionState` for building the rule as we can assume there was no session sent - otherwise,
// the `lastRUMSessionState` would have been set in `CrashContext` and we could be sending the crash to previous session
Expand Down Expand Up @@ -315,18 +311,18 @@ internal struct CrashReportReceiver: FeatureMessageReceiver {
}

if let newRUMView = newRUMView {
send(crashReport: crashReport, to: newRUMView, using: crashTimings.realCrashDate, to: core)
send(crashReport: crashReport, to: newRUMView, using: crashTimings.realCrashDate)
}
}

/// Sends given `CrashReport` by linking it to given `rumView` and updating view counts accordingly.
private func send(crashReport: CrashReport, to rumView: RUMViewEvent, using realCrashDate: Date, to core: DatadogCoreProtocol) {
private func send(crashReport: CrashReport, to rumView: RUMViewEvent, using realCrashDate: Date) {
DD.logger.debug("Updating RUM view with crash report.")
let updatedRUMView = updateRUMViewWithNewError(rumView, crashDate: realCrashDate)

// crash reporting is considering the user consent from previous session, if an event reached
// the message bus it means that consent was granted and we can safely bypass current consent.
core.scope(for: RUMFeature.self).eventWriteContext(bypassConsent: true) { context, writer in
featureScope.eventWriteContext(bypassConsent: true) { context, writer in
let rumError = createRUMError(from: crashReport, and: updatedRUMView, crashDate: realCrashDate, sourceType: context.nativeSourceOverride)

if let mappedError = self.eventsMapper.map(event: rumError) {
Expand Down Expand Up @@ -549,7 +545,7 @@ internal struct CrashReportReceiver: FeatureMessageReceiver {
container: nil,
context: nil,
date: startDate.timeIntervalSince1970.toInt64Milliseconds,
device: .init(device: context.device, telemetry: telemetry),
device: .init(device: context.device, telemetry: featureScope.telemetry),
display: nil,
// RUMM-2197: In very rare cases, the OS info computed below might not be exactly the one
// that the app crashed on. This would correspond to a scenario when the device OS was upgraded
Expand Down
4 changes: 3 additions & 1 deletion DatadogRUM/Sources/Integrations/ErrorMessageReceiver.swift
Expand Up @@ -25,6 +25,8 @@ internal struct ErrorMessageReceiver: FeatureMessageReceiver {
let attributes: [String: AnyCodable]?
}

/// RUM feature scope.
let featureScope: FeatureScope
let monitor: Monitor

/// Adds RUM Error with given message and stack to current RUM View.
Expand All @@ -46,7 +48,7 @@ internal struct ErrorMessageReceiver: FeatureMessageReceiver {

return true
} catch {
core.telemetry
featureScope.telemetry
.error("Fails to decode error message", error: error)
return false
}
Expand Down

0 comments on commit 2d5d2d2

Please sign in to comment.