Skip to content
Open
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
4 changes: 4 additions & 0 deletions Datadog/Datadog.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -278,6 +278,7 @@
3C3EF2B02C1AEBAB009E9E57 /* LaunchReport.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3C3EF2AF2C1AEBAB009E9E57 /* LaunchReport.swift */; };
3C41693C29FBF4D50042B9D2 /* DatadogWebViewTracking.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3CE119FE29F7BE0100202522 /* DatadogWebViewTracking.framework */; };
3C43A3882C188974000BFB21 /* WatchdogTerminationMonitorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3C43A3862C188970000BFB21 /* WatchdogTerminationMonitorTests.swift */; };
B21F9B173E636896FBBF638B /* WatchdogTerminationReporterTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11A0F1FDC6F852757B99EF56 /* WatchdogTerminationReporterTests.swift */; };
3C4CF9922C47BE07006DE1C0 /* MemoryWarningMonitor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3C5CD8C12C3EBA1700B12303 /* MemoryWarningMonitor.swift */; };
3C4CF9982C47CC91006DE1C0 /* MemoryWarningMonitorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3C4CF9972C47CC8C006DE1C0 /* MemoryWarningMonitorTests.swift */; };
3C4CF99B2C47DAA5006DE1C0 /* MemoryWarningMocks.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3C4CF99A2C47DAA5006DE1C0 /* MemoryWarningMocks.swift */; };
Expand Down Expand Up @@ -2113,6 +2114,7 @@
3C3C9E2E2C64F470003AF22F /* Data+CryptoTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Data+CryptoTests.swift"; sourceTree = "<group>"; };
3C3EF2AF2C1AEBAB009E9E57 /* LaunchReport.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LaunchReport.swift; sourceTree = "<group>"; };
3C43A3862C188970000BFB21 /* WatchdogTerminationMonitorTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WatchdogTerminationMonitorTests.swift; sourceTree = "<group>"; };
11A0F1FDC6F852757B99EF56 /* WatchdogTerminationReporterTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WatchdogTerminationReporterTests.swift; sourceTree = "<group>"; };
3C4CF9972C47CC8C006DE1C0 /* MemoryWarningMonitorTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MemoryWarningMonitorTests.swift; sourceTree = "<group>"; };
3C4CF99A2C47DAA5006DE1C0 /* MemoryWarningMocks.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MemoryWarningMocks.swift; sourceTree = "<group>"; };
3C5CD8C12C3EBA1700B12303 /* MemoryWarningMonitor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MemoryWarningMonitor.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -3957,6 +3959,7 @@
3CFF4FA32C0E0FE5006F191D /* WatchdogTerminationCheckerTests.swift */,
3CEC57752C16FDD30042B5F2 /* AppStateManagerTests.swift */,
3C43A3862C188970000BFB21 /* WatchdogTerminationMonitorTests.swift */,
11A0F1FDC6F852757B99EF56 /* WatchdogTerminationReporterTests.swift */,
);
path = WatchdogTerminations;
sourceTree = "<group>";
Expand Down Expand Up @@ -9463,6 +9466,7 @@
3C4CF99B2C47DAA5006DE1C0 /* MemoryWarningMocks.swift in Sources */,
5B1D02942E8ED6C000AB2391 /* FlagEvaluationReceiverTests.swift in Sources */,
3C43A3882C188974000BFB21 /* WatchdogTerminationMonitorTests.swift in Sources */,
B21F9B173E636896FBBF638B /* WatchdogTerminationReporterTests.swift in Sources */,
D29A9F9D29DDB483005C54A4 /* ValuePublisherTests.swift in Sources */,
6174D61A2BFE449300EC7469 /* SessionEndedMetricTests.swift in Sources */,
9654971D2D774060006428EE /* SwiftUIViewNameExtractorTests.swift in Sources */,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,219 @@ class CrashReportReceiverTests: XCTestCase {
XCTAssertEqual(featureScope.eventsWritten(ofType: RUMErrorEvent.self).count, 1)
}

func testGivenCrashDuringRUMSessionWithActiveViewCollectedLessThan4HoursAgoAndTooManyAttributes_whenSending_itSanitizesRUMErrorContext() throws {
let secondsIn4Hours: TimeInterval = 4 * 60 * 60
let numberOfAttributes = AttributesSanitizer.Constraints.maxNumberOfAttributes * 2

// Given
let currentDate: Date = .mockDecember15th2019At10AMUTC()
let crashDate: Date = currentDate.secondsAgo(.random(in: 0..<secondsIn4Hours))
let activeRUMView: RUMViewEvent = .mockRandomWith(crashCount: 0)
let lastRUMAttributes = RUMEventAttributes(
contextInfo: Dictionary(uniqueKeysWithValues: (0..<numberOfAttributes).map { ("attribute-\($0)", String.mockAny() as Encodable) })
)

let crashReport: DDCrashReport = .mockWith(date: crashDate)
let crashContext: CrashContext = .mockWith(
trackingConsent: .granted,
lastRUMViewEvent: activeRUMView,
lastRUMAttributes: lastRUMAttributes
)

let receiver: CrashReportReceiver = .mockWith(
featureScope: featureScope,
dateProvider: RelativeDateProvider(using: currentDate),
sessionSampler: .mockKeepAll(),
trackBackgroundEvents: .mockRandom()
)

// When
XCTAssertTrue(
receiver.receive(message: .payload(
Crash(report: crashReport, context: crashContext)
), from: NOPDatadogCore())
)

// Then
let sentRUMError = try XCTUnwrap(featureScope.eventsWritten(ofType: RUMErrorEvent.self).first)
let usrInfoCount = sentRUMError.usr?.usrInfo.count ?? 0
let accountInfoCount = sentRUMError.account?.accountInfo.count ?? 0
let contextInfoCount = sentRUMError.context?.contextInfo.count ?? 0
XCTAssertEqual(usrInfoCount + accountInfoCount + contextInfoCount, AttributesSanitizer.Constraints.maxNumberOfAttributes)
XCTAssertEqual(contextInfoCount, AttributesSanitizer.Constraints.maxNumberOfAttributes - usrInfoCount - accountInfoCount, "`contextInfo` is removed first, then `account`, when the total exceeds the limit")
}

func testGivenCrashDuringRUMSessionWithActiveViewCollectedMoreThan4HoursAgoAndTooManyAttributes_whenSending_itSanitizesRUMErrorContext() throws {
let secondsIn4Hours: TimeInterval = 4 * 60 * 60
let numberOfAttributes = AttributesSanitizer.Constraints.maxNumberOfAttributes * 2

// Given
let currentDate: Date = .mockDecember15th2019At10AMUTC()
let crashDate: Date = currentDate.secondsAgo(.random(in: secondsIn4Hours..<TimeInterval.greatestFiniteMagnitude))
let activeRUMView: RUMViewEvent = .mockRandomWith(crashCount: 0)
let lastRUMAttributes = RUMEventAttributes(
contextInfo: Dictionary(uniqueKeysWithValues: (0..<numberOfAttributes).map { ("attribute-\($0)", String.mockAny() as Encodable) })
)

let crashReport: DDCrashReport = .mockWith(date: crashDate)
let crashContext: CrashContext = .mockWith(
trackingConsent: .granted,
lastRUMViewEvent: activeRUMView,
lastRUMAttributes: lastRUMAttributes
)

let receiver: CrashReportReceiver = .mockWith(
featureScope: featureScope,
dateProvider: RelativeDateProvider(using: currentDate),
sessionSampler: .mockKeepAll(),
trackBackgroundEvents: .mockRandom()
)

// When
XCTAssertTrue(
receiver.receive(message: .payload(
Crash(report: crashReport, context: crashContext)
), from: NOPDatadogCore())
)

// Then
let sentRUMError = try XCTUnwrap(featureScope.eventsWritten(ofType: RUMErrorEvent.self).first)
let usrInfoCount = sentRUMError.usr?.usrInfo.count ?? 0
let accountInfoCount = sentRUMError.account?.accountInfo.count ?? 0
let contextInfoCount = sentRUMError.context?.contextInfo.count ?? 0
XCTAssertEqual(usrInfoCount + accountInfoCount + contextInfoCount, AttributesSanitizer.Constraints.maxNumberOfAttributes)
XCTAssertEqual(contextInfoCount, AttributesSanitizer.Constraints.maxNumberOfAttributes - usrInfoCount - accountInfoCount, "`contextInfo` is removed first, then `account`, when the total exceeds the limit")
}

func testGivenCrashDuringRUMSessionWithActiveViewCollectedLessThan4HoursAgoAndTooManyAttributes_whenSending_itSanitizesRUMViewContext() throws {
let secondsIn4Hours: TimeInterval = 4 * 60 * 60
let numberOfAttributes = AttributesSanitizer.Constraints.maxNumberOfAttributes * 2

// Given
let currentDate: Date = .mockDecember15th2019At10AMUTC()
let crashDate: Date = currentDate.secondsAgo(.random(in: 0..<secondsIn4Hours))
let activeRUMView: RUMViewEvent = .mockRandomWith(crashCount: 0)
let lastRUMAttributes = RUMEventAttributes(
contextInfo: Dictionary(uniqueKeysWithValues: (0..<numberOfAttributes).map { ("attribute-\($0)", String.mockAny() as Encodable) })
)

let crashReport: DDCrashReport = .mockWith(date: crashDate)
let crashContext: CrashContext = .mockWith(
trackingConsent: .granted,
lastRUMViewEvent: activeRUMView,
lastRUMAttributes: lastRUMAttributes
)

let receiver: CrashReportReceiver = .mockWith(
featureScope: featureScope,
dateProvider: RelativeDateProvider(using: currentDate),
sessionSampler: .mockKeepAll(),
trackBackgroundEvents: .mockRandom()
)

// When
XCTAssertTrue(
receiver.receive(message: .payload(
Crash(report: crashReport, context: crashContext)
), from: NOPDatadogCore())
)

// Then
let sentRUMView = try XCTUnwrap(featureScope.eventsWritten(ofType: RUMViewEvent.self).first)
let usrInfoCount = sentRUMView.usr?.usrInfo.count ?? 0
let accountInfoCount = sentRUMView.account?.accountInfo.count ?? 0
let contextInfoCount = sentRUMView.context?.contextInfo.count ?? 0
XCTAssertEqual(usrInfoCount + accountInfoCount + contextInfoCount, AttributesSanitizer.Constraints.maxNumberOfAttributes)
XCTAssertEqual(contextInfoCount, AttributesSanitizer.Constraints.maxNumberOfAttributes - usrInfoCount - accountInfoCount, "`contextInfo` is removed first, then `account`, when the total exceeds the limit")
}

func testGivenCrashDuringRUMSessionWithActiveViewAndViewEventMapperReintroducingTooManyAttributes_whenSending_itSanitizesTheMappedRUMViewContext() throws {
let secondsIn4Hours: TimeInterval = 4 * 60 * 60
let numberOfAttributes = AttributesSanitizer.Constraints.maxNumberOfAttributes * 2

// Given
let currentDate: Date = .mockDecember15th2019At10AMUTC()
let crashDate: Date = currentDate.secondsAgo(.random(in: 0..<secondsIn4Hours))
let activeRUMView: RUMViewEvent = .mockRandomWith(crashCount: 0)

let receiver: CrashReportReceiver = .mockWith(
featureScope: featureScope,
dateProvider: RelativeDateProvider(using: currentDate),
sessionSampler: .mockKeepAll(),
trackBackgroundEvents: .mockRandom(),
eventsMapper: .mockWith(
viewEventMapper: { viewEvent in
var mappedView = viewEvent
mappedView.context = RUMEventAttributes(
contextInfo: Dictionary(uniqueKeysWithValues: (0..<numberOfAttributes).map { ("attribute-\($0)", String.mockAny() as Encodable) })
)
return mappedView
}
)
)

// When
XCTAssertTrue(
receiver.receive(message: .payload(
Crash(report: .mockWith(date: crashDate), context: .mockWith(
trackingConsent: .granted,
lastRUMViewEvent: activeRUMView
))
), from: NOPDatadogCore())
)

// Then
let sentRUMView = try XCTUnwrap(featureScope.eventsWritten(ofType: RUMViewEvent.self).first)
let usrInfoCount = sentRUMView.usr?.usrInfo.count ?? 0
let accountInfoCount = sentRUMView.account?.accountInfo.count ?? 0
let contextInfoCount = sentRUMView.context?.contextInfo.count ?? 0
XCTAssertEqual(usrInfoCount + accountInfoCount + contextInfoCount, AttributesSanitizer.Constraints.maxNumberOfAttributes, "the view returned by `viewEventMapper` must be sanitized before writing")
}

func testGivenCrashDuringRUMSessionWithActiveView_whenSending_itMapsTheRawRUMViewContextBeforeSanitizing() throws {
let secondsIn4Hours: TimeInterval = 4 * 60 * 60
let numberOfAttributes = AttributesSanitizer.Constraints.maxNumberOfAttributes * 2

// Given
let currentDate: Date = .mockDecember15th2019At10AMUTC()
let crashDate: Date = currentDate.secondsAgo(.random(in: 0..<secondsIn4Hours))
var activeRUMView: RUMViewEvent = .mockRandomWith(crashCount: 0)
activeRUMView.context = RUMEventAttributes(
contextInfo: Dictionary(uniqueKeysWithValues: (0..<numberOfAttributes).map { ("attribute-\($0)", String.mockAny() as Encodable) })
)

var contextInfoCountSeenByMapper: Int?
let receiver: CrashReportReceiver = .mockWith(
featureScope: featureScope,
dateProvider: RelativeDateProvider(using: currentDate),
sessionSampler: .mockKeepAll(),
trackBackgroundEvents: .mockRandom(),
eventsMapper: .mockWith(
viewEventMapper: { viewEvent in
contextInfoCountSeenByMapper = viewEvent.context?.contextInfo.count
return viewEvent
}
)
)

// When
XCTAssertTrue(
receiver.receive(message: .payload(
Crash(report: .mockWith(date: crashDate), context: .mockWith(
trackingConsent: .granted,
lastRUMViewEvent: activeRUMView
))
), from: NOPDatadogCore())
)

// Then
XCTAssertEqual(
contextInfoCountSeenByMapper,
numberOfAttributes,
"`viewEventMapper` must receive the raw view, before sanitization removes attributes"
)
}

func testGivenCrashDuringBackgroundRUMSessionWithNoActiveView_whenSending_itSendsBothRUMErrorAndRUMViewEvent() throws {
// Given
let currentDate: Date = .mockDecember15th2019At10AMUTC()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ internal final class FatalAppHangsHandler {
/// Device date provider.
private let dateProvider: DateProvider
private let uuidGenerator: RUMUUIDGenerator
private let sanitizer = RUMEventSanitizer()

init(
featureScope: FeatureScope,
Expand Down Expand Up @@ -117,8 +118,8 @@ internal final class FatalAppHangsHandler {
additionalAttributes: nil,
timeSinceAppStart: timeSinceAppStart
)
let error = builder.createRUMError(with: fatalHang.lastRUMView)
let view = builder.updateRUMViewWithError(fatalHang.lastRUMView)
let error = self.sanitizer.sanitize(event: builder.createRUMError(with: fatalHang.lastRUMView))
let view = self.sanitizer.sanitize(event: builder.updateRUMViewWithError(fatalHang.lastRUMView))

if realDateNow.timeIntervalSince(realErrorDate) < FatalErrorBuilder.Constants.viewEventAvailabilityThreshold {
DD.logger.debug("Sending fatal App hang as RUM error with issuing RUM view update")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ internal final class WatchdogTerminationReporter: WatchdogTerminationReporting {

private let dateProvider: DateProvider
private let uuidGenerator: RUMUUIDGenerator
private let sanitizer = RUMEventSanitizer()

init(
featureScope: FeatureScope,
Expand Down Expand Up @@ -67,8 +68,8 @@ internal final class WatchdogTerminationReporter: WatchdogTerminationReporting {
errorMeta: nil,
additionalAttributes: nil
)
let error = builder.createRUMError(with: viewEvent)
let view = builder.updateRUMViewWithError(viewEvent)
let error = self.sanitizer.sanitize(event: builder.createRUMError(with: viewEvent))
Comment thread
barboraplasovska marked this conversation as resolved.
let view = self.sanitizer.sanitize(event: builder.updateRUMViewWithError(viewEvent))
Comment thread
barboraplasovska marked this conversation as resolved.

if realDateNow.timeIntervalSince(errorDate) < FatalErrorBuilder.Constants.viewEventAvailabilityThreshold {
DD.logger.debug("Sending Watchdog Termination as RUM error with issuing RUM view update")
Expand Down
11 changes: 6 additions & 5 deletions DatadogRUM/Sources/Integrations/CrashReportReceiver.swift
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ internal struct CrashReportReceiver: FeatureMessageReceiver {
/// Integration with Synthetics tests. It contains the Synthetics test context when active.
let syntheticsTest: RUMSyntheticsTest?
let eventsMapper: RUMEventsMapper
private let sanitizer = RUMEventSanitizer()

// MARK: - Initialization

Expand Down Expand Up @@ -132,10 +133,10 @@ internal struct CrashReportReceiver: FeatureMessageReceiver {
let rumError = builder.createRUMError(with: lastRUMViewEvent)

if let mappedError = self.eventsMapper.map(event: rumError) {
writer.write(value: mappedError)
writer.write(value: self.sanitizer.sanitize(event: mappedError))
} else {
DD.logger.warn("errorEventMapper returned 'nil' for a crash. Discarding crashes is not supported. The unmodified event will be sent.")
writer.write(value: rumError)
writer.write(value: self.sanitizer.sanitize(event: rumError))
}
}
}
Expand Down Expand Up @@ -274,13 +275,13 @@ internal struct CrashReportReceiver: FeatureMessageReceiver {
let rumError = builder.createRUMError(with: updatedRUMView)

if let mappedError = self.eventsMapper.map(event: rumError) {
writer.write(value: mappedError)
writer.write(value: self.sanitizer.sanitize(event: mappedError))
Comment thread
barboraplasovska marked this conversation as resolved.
} else {
DD.logger.warn("errorEventMapper returned 'nil' for a crash. Discarding crashes is not supported. The unmodified event will be sent.")
writer.write(value: rumError)
writer.write(value: self.sanitizer.sanitize(event: rumError))
}
if let mappedView = self.eventsMapper.map(event: updatedRUMView) {
writer.write(value: self.eventsMapper.map(event: mappedView))
writer.write(value: self.sanitizer.sanitize(event: mappedView))
}
}
}
Expand Down
4 changes: 4 additions & 0 deletions DatadogRUM/Sources/RUMEvent/RUMEventSanitizer.swift
Original file line number Diff line number Diff line change
Expand Up @@ -91,3 +91,7 @@ extension RUMResourceEvent: RUMSanitizableEvent {}
extension RUMErrorEvent: RUMSanitizableEvent {}

extension RUMLongTaskEvent: RUMSanitizableEvent {}

extension RUMVitalAppLaunchEvent: RUMSanitizableEvent {}

extension RUMVitalOperationStepEvent: RUMSanitizableEvent {}
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ internal class RUMAppLaunchManager {
private unowned let parent: RUMContextProvider
private let dependencies: RUMScopeDependencies
private let telemetryController: AppLaunchMetricController
private let sanitizer = RUMEventSanitizer()

private var timeToInitialDisplay: Double?
private var timeToFullDisplay: (
Expand Down Expand Up @@ -237,8 +238,9 @@ private extension RUMAppLaunchManager {
vital: vital
)

writer.write(value: vitalEvent)
telemetryController.track(ttidEvent: vitalEvent, context: context)
let sanitizedVitalEvent = sanitizer.sanitize(event: vitalEvent)
writer.write(value: sanitizedVitalEvent)
telemetryController.track(ttidEvent: sanitizedVitalEvent, context: context)
}

func sendTTIDMessageToProfiler(vital: Vital) {
Expand Down
Loading