From 42d6a9ea8ad75228a7a11c1a1ef83a5858de48ec Mon Sep 17 00:00:00 2001 From: Mert Buran Date: Wed, 8 Dec 2021 18:54:38 +0100 Subject: [PATCH 01/10] RUMM-1791 WebRUMEventConsumer added --- .../WKUserContentController+Datadog.swift | 28 +++++- .../WebView/WebEventBridge.swift | 6 +- .../WebView/WebLogEventConsumer.swift | 4 +- .../WebView/WebRUMEventConsumer.swift | 87 ++++++++++++++++++- 4 files changed, 115 insertions(+), 10 deletions(-) diff --git a/Sources/Datadog/FeaturesIntegration/WebView/WKUserContentController+Datadog.swift b/Sources/Datadog/FeaturesIntegration/WebView/WKUserContentController+Datadog.swift index e3baf4b917..1600d020ec 100644 --- a/Sources/Datadog/FeaturesIntegration/WebView/WKUserContentController+Datadog.swift +++ b/Sources/Datadog/FeaturesIntegration/WebView/WKUserContentController+Datadog.swift @@ -7,6 +7,17 @@ import Foundation import WebKit +internal protocol WebRUMEventContextProviding { + var context: RUMContext? { get } +} + +internal class WebRUMEventContextProvider: WebRUMEventContextProviding { + var context: RUMContext? { + // TODO: RUMM-1786 implement web event context provider + return nil + } +} + // TODO: RUMM-1794 rename the methods public extension WKUserContentController { func addDatadogMessageHandler(allowedWebViewHosts: Set) { @@ -16,10 +27,19 @@ public extension WKUserContentController { internal func __addDatadogMessageHandler(allowedWebViewHosts: Set, hostsSanitizer: HostsSanitizing) { let bridgeName = DatadogMessageHandler.name + let contextProvider = WebRUMEventContextProvider() + let logEventConsumer = WebLogEventConsumer() + let rumEventConsumer = WebRUMEventConsumer( + dataWriter: RUMFeature.instance?.storage.writer, + dateCorrector: RUMFeature.instance?.dateCorrector, + webRUMEventMapper: WebRUMEventMapper(), + contextProvider: contextProvider + ) + let messageHandler = DatadogMessageHandler( eventBridge: WebEventBridge( - logEventConsumer: WebLogEventConsumer(), - rumEventConsumer: WebRUMEventConsumer() + logEventConsumer: logEventConsumer, + rumEventConsumer: rumEventConsumer ) ) add(messageHandler, name: bridgeName) @@ -73,9 +93,11 @@ private class DatadogMessageHandler: NSObject, WKScriptMessageHandler { _ userContentController: WKUserContentController, didReceive message: WKScriptMessage ) { + // message.body must be called within UI thread + let messageBody = message.body queue.async { do { - try self.eventBridge.consume(message.body) + try self.eventBridge.consume(messageBody) } catch { userLogger.error("🔥 Web Event Error: \(error)") } diff --git a/Sources/Datadog/FeaturesIntegration/WebView/WebEventBridge.swift b/Sources/Datadog/FeaturesIntegration/WebView/WebEventBridge.swift index 457a7f3bdc..7cd97257ce 100644 --- a/Sources/Datadog/FeaturesIntegration/WebView/WebEventBridge.swift +++ b/Sources/Datadog/FeaturesIntegration/WebView/WebEventBridge.swift @@ -9,7 +9,7 @@ import Foundation internal typealias JSON = [String: Any] internal protocol WebEventConsumer { - func consume(event: JSON, eventType: String) + func consume(event: JSON, eventType: String) throws } internal enum WebEventError: Error, Equatable { @@ -47,9 +47,9 @@ internal class WebEventBridge { } if eventType == Constants.eventTypeLog { - logEventConsumer.consume(event: wrappedEvent, eventType: eventType) + try logEventConsumer.consume(event: wrappedEvent, eventType: eventType) } else { - rumEventConsumer.consume(event: wrappedEvent, eventType: eventType) + try rumEventConsumer.consume(event: wrappedEvent, eventType: eventType) } } diff --git a/Sources/Datadog/FeaturesIntegration/WebView/WebLogEventConsumer.swift b/Sources/Datadog/FeaturesIntegration/WebView/WebLogEventConsumer.swift index b9935ce23c..4ac31acf35 100644 --- a/Sources/Datadog/FeaturesIntegration/WebView/WebLogEventConsumer.swift +++ b/Sources/Datadog/FeaturesIntegration/WebView/WebLogEventConsumer.swift @@ -7,7 +7,7 @@ import Foundation internal class WebLogEventConsumer: WebEventConsumer { - func consume(event: [String: Any], eventType: String) { - // TODO: RUMM-1791 implement event consumers + func consume(event: JSON, eventType: String) throws { + // TODO: RUMM-1791 implement log event consumer } } diff --git a/Sources/Datadog/FeaturesIntegration/WebView/WebRUMEventConsumer.swift b/Sources/Datadog/FeaturesIntegration/WebView/WebRUMEventConsumer.swift index 9a4df60d05..4ebc174e72 100644 --- a/Sources/Datadog/FeaturesIntegration/WebView/WebRUMEventConsumer.swift +++ b/Sources/Datadog/FeaturesIntegration/WebView/WebRUMEventConsumer.swift @@ -6,8 +6,91 @@ import Foundation +// TODO: RUMM-1786 implement mappers +internal class WebRUMEventMapper { } + internal class WebRUMEventConsumer: WebEventConsumer { - func consume(event: [String: Any], eventType: String) { - // TODO: RUMM-1791 implement event consumers + private let dataWriter: AsyncWriter? + private let dateCorrector: DateCorrectorType? + private let webRUMEventMapper: WebRUMEventMapper? + private let contextProvider: WebRUMEventContextProviding? + + init( + dataWriter: AsyncWriter?, + dateCorrector: DateCorrectorType?, + webRUMEventMapper: WebRUMEventMapper?, + contextProvider: WebRUMEventContextProviding? + ) { + self.dataWriter = dataWriter + self.dateCorrector = dateCorrector + self.webRUMEventMapper = webRUMEventMapper + self.contextProvider = contextProvider + } + + func consume(event: JSON, eventType: String) throws { + let eventData = try JSONSerialization.data(withJSONObject: event, options: []) + let jsonDecoder = JSONDecoder() + let rumContext = contextProvider?.context + + switch eventType { + case "view": + let viewEvent = try jsonDecoder.decode(RUMViewEvent.self, from: eventData) + let mappedViewEvent = mapIfNeeded(dataModel: viewEvent, context: rumContext, offset: getOffset(viewID: viewEvent.view.id)) + write(mappedViewEvent) + case "action": + let actionEvent = try jsonDecoder.decode(RUMViewEvent.self, from: eventData) + let mappedActionEvent = mapIfNeeded(dataModel: actionEvent, context: rumContext, offset: getOffset(viewID: actionEvent.view.id)) + write(mappedActionEvent) + case "resource": + let resourceEvent = try jsonDecoder.decode(RUMViewEvent.self, from: eventData) + let mappedResourceEvent = mapIfNeeded(dataModel: resourceEvent, context: rumContext, offset: getOffset(viewID: resourceEvent.view.id)) + write(mappedResourceEvent) + case "error": + let errorEvent = try jsonDecoder.decode(RUMViewEvent.self, from: eventData) + let mappedErrorEvent = mapIfNeeded(dataModel: errorEvent, context: rumContext, offset: getOffset(viewID: errorEvent.view.id)) + write(mappedErrorEvent) + case "long_task": + let longTaskEvent = try jsonDecoder.decode(RUMViewEvent.self, from: eventData) + let mappedLongTaskEvent = mapIfNeeded(dataModel: longTaskEvent, context: rumContext, offset: getOffset(viewID: longTaskEvent.view.id)) + write(mappedLongTaskEvent) + default: + userLogger.error("🔥 Web RUM Event Error - Unknown event type: \(eventType)") + } + } + + private func mapIfNeeded(dataModel: T, context: RUMContext?, offset: Offset) -> T { + guard let context = context else { + return dataModel + } + // TODO: RUMM-1786 implement mappers + let mappedDataModel = dataModel + return mappedDataModel + } + + private func write(_ model: T) { + dataWriter?.write(value: model) + } + + // MARK: - Time offsets + + // Q: do we really need to cache `offsets`? can't we just read `dateCorrector?.currentCorrection.serverTimeOffset`? + + private typealias Offset = TimeInterval + private var offsets = [String: Offset]() + + private func getOffset(viewID: String) -> Offset { + var offset = offsets[viewID] + if offset == nil { + offset = dateCorrector?.currentCorrection.serverTimeOffset + offsets[viewID] = offset + } + + purgeOffsets() + return offset ?? 0.0 + } + + private func purgeOffsets() { + // TODO: RUMM-1791 keep only 3 most recent entries. + // android uses LinkedHashMap/OrderedDictionary. } } From fd18fa7cf9b57fa62b9944a33c8c4e18a0f19eb2 Mon Sep 17 00:00:00 2001 From: Mert Buran Date: Fri, 10 Dec 2021 14:50:25 +0100 Subject: [PATCH 02/10] RUMM-1791 WebLogEventConsumer added --- Datadog/Datadog.xcodeproj/project.pbxproj | 4 + .../WKUserContentController+Datadog.swift | 20 +-- .../WebView/WebLogEventConsumer.swift | 65 +++++++- .../Datadog/Logging/Log/LogEventEncoder.swift | 1 + .../WebView/WebLogEventConsumerTests.swift | 142 ++++++++++++++++++ 5 files changed, 218 insertions(+), 14 deletions(-) create mode 100644 Tests/DatadogTests/Datadog/RUM/WebView/WebLogEventConsumerTests.swift diff --git a/Datadog/Datadog.xcodeproj/project.pbxproj b/Datadog/Datadog.xcodeproj/project.pbxproj index 38ddfb970e..c030a188e2 100644 --- a/Datadog/Datadog.xcodeproj/project.pbxproj +++ b/Datadog/Datadog.xcodeproj/project.pbxproj @@ -502,6 +502,7 @@ 9E9973F1268DF69500D8059B /* VitalInfoSampler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E9973F0268DF69500D8059B /* VitalInfoSampler.swift */; }; 9EA3CA6926775A3500B16871 /* VitalRefreshRateReader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9EA3CA6826775A3500B16871 /* VitalRefreshRateReader.swift */; }; 9EA8A7F1275E1518007D6FDB /* HostsSanitizerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9EA8A7F0275E1518007D6FDB /* HostsSanitizerTests.swift */; }; + 9EA8A7F82768A72B007D6FDB /* WebLogEventConsumerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9EA8A7F72768A72B007D6FDB /* WebLogEventConsumerTests.swift */; }; 9EAF0CF6275A21100044E8CA /* WKUserContentController+DatadogTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9EAF0CF5275A21100044E8CA /* WKUserContentController+DatadogTests.swift */; }; 9EAF0CF8275A2FDC0044E8CA /* HostsSanitizer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9EAF0CF7275A2FDC0044E8CA /* HostsSanitizer.swift */; }; 9EB4B862274E79D50041CD03 /* WKUserContentController+Datadog.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9EB4B861274E79D50041CD03 /* WKUserContentController+Datadog.swift */; }; @@ -1167,6 +1168,7 @@ 9E9EB37624468CE90002C80B /* Datadog.modulemap */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.module-map"; path = Datadog.modulemap; sourceTree = ""; }; 9EA3CA6826775A3500B16871 /* VitalRefreshRateReader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VitalRefreshRateReader.swift; sourceTree = ""; }; 9EA8A7F0275E1518007D6FDB /* HostsSanitizerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HostsSanitizerTests.swift; sourceTree = ""; }; + 9EA8A7F72768A72B007D6FDB /* WebLogEventConsumerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WebLogEventConsumerTests.swift; sourceTree = ""; }; 9EAF0CF5275A21100044E8CA /* WKUserContentController+DatadogTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "WKUserContentController+DatadogTests.swift"; sourceTree = ""; }; 9EAF0CF7275A2FDC0044E8CA /* HostsSanitizer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HostsSanitizer.swift; sourceTree = ""; }; 9EB4B861274E79D50041CD03 /* WKUserContentController+Datadog.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "WKUserContentController+Datadog.swift"; sourceTree = ""; }; @@ -3295,6 +3297,7 @@ children = ( 9EB4B86B27510AF90041CD03 /* WebEventBridgeTests.swift */, 9EAF0CF5275A21100044E8CA /* WKUserContentController+DatadogTests.swift */, + 9EA8A7F72768A72B007D6FDB /* WebLogEventConsumerTests.swift */, ); path = WebView; sourceTree = ""; @@ -4216,6 +4219,7 @@ 61B5E42B26DFC433000B0A5F /* DDNSURLSessionDelegate+apiTests.m in Sources */, 61F8CC092469295500FE2908 /* DatadogConfigurationBuilderTests.swift in Sources */, 61F1A623249B811200075390 /* Encoding.swift in Sources */, + 9EA8A7F82768A72B007D6FDB /* WebLogEventConsumerTests.swift in Sources */, 6114FE3B25768AA90084E372 /* ConsentProviderTests.swift in Sources */, 61133C642423990D00786299 /* LoggerTests.swift in Sources */, 617B953D24BF4D8F00E6F443 /* RUMMonitorTests.swift in Sources */, diff --git a/Sources/Datadog/FeaturesIntegration/WebView/WKUserContentController+Datadog.swift b/Sources/Datadog/FeaturesIntegration/WebView/WKUserContentController+Datadog.swift index 1600d020ec..befbe3f387 100644 --- a/Sources/Datadog/FeaturesIntegration/WebView/WKUserContentController+Datadog.swift +++ b/Sources/Datadog/FeaturesIntegration/WebView/WKUserContentController+Datadog.swift @@ -7,17 +7,6 @@ import Foundation import WebKit -internal protocol WebRUMEventContextProviding { - var context: RUMContext? { get } -} - -internal class WebRUMEventContextProvider: WebRUMEventContextProviding { - var context: RUMContext? { - // TODO: RUMM-1786 implement web event context provider - return nil - } -} - // TODO: RUMM-1794 rename the methods public extension WKUserContentController { func addDatadogMessageHandler(allowedWebViewHosts: Set) { @@ -27,8 +16,13 @@ public extension WKUserContentController { internal func __addDatadogMessageHandler(allowedWebViewHosts: Set, hostsSanitizer: HostsSanitizing) { let bridgeName = DatadogMessageHandler.name - let contextProvider = WebRUMEventContextProvider() - let logEventConsumer = WebLogEventConsumer() + let contextProvider = (Global.rum as? RUMMonitor)?.contextProvider + let logEventConsumer = WebLogEventConsumer( + userLogsWriter: LoggingFeature.instance?.storage.writer, + internalLogsWriter: InternalMonitoringFeature.instance?.logsStorage.writer, + dateCorrector: LoggingFeature.instance?.dateCorrector, + rumContextProvider: contextProvider + ) let rumEventConsumer = WebRUMEventConsumer( dataWriter: RUMFeature.instance?.storage.writer, dateCorrector: RUMFeature.instance?.dateCorrector, diff --git a/Sources/Datadog/FeaturesIntegration/WebView/WebLogEventConsumer.swift b/Sources/Datadog/FeaturesIntegration/WebView/WebLogEventConsumer.swift index 4ac31acf35..31ac760ce1 100644 --- a/Sources/Datadog/FeaturesIntegration/WebView/WebLogEventConsumer.swift +++ b/Sources/Datadog/FeaturesIntegration/WebView/WebLogEventConsumer.swift @@ -7,7 +7,70 @@ import Foundation internal class WebLogEventConsumer: WebEventConsumer { + private struct Constants { + static let logEventType = "log" + static let internalLogEventType = "internal_log" + + static let applicationIDKey = "application_id" + static let sessionIDKey = "session_id" + static let ddTagsKey = "ddtags" + static let dateKey = "date" + } + + private let userLogsWriter: Writer? + private let internalLogsWriter: Writer? + private let dateCorrector: DateCorrectorType? + private let rumContextProvider: RUMContextProvider? + private let jsonDecoder = JSONDecoder() + + private lazy var ddTags: String = { + let versionKey = LogEventEncoder.StaticCodingKeys.applicationVersion.rawValue + let versionValue = LoggingFeature.instance?.configuration.common.applicationVersion ?? "" + let envKey = LogEventEncoder.StaticCodingKeys.environment.rawValue + let envValue = LoggingFeature.instance?.configuration.common.environment ?? "" + + return "\(versionKey):\(versionValue),\(envKey):\(envValue)" + }() + + init( + userLogsWriter: Writer?, + internalLogsWriter: Writer?, + dateCorrector: DateCorrectorType?, + rumContextProvider: RUMContextProvider? + ) { + self.userLogsWriter = userLogsWriter + self.internalLogsWriter = internalLogsWriter + self.dateCorrector = dateCorrector + self.rumContextProvider = rumContextProvider + } + func consume(event: JSON, eventType: String) throws { - // TODO: RUMM-1791 implement log event consumer + var mutableEvent = event + + if let existingTags = mutableEvent[Constants.ddTagsKey] as? String, !existingTags.isEmpty { + mutableEvent[Constants.ddTagsKey] = "\(ddTags),\(existingTags)" + } else { + mutableEvent[Constants.ddTagsKey] = ddTags + } + + if let date = mutableEvent[Constants.dateKey] as? Int { + let serverTimeOffsetInNs = dateCorrector?.currentCorrection.serverTimeOffset.toInt64Nanoseconds ?? 0 + let correctedDate = Int64(date) + serverTimeOffsetInNs + mutableEvent[Constants.dateKey] = correctedDate + } + + if let context = rumContextProvider?.context { + mutableEvent[Constants.applicationIDKey] = context.rumApplicationID + mutableEvent[Constants.sessionIDKey] = context.sessionID.toRUMDataFormat + } + + let jsonData = try JSONSerialization.data(withJSONObject: mutableEvent, options: []) + let encodableEvent = try jsonDecoder.decode(CodableValue.self, from: jsonData) + + if eventType == Constants.logEventType { + userLogsWriter?.write(value: encodableEvent) + } else if eventType == Constants.internalLogEventType { + internalLogsWriter?.write(value: encodableEvent) + } } } diff --git a/Sources/Datadog/Logging/Log/LogEventEncoder.swift b/Sources/Datadog/Logging/Log/LogEventEncoder.swift index 58663c2808..252c6290d9 100644 --- a/Sources/Datadog/Logging/Log/LogEventEncoder.swift +++ b/Sources/Datadog/Logging/Log/LogEventEncoder.swift @@ -89,6 +89,7 @@ internal struct LogEventEncoder { case status case message case serviceName = "service" + case environment = "env" case tags = "ddtags" // MARK: - Error diff --git a/Tests/DatadogTests/Datadog/RUM/WebView/WebLogEventConsumerTests.swift b/Tests/DatadogTests/Datadog/RUM/WebView/WebLogEventConsumerTests.swift new file mode 100644 index 0000000000..b3f6fa5a59 --- /dev/null +++ b/Tests/DatadogTests/Datadog/RUM/WebView/WebLogEventConsumerTests.swift @@ -0,0 +1,142 @@ +/* + * Unless explicitly stated otherwise all files in this repository are licensed under the Apache License Version 2.0. + * This product includes software developed at Datadog (https://www.datadoghq.com/). + * Copyright 2019-2020 Datadog, Inc. + */ + +import XCTest +@testable import Datadog + +class WebLogEventConsumerTests: XCTestCase { + let mockUserLogsWriter = FileWriterMock() + let mockInternalLogsWriter = FileWriterMock() + let mockDateCorrector = DateCorrectorMock() + let mockContextProvider = RUMContextProviderMock(context: .mockWith(rumApplicationID: "123456")) + + func testWhenValidWebLogEventPassed_itDecoratesAndPassesToWriter() throws { + let mockSessionID = UUID(uuidString: "e9796469-c2a1-43d6-b0f6-65c47d33cf5f")! + mockContextProvider.context.sessionID = RUMUUID(rawValue: mockSessionID) + mockDateCorrector.correctionOffset = 123 + let eventConsumer = WebLogEventConsumer( + userLogsWriter: mockUserLogsWriter, + internalLogsWriter: mockInternalLogsWriter, + dateCorrector: mockDateCorrector, + rumContextProvider: mockContextProvider + ) + + let webLogEvent: JSON = [ + "date": 1_635_932_927_012, + "error": ["origin": "console"], + "message": "console error: error", + "session_id": "0110cab4-7471-480e-aa4e-7ce039ced355", + "status": "error", + "view": ["referrer": "", "url": "https://datadoghq.dev/browser-sdk-test-playground"] + ] + let expectedWebLogEvent: JSON = [ + "date": 1_635_932_927_012 + 123.toInt64Nanoseconds, + "error": ["origin": "console"], + "message": "console error: error", + "application_id": "123456", + "session_id": mockSessionID.uuidString.lowercased(), + "status": "error", + "ddtags": "version:,env:", + "view": ["referrer": "", "url": "https://datadoghq.dev/browser-sdk-test-playground"] + ] + + try eventConsumer.consume(event: webLogEvent, eventType: "log") + + let data = try JSONEncoder().encode(mockUserLogsWriter.dataWritten as? CodableValue) + let writtenJSON = try XCTUnwrap(try JSONSerialization.jsonObject(with: data, options: []) as? JSON) + + AssertDictionariesEqual(writtenJSON, expectedWebLogEvent) + + XCTAssertNil(mockInternalLogsWriter.dataWritten) + } + + func testWhenValidWebInternalLogEventPassed_itDecoratesAndPassesToWriter() throws { + let mockSessionID = UUID(uuidString: "e9796469-c2a1-43d6-b0f6-65c47d33cf5f")! + mockContextProvider.context.sessionID = RUMUUID(rawValue: mockSessionID) + mockDateCorrector.correctionOffset = 123 + let eventConsumer = WebLogEventConsumer( + userLogsWriter: mockUserLogsWriter, + internalLogsWriter: mockInternalLogsWriter, + dateCorrector: mockDateCorrector, + rumContextProvider: mockContextProvider + ) + + let webLogEvent: JSON = [ + "date": 1_635_932_927_012, + "error": ["origin": "console"], + "message": "console error: error", + "session_id": "0110cab4-7471-480e-aa4e-7ce039ced355", + "status": "error", + "view": ["referrer": "", "url": "https://datadoghq.dev/browser-sdk-test-playground"] + ] + let expectedWebLogEvent: JSON = [ + "date": 1_635_932_927_012 + 123.toInt64Nanoseconds, + "error": ["origin": "console"], + "message": "console error: error", + "application_id": "123456", + "session_id": mockSessionID.uuidString.lowercased(), + "status": "error", + "ddtags": "version:,env:", + "view": ["referrer": "", "url": "https://datadoghq.dev/browser-sdk-test-playground"] + ] + + try eventConsumer.consume(event: webLogEvent, eventType: "internal_log") + + let data = try JSONEncoder().encode(mockInternalLogsWriter.dataWritten as? CodableValue) + let writtenJSON = try XCTUnwrap(try JSONSerialization.jsonObject(with: data, options: []) as? JSON) + + AssertDictionariesEqual(writtenJSON, expectedWebLogEvent) + + XCTAssertNil(mockUserLogsWriter.dataWritten) + } + + func testWhenInvalidEventTypePassed_itIgnoresEvent() throws { + let eventConsumer = WebLogEventConsumer( + userLogsWriter: mockUserLogsWriter, + internalLogsWriter: mockInternalLogsWriter, + dateCorrector: mockDateCorrector, + rumContextProvider: mockContextProvider + ) + + let webLogEvent: JSON = [ + "date": 1_635_932_927_012, + "message": "console error: error", + "status": "error" + ] + + try eventConsumer.consume(event: webLogEvent, eventType: "invalid_log") + + XCTAssertNil(mockUserLogsWriter.dataWritten) + XCTAssertNil(mockInternalLogsWriter.dataWritten) + } + + func testWhenContextIsUnavailable_itPassesEventAsIs() throws { + let eventConsumer = WebLogEventConsumer( + userLogsWriter: mockUserLogsWriter, + internalLogsWriter: mockInternalLogsWriter, + dateCorrector: nil, + rumContextProvider: nil + ) + + let webLogEvent: JSON = [ + "date": 1_635_932_927_012, + "error": ["origin": "console"], + "message": "console error: error", + "session_id": "0110cab4-7471-480e-aa4e-7ce039ced355", + "status": "error", + "view": ["referrer": "", "url": "https://datadoghq.dev/browser-sdk-test-playground"] + ] + + try eventConsumer.consume(event: webLogEvent, eventType: "log") + + let data = try JSONEncoder().encode(mockUserLogsWriter.dataWritten as? CodableValue) + let writtenJSON = try XCTUnwrap(try JSONSerialization.jsonObject(with: data, options: []) as? JSON) + + AssertDictionariesEqual(writtenJSON, webLogEvent) + + XCTAssertNil(mockInternalLogsWriter.dataWritten) + } +} From aeab8c45dcf5cf08064f409c2f464cf319268370 Mon Sep 17 00:00:00 2001 From: Mert Buran Date: Wed, 15 Dec 2021 21:03:05 +0100 Subject: [PATCH 03/10] RUMM-1791 Offsets cache added to WebRUMEventConsumer --- .../WebView/WebRUMEventConsumer.swift | 33 ++++++++++--------- 1 file changed, 17 insertions(+), 16 deletions(-) diff --git a/Sources/Datadog/FeaturesIntegration/WebView/WebRUMEventConsumer.swift b/Sources/Datadog/FeaturesIntegration/WebView/WebRUMEventConsumer.swift index 4ebc174e72..a240b1f508 100644 --- a/Sources/Datadog/FeaturesIntegration/WebView/WebRUMEventConsumer.swift +++ b/Sources/Datadog/FeaturesIntegration/WebView/WebRUMEventConsumer.swift @@ -10,16 +10,16 @@ import Foundation internal class WebRUMEventMapper { } internal class WebRUMEventConsumer: WebEventConsumer { - private let dataWriter: AsyncWriter? + private let dataWriter: Writer? private let dateCorrector: DateCorrectorType? private let webRUMEventMapper: WebRUMEventMapper? - private let contextProvider: WebRUMEventContextProviding? + private let contextProvider: RUMContextProvider? init( - dataWriter: AsyncWriter?, + dataWriter: Writer?, dateCorrector: DateCorrectorType?, webRUMEventMapper: WebRUMEventMapper?, - contextProvider: WebRUMEventContextProviding? + contextProvider: RUMContextProvider? ) { self.dataWriter = dataWriter self.dateCorrector = dateCorrector @@ -73,24 +73,25 @@ internal class WebRUMEventConsumer: WebEventConsumer { // MARK: - Time offsets - // Q: do we really need to cache `offsets`? can't we just read `dateCorrector?.currentCorrection.serverTimeOffset`? - private typealias Offset = TimeInterval - private var offsets = [String: Offset]() + private typealias ViewIDOffsetPair = (viewID: String, offset: Offset) + private var viewIDOffsetPairs = [ViewIDOffsetPair]() private func getOffset(viewID: String) -> Offset { - var offset = offsets[viewID] - if offset == nil { - offset = dateCorrector?.currentCorrection.serverTimeOffset - offsets[viewID] = offset - } - purgeOffsets() - return offset ?? 0.0 + + let found = viewIDOffsetPairs.first { $0.viewID == viewID } + if let found = found { + return found.offset + } + let offset = dateCorrector?.currentCorrection.serverTimeOffset ?? 0.0 + viewIDOffsetPairs.insert((viewID: viewID, offset: offset), at: 0) + return offset } private func purgeOffsets() { - // TODO: RUMM-1791 keep only 3 most recent entries. - // android uses LinkedHashMap/OrderedDictionary. + while viewIDOffsetPairs.count > 3 { + _ = viewIDOffsetPairs.popLast() + } } } From fc693d3089d93ba3bc8e5d3ef21e841b6e973125 Mon Sep 17 00:00:00 2001 From: Mert Buran Date: Mon, 20 Dec 2021 21:30:21 +0100 Subject: [PATCH 04/10] RUMM-1791 PR comments addressed Optionals to non-optionals, non-optionals to optionals ashes to ashes, dust to dust... --- .../WKUserContentController+Datadog.swift | 32 ++++++++++++------- .../WebView/WebEventBridge.swift | 10 +++--- .../WebView/WebLogEventConsumer.swift | 12 +++---- .../WebView/WebRUMEventConsumer.swift | 12 +++---- .../WebView/WebLogEventConsumerTests.swift | 2 +- 5 files changed, 38 insertions(+), 30 deletions(-) diff --git a/Sources/Datadog/FeaturesIntegration/WebView/WKUserContentController+Datadog.swift b/Sources/Datadog/FeaturesIntegration/WebView/WKUserContentController+Datadog.swift index befbe3f387..e5e211c73c 100644 --- a/Sources/Datadog/FeaturesIntegration/WebView/WKUserContentController+Datadog.swift +++ b/Sources/Datadog/FeaturesIntegration/WebView/WKUserContentController+Datadog.swift @@ -17,18 +17,26 @@ public extension WKUserContentController { let bridgeName = DatadogMessageHandler.name let contextProvider = (Global.rum as? RUMMonitor)?.contextProvider - let logEventConsumer = WebLogEventConsumer( - userLogsWriter: LoggingFeature.instance?.storage.writer, - internalLogsWriter: InternalMonitoringFeature.instance?.logsStorage.writer, - dateCorrector: LoggingFeature.instance?.dateCorrector, - rumContextProvider: contextProvider - ) - let rumEventConsumer = WebRUMEventConsumer( - dataWriter: RUMFeature.instance?.storage.writer, - dateCorrector: RUMFeature.instance?.dateCorrector, - webRUMEventMapper: WebRUMEventMapper(), - contextProvider: contextProvider - ) + + var logEventConsumer: WebLogEventConsumer? = nil + if let loggingFeature = LoggingFeature.instance { + logEventConsumer = WebLogEventConsumer( + userLogsWriter: loggingFeature.storage.writer, + internalLogsWriter: InternalMonitoringFeature.instance?.logsStorage.writer, + dateCorrector: loggingFeature.dateCorrector, + rumContextProvider: contextProvider + ) + } + + var rumEventConsumer: WebRUMEventConsumer? = nil + if let rumFeature = RUMFeature.instance { + rumEventConsumer = WebRUMEventConsumer( + dataWriter: rumFeature.storage.writer, + dateCorrector: rumFeature.dateCorrector, + webRUMEventMapper: WebRUMEventMapper(), + contextProvider: contextProvider + ) + } let messageHandler = DatadogMessageHandler( eventBridge: WebEventBridge( diff --git a/Sources/Datadog/FeaturesIntegration/WebView/WebEventBridge.swift b/Sources/Datadog/FeaturesIntegration/WebView/WebEventBridge.swift index 7cd97257ce..994f153ec7 100644 --- a/Sources/Datadog/FeaturesIntegration/WebView/WebEventBridge.swift +++ b/Sources/Datadog/FeaturesIntegration/WebView/WebEventBridge.swift @@ -26,10 +26,10 @@ internal class WebEventBridge { static let eventTypeLog = "log" } - private let logEventConsumer: WebEventConsumer - private let rumEventConsumer: WebEventConsumer + private let logEventConsumer: WebEventConsumer? + private let rumEventConsumer: WebEventConsumer? - init(logEventConsumer: WebEventConsumer, rumEventConsumer: WebEventConsumer) { + init(logEventConsumer: WebEventConsumer?, rumEventConsumer: WebEventConsumer?) { self.logEventConsumer = logEventConsumer self.rumEventConsumer = rumEventConsumer } @@ -47,9 +47,9 @@ internal class WebEventBridge { } if eventType == Constants.eventTypeLog { - try logEventConsumer.consume(event: wrappedEvent, eventType: eventType) + try logEventConsumer?.consume(event: wrappedEvent, eventType: eventType) } else { - try rumEventConsumer.consume(event: wrappedEvent, eventType: eventType) + try rumEventConsumer?.consume(event: wrappedEvent, eventType: eventType) } } diff --git a/Sources/Datadog/FeaturesIntegration/WebView/WebLogEventConsumer.swift b/Sources/Datadog/FeaturesIntegration/WebView/WebLogEventConsumer.swift index 31ac760ce1..0c3839f292 100644 --- a/Sources/Datadog/FeaturesIntegration/WebView/WebLogEventConsumer.swift +++ b/Sources/Datadog/FeaturesIntegration/WebView/WebLogEventConsumer.swift @@ -17,9 +17,9 @@ internal class WebLogEventConsumer: WebEventConsumer { static let dateKey = "date" } - private let userLogsWriter: Writer? + private let userLogsWriter: Writer private let internalLogsWriter: Writer? - private let dateCorrector: DateCorrectorType? + private let dateCorrector: DateCorrectorType private let rumContextProvider: RUMContextProvider? private let jsonDecoder = JSONDecoder() @@ -33,9 +33,9 @@ internal class WebLogEventConsumer: WebEventConsumer { }() init( - userLogsWriter: Writer?, + userLogsWriter: Writer, internalLogsWriter: Writer?, - dateCorrector: DateCorrectorType?, + dateCorrector: DateCorrectorType, rumContextProvider: RUMContextProvider? ) { self.userLogsWriter = userLogsWriter @@ -54,7 +54,7 @@ internal class WebLogEventConsumer: WebEventConsumer { } if let date = mutableEvent[Constants.dateKey] as? Int { - let serverTimeOffsetInNs = dateCorrector?.currentCorrection.serverTimeOffset.toInt64Nanoseconds ?? 0 + let serverTimeOffsetInNs = dateCorrector.currentCorrection.serverTimeOffset.toInt64Nanoseconds let correctedDate = Int64(date) + serverTimeOffsetInNs mutableEvent[Constants.dateKey] = correctedDate } @@ -68,7 +68,7 @@ internal class WebLogEventConsumer: WebEventConsumer { let encodableEvent = try jsonDecoder.decode(CodableValue.self, from: jsonData) if eventType == Constants.logEventType { - userLogsWriter?.write(value: encodableEvent) + userLogsWriter.write(value: encodableEvent) } else if eventType == Constants.internalLogEventType { internalLogsWriter?.write(value: encodableEvent) } diff --git a/Sources/Datadog/FeaturesIntegration/WebView/WebRUMEventConsumer.swift b/Sources/Datadog/FeaturesIntegration/WebView/WebRUMEventConsumer.swift index a240b1f508..3327856bbf 100644 --- a/Sources/Datadog/FeaturesIntegration/WebView/WebRUMEventConsumer.swift +++ b/Sources/Datadog/FeaturesIntegration/WebView/WebRUMEventConsumer.swift @@ -10,14 +10,14 @@ import Foundation internal class WebRUMEventMapper { } internal class WebRUMEventConsumer: WebEventConsumer { - private let dataWriter: Writer? - private let dateCorrector: DateCorrectorType? + private let dataWriter: Writer + private let dateCorrector: DateCorrectorType private let webRUMEventMapper: WebRUMEventMapper? private let contextProvider: RUMContextProvider? init( - dataWriter: Writer?, - dateCorrector: DateCorrectorType?, + dataWriter: Writer, + dateCorrector: DateCorrectorType, webRUMEventMapper: WebRUMEventMapper?, contextProvider: RUMContextProvider? ) { @@ -68,7 +68,7 @@ internal class WebRUMEventConsumer: WebEventConsumer { } private func write(_ model: T) { - dataWriter?.write(value: model) + dataWriter.write(value: model) } // MARK: - Time offsets @@ -84,7 +84,7 @@ internal class WebRUMEventConsumer: WebEventConsumer { if let found = found { return found.offset } - let offset = dateCorrector?.currentCorrection.serverTimeOffset ?? 0.0 + let offset = dateCorrector.currentCorrection.serverTimeOffset viewIDOffsetPairs.insert((viewID: viewID, offset: offset), at: 0) return offset } diff --git a/Tests/DatadogTests/Datadog/RUM/WebView/WebLogEventConsumerTests.swift b/Tests/DatadogTests/Datadog/RUM/WebView/WebLogEventConsumerTests.swift index b3f6fa5a59..080f9b7eca 100644 --- a/Tests/DatadogTests/Datadog/RUM/WebView/WebLogEventConsumerTests.swift +++ b/Tests/DatadogTests/Datadog/RUM/WebView/WebLogEventConsumerTests.swift @@ -117,7 +117,7 @@ class WebLogEventConsumerTests: XCTestCase { let eventConsumer = WebLogEventConsumer( userLogsWriter: mockUserLogsWriter, internalLogsWriter: mockInternalLogsWriter, - dateCorrector: nil, + dateCorrector: mockDateCorrector, rumContextProvider: nil ) From edc041308ac2f87978c8106af034adfe3c9673ea Mon Sep 17 00:00:00 2001 From: Mert Buran Date: Wed, 22 Dec 2021 21:06:22 +0100 Subject: [PATCH 05/10] RUMM-1791 PR comments addressed WKUserContentController_DatadogTests improved --- .../WKUserContentController+Datadog.swift | 4 +- .../WebView/WebRUMEventConsumer.swift | 8 +- ...WKUserContentController+DatadogTests.swift | 120 +++++++++++++++++- 3 files changed, 123 insertions(+), 9 deletions(-) diff --git a/Sources/Datadog/FeaturesIntegration/WebView/WKUserContentController+Datadog.swift b/Sources/Datadog/FeaturesIntegration/WebView/WKUserContentController+Datadog.swift index e5e211c73c..7486fa7dad 100644 --- a/Sources/Datadog/FeaturesIntegration/WebView/WKUserContentController+Datadog.swift +++ b/Sources/Datadog/FeaturesIntegration/WebView/WKUserContentController+Datadog.swift @@ -79,10 +79,10 @@ public extension WKUserContentController { } } -private class DatadogMessageHandler: NSObject, WKScriptMessageHandler { +internal class DatadogMessageHandler: NSObject, WKScriptMessageHandler { static let name = "DatadogEventBridge" private let eventBridge: WebEventBridge - private let queue = DispatchQueue( + let queue = DispatchQueue( label: "com.datadoghq.JSEventBridge", target: .global(qos: .userInteractive) ) diff --git a/Sources/Datadog/FeaturesIntegration/WebView/WebRUMEventConsumer.swift b/Sources/Datadog/FeaturesIntegration/WebView/WebRUMEventConsumer.swift index 3327856bbf..2f1a1e9576 100644 --- a/Sources/Datadog/FeaturesIntegration/WebView/WebRUMEventConsumer.swift +++ b/Sources/Datadog/FeaturesIntegration/WebView/WebRUMEventConsumer.swift @@ -38,19 +38,19 @@ internal class WebRUMEventConsumer: WebEventConsumer { let mappedViewEvent = mapIfNeeded(dataModel: viewEvent, context: rumContext, offset: getOffset(viewID: viewEvent.view.id)) write(mappedViewEvent) case "action": - let actionEvent = try jsonDecoder.decode(RUMViewEvent.self, from: eventData) + let actionEvent = try jsonDecoder.decode(RUMActionEvent.self, from: eventData) let mappedActionEvent = mapIfNeeded(dataModel: actionEvent, context: rumContext, offset: getOffset(viewID: actionEvent.view.id)) write(mappedActionEvent) case "resource": - let resourceEvent = try jsonDecoder.decode(RUMViewEvent.self, from: eventData) + let resourceEvent = try jsonDecoder.decode(RUMResourceEvent.self, from: eventData) let mappedResourceEvent = mapIfNeeded(dataModel: resourceEvent, context: rumContext, offset: getOffset(viewID: resourceEvent.view.id)) write(mappedResourceEvent) case "error": - let errorEvent = try jsonDecoder.decode(RUMViewEvent.self, from: eventData) + let errorEvent = try jsonDecoder.decode(RUMErrorEvent.self, from: eventData) let mappedErrorEvent = mapIfNeeded(dataModel: errorEvent, context: rumContext, offset: getOffset(viewID: errorEvent.view.id)) write(mappedErrorEvent) case "long_task": - let longTaskEvent = try jsonDecoder.decode(RUMViewEvent.self, from: eventData) + let longTaskEvent = try jsonDecoder.decode(RUMLongTaskEvent.self, from: eventData) let mappedLongTaskEvent = mapIfNeeded(dataModel: longTaskEvent, context: rumContext, offset: getOffset(viewID: longTaskEvent.view.id)) write(mappedLongTaskEvent) default: diff --git a/Tests/DatadogTests/Datadog/RUM/WebView/WKUserContentController+DatadogTests.swift b/Tests/DatadogTests/Datadog/RUM/WebView/WKUserContentController+DatadogTests.swift index 4fad15162f..fcf523e35c 100644 --- a/Tests/DatadogTests/Datadog/RUM/WebView/WKUserContentController+DatadogTests.swift +++ b/Tests/DatadogTests/Datadog/RUM/WebView/WKUserContentController+DatadogTests.swift @@ -9,14 +9,41 @@ import WebKit @testable import Datadog final class DDUserContentController: WKUserContentController { - private(set) var messageHandlerNames = [String]() + typealias NameHandlerPair = (name: String, handler: WKScriptMessageHandler) + private(set) var messageHandlers = [NameHandlerPair]() override func add(_ scriptMessageHandler: WKScriptMessageHandler, name: String) { - messageHandlerNames.append(name) + messageHandlers.append((name: name, handler: scriptMessageHandler)) } } +final class MockScriptMessage: WKScriptMessage { + let mockBody: Any + + init(body: Any) { + self.mockBody = body + } + + override var body: Any { return mockBody } +} + class WKUserContentController_DatadogTests: XCTestCase { + override func setUp() { + super.setUp() + XCTAssertNil(Datadog.instance) + XCTAssertNil(LoggingFeature.instance) + XCTAssertNil(RUMFeature.instance) + temporaryFeatureDirectories.create() + } + + override func tearDown() { + XCTAssertNil(Datadog.instance) + XCTAssertNil(LoggingFeature.instance) + XCTAssertNil(RUMFeature.instance) + temporaryFeatureDirectories.delete() + super.tearDown() + } + func testItAddsUserScriptAndMessageHandler() throws { let mockSanitizer = MockHostsSanitizer() let controller = DDUserContentController() @@ -26,10 +53,97 @@ class WKUserContentController_DatadogTests: XCTestCase { controller.__addDatadogMessageHandler(allowedWebViewHosts: ["datadoghq.com"], hostsSanitizer: mockSanitizer) XCTAssertEqual(controller.userScripts.count, initialUserScriptCount + 1) - XCTAssertEqual(controller.messageHandlerNames, ["DatadogEventBridge"]) + XCTAssertEqual(controller.messageHandlers.map({ $0.name }), ["DatadogEventBridge"]) + XCTAssertEqual(mockSanitizer.sanitizations.count, 1) let sanitization = try XCTUnwrap(mockSanitizer.sanitizations.first) XCTAssertEqual(sanitization.hosts, ["datadoghq.com"]) XCTAssertEqual(sanitization.warningMessage, "The allowed WebView host configured for Datadog SDK is not valid") } + + func testItLogsInvalidWebMessages() throws { + let previousUserLogger = userLogger + defer { userLogger = previousUserLogger } + let output = LogOutputMock() + userLogger = .mockWith(logOutput: output) + + let controller = DDUserContentController() + controller.__addDatadogMessageHandler(allowedWebViewHosts: ["datadoghq.com"], hostsSanitizer: MockHostsSanitizer()) + + let messageHandler = try XCTUnwrap(controller.messageHandlers.first?.handler) as? DatadogMessageHandler + // non-string body is passed + messageHandler?.userContentController(controller, didReceive: MockScriptMessage(body: 123)) + messageHandler?.queue.sync { } + + XCTAssertEqual(output.recordedLog?.status, .error) + let userLogMessage = try XCTUnwrap(output.recordedLog?.message) + XCTAssertEqual(userLogMessage, #"🔥 Web Event Error: invalidMessage(description: "123")"#) + } + + func testSendingWebEvents() throws { + let dateProvider = RelativeDateProvider(startingFrom: Date(), advancingBySeconds: 1) + LoggingFeature.instance = .mockByRecordingLogMatchers( + directories: temporaryFeatureDirectories, + configuration: .mockWith( + common: .mockWith( + applicationVersion: "1.0.0", + applicationBundleIdentifier: "com.datadoghq.ios-sdk", + serviceName: "default-service-name", + environment: "tests", + sdkVersion: "1.2.3" + ) + ), + dependencies: .mockWith( + dateProvider: RelativeDateProvider(using: .mockDecember15th2019At10AMUTC()) + ) + ) + RUMFeature.instance = .mockByRecordingRUMEventMatchers( + directories: temporaryFeatureDirectories, + dependencies: .mockWith( + dateProvider: dateProvider + ) + ) + Global.rum = RUMMonitor.initialize() + defer { + LoggingFeature.instance?.deinitialize() + Global.rum = DDNoopRUMMonitor() + RUMFeature.instance?.deinitialize() + } + + let controller = DDUserContentController() + controller.__addDatadogMessageHandler( + allowedWebViewHosts: ["datadoghq.com"], + hostsSanitizer: MockHostsSanitizer() + ) + + let messageHandler = try XCTUnwrap(controller.messageHandlers.first?.handler) as? DatadogMessageHandler + let webLogMessage = MockScriptMessage(body: #"{"eventType":"log","event":{"date":1635932927012,"error":{"origin":"console"},"message":"console error: error","session_id":"0110cab4-7471-480e-aa4e-7ce039ced355","status":"error","view":{"referrer":"","url":"https://datadoghq.dev/browser-sdk-test-playground"}},"tags":["browser_sdk_version:3.6.13"]}"#) + messageHandler?.userContentController(controller, didReceive: webLogMessage) + + let logMatcher = try LoggingFeature.waitAndReturnLogMatchers(count: 1)[0] + + logMatcher.assertValue(forKey: "date", equals: 1_635_932_927_012) + logMatcher.assertValue(forKey: "ddtags", equals: "version:1.0.0,env:tests") + logMatcher.assertValue(forKey: "message", equals: "console error: error") + logMatcher.assertValue(forKey: "status", equals: "error") + logMatcher.assertValue( + forKey: "view", + equals: ["referrer": "", "url": "https://datadoghq.dev/browser-sdk-test-playground"] + ) + logMatcher.assertValue( + forKey: "error", + equals: ["origin": "console"] + ) + // 00000000-0000-0000-0000-000000000000 is session_id of mock RUM context + logMatcher.assertValue(forKey: "session_id", equals: "00000000-0000-0000-0000-000000000000") + + let webRUMMessage = MockScriptMessage(body: #"{"eventType":"view","event":{"application":{"id":"xxx"},"date":1635933113708,"service":"super","session":{"id":"0110cab4-7471-480e-aa4e-7ce039ced355","type":"user"},"type":"view","view":{"action":{"count":0},"cumulative_layout_shift":0,"dom_complete":152800000,"dom_content_loaded":118300000,"dom_interactive":116400000,"error":{"count":0},"first_contentful_paint":121300000,"id":"64308fd4-83f9-48cb-b3e1-1e91f6721230","in_foreground_periods":[],"is_active":true,"largest_contentful_paint":121299000,"load_event":152800000,"loading_time":152800000,"loading_type":"initial_load","long_task":{"count":0},"referrer":"","resource":{"count":3},"time_spent":3120000000,"url":"http://localhost:8080/test.html"},"_dd":{"document_version":2,"drift":0,"format_version":2,"session":{"plan":2}}},"tags":["browser_sdk_version:3.6.13"]}"#) + messageHandler?.userContentController(controller, didReceive: webRUMMessage) + + let rumEventMatchers = try RUMFeature.waitAndReturnRUMEventMatchers(count: 1) + try rumEventMatchers[0].model(ofType: RUMViewEvent.self) { rumModel in + XCTAssertEqual(rumModel.application.id, "xxx") + XCTAssertEqual(rumModel.view.id, "64308fd4-83f9-48cb-b3e1-1e91f6721230") + } + } } From 5b0387966d37634e95a8ac0debfafe0ef4971b50 Mon Sep 17 00:00:00 2001 From: Mert Buran Date: Thu, 23 Dec 2021 11:39:56 +0100 Subject: [PATCH 06/10] RUMM-1791 WebRUMEventConsumerTests added --- Datadog/Datadog.xcodeproj/project.pbxproj | 4 + .../WKUserContentController+Datadog.swift | 1 - .../WebView/WebRUMEventConsumer.swift | 14 +- .../WebView/WebLogEventConsumerTests.swift | 4 +- .../WebView/WebRUMEventConsumerTests.swift | 219 ++++++++++++++++++ 5 files changed, 228 insertions(+), 14 deletions(-) create mode 100644 Tests/DatadogTests/Datadog/RUM/WebView/WebRUMEventConsumerTests.swift diff --git a/Datadog/Datadog.xcodeproj/project.pbxproj b/Datadog/Datadog.xcodeproj/project.pbxproj index c030a188e2..bb26e5cb60 100644 --- a/Datadog/Datadog.xcodeproj/project.pbxproj +++ b/Datadog/Datadog.xcodeproj/project.pbxproj @@ -486,6 +486,7 @@ 9E307C3224C8846D0039607E /* RUMDataModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E26E6B824C87693000B3270 /* RUMDataModels.swift */; }; 9E359F4E26CD518D001E25E9 /* LongTaskObserver.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E359F4D26CD518D001E25E9 /* LongTaskObserver.swift */; }; 9E36D92224373EA700BFBDB7 /* SwiftExtensionsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E36D92124373EA700BFBDB7 /* SwiftExtensionsTests.swift */; }; + 9E53889C2773C4B300A7DC42 /* WebRUMEventConsumerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E53889B2773C4B300A7DC42 /* WebRUMEventConsumerTests.swift */; }; 9E544A4F24753C6E00E83072 /* MethodSwizzler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E544A4E24753C6E00E83072 /* MethodSwizzler.swift */; }; 9E544A5124753DDE00E83072 /* MethodSwizzlerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E544A5024753DDE00E83072 /* MethodSwizzlerTests.swift */; }; 9E55407C25812D1C00F6E3AD /* RUMMonitor+objc.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E55407B25812D1C00F6E3AD /* RUMMonitor+objc.swift */; }; @@ -1151,6 +1152,7 @@ 9E2EF44E2694FA14008A7DAE /* VitalInfoSamplerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VitalInfoSamplerTests.swift; sourceTree = ""; }; 9E359F4D26CD518D001E25E9 /* LongTaskObserver.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LongTaskObserver.swift; sourceTree = ""; }; 9E36D92124373EA700BFBDB7 /* SwiftExtensionsTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SwiftExtensionsTests.swift; sourceTree = ""; }; + 9E53889B2773C4B300A7DC42 /* WebRUMEventConsumerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WebRUMEventConsumerTests.swift; sourceTree = ""; }; 9E544A4E24753C6E00E83072 /* MethodSwizzler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MethodSwizzler.swift; sourceTree = ""; }; 9E544A5024753DDE00E83072 /* MethodSwizzlerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MethodSwizzlerTests.swift; sourceTree = ""; }; 9E55407B25812D1C00F6E3AD /* RUMMonitor+objc.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "RUMMonitor+objc.swift"; sourceTree = ""; }; @@ -3298,6 +3300,7 @@ 9EB4B86B27510AF90041CD03 /* WebEventBridgeTests.swift */, 9EAF0CF5275A21100044E8CA /* WKUserContentController+DatadogTests.swift */, 9EA8A7F72768A72B007D6FDB /* WebLogEventConsumerTests.swift */, + 9E53889B2773C4B300A7DC42 /* WebRUMEventConsumerTests.swift */, ); path = WebView; sourceTree = ""; @@ -4117,6 +4120,7 @@ 61C5A89D24509C1100DA608C /* DDSpanTests.swift in Sources */, 61B038BA2527257B00518F3C /* URLSessionAutoInstrumentationMocks.swift in Sources */, 61133C672423990D00786299 /* LogConsoleOutputTests.swift in Sources */, + 9E53889C2773C4B300A7DC42 /* WebRUMEventConsumerTests.swift in Sources */, 6114FDEC257659E90084E372 /* FeatureDirectoriesMock.swift in Sources */, 61122EE825B1C92500F9C7F5 /* SpanSanitizerTests.swift in Sources */, 61B5E42526DFAFBC000B0A5F /* DDGlobal+apiTests.m in Sources */, diff --git a/Sources/Datadog/FeaturesIntegration/WebView/WKUserContentController+Datadog.swift b/Sources/Datadog/FeaturesIntegration/WebView/WKUserContentController+Datadog.swift index 7486fa7dad..d163669268 100644 --- a/Sources/Datadog/FeaturesIntegration/WebView/WKUserContentController+Datadog.swift +++ b/Sources/Datadog/FeaturesIntegration/WebView/WKUserContentController+Datadog.swift @@ -33,7 +33,6 @@ public extension WKUserContentController { rumEventConsumer = WebRUMEventConsumer( dataWriter: rumFeature.storage.writer, dateCorrector: rumFeature.dateCorrector, - webRUMEventMapper: WebRUMEventMapper(), contextProvider: contextProvider ) } diff --git a/Sources/Datadog/FeaturesIntegration/WebView/WebRUMEventConsumer.swift b/Sources/Datadog/FeaturesIntegration/WebView/WebRUMEventConsumer.swift index 2f1a1e9576..5fda40e0b9 100644 --- a/Sources/Datadog/FeaturesIntegration/WebView/WebRUMEventConsumer.swift +++ b/Sources/Datadog/FeaturesIntegration/WebView/WebRUMEventConsumer.swift @@ -6,24 +6,18 @@ import Foundation -// TODO: RUMM-1786 implement mappers -internal class WebRUMEventMapper { } - internal class WebRUMEventConsumer: WebEventConsumer { private let dataWriter: Writer private let dateCorrector: DateCorrectorType - private let webRUMEventMapper: WebRUMEventMapper? private let contextProvider: RUMContextProvider? init( dataWriter: Writer, dateCorrector: DateCorrectorType, - webRUMEventMapper: WebRUMEventMapper?, contextProvider: RUMContextProvider? ) { self.dataWriter = dataWriter self.dateCorrector = dateCorrector - self.webRUMEventMapper = webRUMEventMapper self.contextProvider = contextProvider } @@ -59,12 +53,8 @@ internal class WebRUMEventConsumer: WebEventConsumer { } private func mapIfNeeded(dataModel: T, context: RUMContext?, offset: Offset) -> T { - guard let context = context else { - return dataModel - } - // TODO: RUMM-1786 implement mappers - let mappedDataModel = dataModel - return mappedDataModel + // TODO: RUMM-1786 implement mutating session_id & application_id + return dataModel } private func write(_ model: T) { diff --git a/Tests/DatadogTests/Datadog/RUM/WebView/WebLogEventConsumerTests.swift b/Tests/DatadogTests/Datadog/RUM/WebView/WebLogEventConsumerTests.swift index 080f9b7eca..d2c8572312 100644 --- a/Tests/DatadogTests/Datadog/RUM/WebView/WebLogEventConsumerTests.swift +++ b/Tests/DatadogTests/Datadog/RUM/WebView/WebLogEventConsumerTests.swift @@ -129,13 +129,15 @@ class WebLogEventConsumerTests: XCTestCase { "status": "error", "view": ["referrer": "", "url": "https://datadoghq.dev/browser-sdk-test-playground"] ] + var expectedWebLogEvent: JSON = webLogEvent + expectedWebLogEvent["ddtags"] = "version:,env:" try eventConsumer.consume(event: webLogEvent, eventType: "log") let data = try JSONEncoder().encode(mockUserLogsWriter.dataWritten as? CodableValue) let writtenJSON = try XCTUnwrap(try JSONSerialization.jsonObject(with: data, options: []) as? JSON) - AssertDictionariesEqual(writtenJSON, webLogEvent) + AssertDictionariesEqual(writtenJSON, expectedWebLogEvent) XCTAssertNil(mockInternalLogsWriter.dataWritten) } diff --git a/Tests/DatadogTests/Datadog/RUM/WebView/WebRUMEventConsumerTests.swift b/Tests/DatadogTests/Datadog/RUM/WebView/WebRUMEventConsumerTests.swift new file mode 100644 index 0000000000..8357572f08 --- /dev/null +++ b/Tests/DatadogTests/Datadog/RUM/WebView/WebRUMEventConsumerTests.swift @@ -0,0 +1,219 @@ +/* + * Unless explicitly stated otherwise all files in this repository are licensed under the Apache License Version 2.0. + * This product includes software developed at Datadog (https://www.datadoghq.com/). + * Copyright 2019-2020 Datadog, Inc. + */ + +import XCTest +@testable import Datadog + +// TODO: RUMM-1786 test mutations (session_id, application_id, date) +class WebRUMEventConsumerTests: XCTestCase { + let mockWriter = FileWriterMock() + let mockDateCorrector = DateCorrectorMock() + let mockContextProvider = RUMContextProviderMock(context: .mockWith(rumApplicationID: "123456")) + + private func buildWebRUMViewEvent() -> JSON { + return [ + "application": ["id": "xxx"], + "date": 1_635_933_113_708, + "service": "super", + "session": ["id": "0110cab4-7471-480e-aa4e-7ce039ced355", "type": "user"], + "type": "view", + "view": [ + "action": ["count": 0], + "cumulative_layout_shift": 0, + "dom_complete": 152_800_000, + "dom_content_loaded": 118_300_000, + "dom_interactive": 116_400_000, + "error": ["count": 0], + "first_contentful_paint": 121_300_000, + "id": "64308fd4-83f9-48cb-b3e1-1e91f6721230", + "in_foreground_periods": [], + "is_active": true, + "largest_contentful_paint": 121_299_000, + "load_event": 152_800_000, + "loading_time": 152_800_000, + "loading_type": "initial_load", + "long_task": ["count": 0], + "referrer": "", + "resource": ["count": 3], + "time_spent": 3_120_000_000, + "url": "http://localhost:8080/test.html" + ], + "_dd": [ + "document_version": 2, + "drift": 0, + "format_version": 2, + "session": ["plan": 2] + ] + ] + } + + func testWhenValidWebRUMViewEventPassedWithWrongEventType_itThrowsError() throws { + let mockSessionID = UUID(uuidString: "e9796469-c2a1-43d6-b0f6-65c47d33cf5f")! + mockContextProvider.context.sessionID = RUMUUID(rawValue: mockSessionID) + mockDateCorrector.correctionOffset = 123 + let eventConsumer = WebRUMEventConsumer(dataWriter: mockWriter, dateCorrector: mockDateCorrector, contextProvider: mockContextProvider) + + let webRUMViewEvent = buildWebRUMViewEvent() + + XCTAssertThrowsError(try eventConsumer.consume(event: webRUMViewEvent, eventType: "action")) + } + + func testWhenValidWebRUMViewEventPassed_itDecoratesAndPassesToWriter() throws { + let mockSessionID = UUID(uuidString: "e9796469-c2a1-43d6-b0f6-65c47d33cf5f")! + mockContextProvider.context.sessionID = RUMUUID(rawValue: mockSessionID) + mockDateCorrector.correctionOffset = 123 + let eventConsumer = WebRUMEventConsumer(dataWriter: mockWriter, dateCorrector: mockDateCorrector, contextProvider: mockContextProvider) + + let webRUMViewEvent = buildWebRUMViewEvent() + try eventConsumer.consume(event: webRUMViewEvent, eventType: "view") + + let writtenRUMViewEvent = try XCTUnwrap(mockWriter.dataWritten as? RUMViewEvent) + XCTAssertEqual(writtenRUMViewEvent.view.id, "64308fd4-83f9-48cb-b3e1-1e91f6721230") + XCTAssertEqual(writtenRUMViewEvent.view.loadingTime, 152_800_000) + } + + func testWhenValidWebRUMActionEventPassed_itDecoratesAndPassesToWriter() throws { + let mockSessionID = UUID(uuidString: "e9796469-c2a1-43d6-b0f6-65c47d33cf5f")! + mockContextProvider.context.sessionID = RUMUUID(rawValue: mockSessionID) + mockDateCorrector.correctionOffset = 123 + let eventConsumer = WebRUMEventConsumer(dataWriter: mockWriter, dateCorrector: mockDateCorrector, contextProvider: mockContextProvider) + + let webRUMActionEvent: JSON = [ + "_dd": [ + "format_version": 2, + "drift": 1, + "session": ["plan": 2], + "browser_sdk_version": "3.10.1" + ], + "application": ["id": "75d50c62-8b66-403c-a453-aaa1c44d64bd"], + "date": 1_640_252_823_292, + "service": "shopist-web-ui", + "session": ["id": "00000000-aaaa-0000-aaaa-000000000000", "type": "user", "has_replay": true], + "view": [ + "url": "https://foo.bar/department/chairs/product/2", + "referrer": "https://foo.bar/department/chairs", + "id": "00413060-599f-4a77-80de-5d3beab3da2e", + "in_foreground": true + ], + "action": [ + "id": "e73c32c2-e748-4873-b621-debd7f674c0d", + "target": ["name": "ADD TO CART"], + "type": "click", + "error": ["count": 0], + "loading_time": 5_000_000, + "long_task": ["count": 0], + "resource": ["count": 0] + ], + "type": "action" + ] + + try eventConsumer.consume(event: webRUMActionEvent, eventType: "action") + + let writtenRUMActionEvent = try XCTUnwrap(mockWriter.dataWritten as? RUMActionEvent) + XCTAssertEqual(writtenRUMActionEvent.view.id, "00413060-599f-4a77-80de-5d3beab3da2e") + } + + func testWhenValidWebRUMResourceEventPassed_itDecoratesAndPassesToWriter() throws { + let mockSessionID = UUID(uuidString: "e9796469-c2a1-43d6-b0f6-65c47d33cf5f")! + mockContextProvider.context.sessionID = RUMUUID(rawValue: mockSessionID) + mockDateCorrector.correctionOffset = 123 + let eventConsumer = WebRUMEventConsumer(dataWriter: mockWriter, dateCorrector: mockDateCorrector, contextProvider: mockContextProvider) + + let webRUMResourceEvent: JSON = [ + "_dd": [ + "format_version": 2, + "drift": 0, + "session": ["plan": 2], + "browser_sdk_version": "3.10.1" + ], + "application": ["id": "75d50c62-8b66-403c-a453-aaa1c44d64bd"], + "date": 1_640_252_561_077, + "service": "shopist-web-ui", + "session": [ + "id": "00000000-aaaa-0000-aaaa-000000000000", + "type": "user", + "has_replay": true + ], + "view": [ + "url": "https://foo.bar/", + "referrer": "", + "id": "2aac5419-a626-4098-b1b5-39154f8ea8f3" + ], + "resource": [ + "id": "6df22efd-78b4-454f-b44f-3ac8846e5311", + "type": "image", + "url": "https://foo.bar/_nuxt/img/bedding.8af1600.jpg", + "duration": 369_000_000, + "download": ["duration": 351_000_000, "start": 18_000_000], + "first_byte": ["duration": 17_000_000, "start": 1_000_000] + ], + "type": "resource" + ] + + try eventConsumer.consume(event: webRUMResourceEvent, eventType: "resource") + + let writtenRUMResourceEvent = try XCTUnwrap(mockWriter.dataWritten as? RUMResourceEvent) + XCTAssertEqual(writtenRUMResourceEvent.view.id, "2aac5419-a626-4098-b1b5-39154f8ea8f3") + } + + func testWhenValidWebRUMErrorEventPassed_itDecoratesAndPassesToWriter() throws { + let mockSessionID = UUID(uuidString: "e9796469-c2a1-43d6-b0f6-65c47d33cf5f")! + mockContextProvider.context.sessionID = RUMUUID(rawValue: mockSessionID) + mockDateCorrector.correctionOffset = 123 + let eventConsumer = WebRUMEventConsumer(dataWriter: mockWriter, dateCorrector: mockDateCorrector, contextProvider: mockContextProvider) + + let webRUMErrorEvent: JSON = [ + "_dd": [ + "format_version": 2, + "drift": 0, + "session": ["plan": 2], + "browser_sdk_version": "3.10.1" + ], + "application": ["id": "75d50c62-8b66-403c-a453-aaa1c44d64bd"], + "date": 1_640_252_666_129, + "service": "shopist-web-ui", + "session": [ + "id": "00000000-aaaa-0000-aaaa-000000000000", + "type": "user", + "has_replay": true + ], + "view": [ + "url": "https://foo.bar/department/chairs/product/2", + "referrer": "https://foo.bar/department/chairs", + "id": "00413060-599f-4a77-80de-5d3beab3da2e", + "in_foreground": true + ], + "error": [ + "id": "3de88670-be12-4a30-91c8-378f8ccb8a75", + "message": "Provided [\"type\":\"network error\",\"status\":404]", + "source": "custom", + "stack": "No stack, consider using an instance of Error", + "handling_stack": "Error: \n at @ https://foo.bar/_nuxt/app.30bb4c9.js:2:47460\n at promiseReactionJob @ [native code]", + "handling": "handled" + ], + "type": "error" + ] + + try eventConsumer.consume(event: webRUMErrorEvent, eventType: "error") + + let writtenRUMErrorEvent = try XCTUnwrap(mockWriter.dataWritten as? RUMErrorEvent) + XCTAssertEqual(writtenRUMErrorEvent.view.id, "00413060-599f-4a77-80de-5d3beab3da2e") + } + + func testWhenInvalidEventTypeIsPassed_itLogsToUserLogger() throws { + let previousUserLogger = userLogger + defer { userLogger = previousUserLogger } + let output = LogOutputMock() + userLogger = .mockWith(logOutput: output) + + let eventConsumer = WebRUMEventConsumer(dataWriter: mockWriter, dateCorrector: mockDateCorrector, contextProvider: mockContextProvider) + try eventConsumer.consume(event: [:], eventType: "unknown_event_type") + + XCTAssertEqual(output.recordedLog?.status, .error) + let userLogMessage = try XCTUnwrap(output.recordedLog?.message) + XCTAssertEqual(userLogMessage, "🔥 Web RUM Event Error - Unknown event type: unknown_event_type") + } +} From e10f099f97e0fb64987d42d71466fa12f7f6b2e2 Mon Sep 17 00:00:00 2001 From: Mert Buran Date: Tue, 28 Dec 2021 14:55:20 +0100 Subject: [PATCH 07/10] RUMM-1791 PR comments addressed --- .../WKUserContentController+Datadog.swift | 4 ++- .../WebView/WebLogEventConsumer.swift | 15 ++++++-- .../WebView/WebLogEventConsumerTests.swift | 34 +++++++++++++++---- 3 files changed, 42 insertions(+), 11 deletions(-) diff --git a/Sources/Datadog/FeaturesIntegration/WebView/WKUserContentController+Datadog.swift b/Sources/Datadog/FeaturesIntegration/WebView/WKUserContentController+Datadog.swift index d163669268..cc8ed0531b 100644 --- a/Sources/Datadog/FeaturesIntegration/WebView/WKUserContentController+Datadog.swift +++ b/Sources/Datadog/FeaturesIntegration/WebView/WKUserContentController+Datadog.swift @@ -24,7 +24,9 @@ public extension WKUserContentController { userLogsWriter: loggingFeature.storage.writer, internalLogsWriter: InternalMonitoringFeature.instance?.logsStorage.writer, dateCorrector: loggingFeature.dateCorrector, - rumContextProvider: contextProvider + rumContextProvider: contextProvider, + applicationVersion: loggingFeature.configuration.common.applicationVersion, + environment: loggingFeature.configuration.common.environment ) } diff --git a/Sources/Datadog/FeaturesIntegration/WebView/WebLogEventConsumer.swift b/Sources/Datadog/FeaturesIntegration/WebView/WebLogEventConsumer.swift index 0c3839f292..ea6213dc49 100644 --- a/Sources/Datadog/FeaturesIntegration/WebView/WebLogEventConsumer.swift +++ b/Sources/Datadog/FeaturesIntegration/WebView/WebLogEventConsumer.swift @@ -21,13 +21,16 @@ internal class WebLogEventConsumer: WebEventConsumer { private let internalLogsWriter: Writer? private let dateCorrector: DateCorrectorType private let rumContextProvider: RUMContextProvider? + private let applicationVersion: String + private let environment: String + private let jsonDecoder = JSONDecoder() private lazy var ddTags: String = { let versionKey = LogEventEncoder.StaticCodingKeys.applicationVersion.rawValue - let versionValue = LoggingFeature.instance?.configuration.common.applicationVersion ?? "" + let versionValue = applicationVersion let envKey = LogEventEncoder.StaticCodingKeys.environment.rawValue - let envValue = LoggingFeature.instance?.configuration.common.environment ?? "" + let envValue = environment return "\(versionKey):\(versionValue),\(envKey):\(envValue)" }() @@ -36,12 +39,16 @@ internal class WebLogEventConsumer: WebEventConsumer { userLogsWriter: Writer, internalLogsWriter: Writer?, dateCorrector: DateCorrectorType, - rumContextProvider: RUMContextProvider? + rumContextProvider: RUMContextProvider?, + applicationVersion: String, + environment: String ) { self.userLogsWriter = userLogsWriter self.internalLogsWriter = internalLogsWriter self.dateCorrector = dateCorrector self.rumContextProvider = rumContextProvider + self.applicationVersion = applicationVersion + self.environment = environment } func consume(event: JSON, eventType: String) throws { @@ -71,6 +78,8 @@ internal class WebLogEventConsumer: WebEventConsumer { userLogsWriter.write(value: encodableEvent) } else if eventType == Constants.internalLogEventType { internalLogsWriter?.write(value: encodableEvent) + } else { + userLogger.error("🔥 Invalid Web Event Type: \(eventType)") } } } diff --git a/Tests/DatadogTests/Datadog/RUM/WebView/WebLogEventConsumerTests.swift b/Tests/DatadogTests/Datadog/RUM/WebView/WebLogEventConsumerTests.swift index d2c8572312..006ee76704 100644 --- a/Tests/DatadogTests/Datadog/RUM/WebView/WebLogEventConsumerTests.swift +++ b/Tests/DatadogTests/Datadog/RUM/WebView/WebLogEventConsumerTests.swift @@ -17,11 +17,15 @@ class WebLogEventConsumerTests: XCTestCase { let mockSessionID = UUID(uuidString: "e9796469-c2a1-43d6-b0f6-65c47d33cf5f")! mockContextProvider.context.sessionID = RUMUUID(rawValue: mockSessionID) mockDateCorrector.correctionOffset = 123 + let applicationVersion = String.mockRandom() + let environment = String.mockRandom() let eventConsumer = WebLogEventConsumer( userLogsWriter: mockUserLogsWriter, internalLogsWriter: mockInternalLogsWriter, dateCorrector: mockDateCorrector, - rumContextProvider: mockContextProvider + rumContextProvider: mockContextProvider, + applicationVersion: applicationVersion, + environment: environment ) let webLogEvent: JSON = [ @@ -39,7 +43,7 @@ class WebLogEventConsumerTests: XCTestCase { "application_id": "123456", "session_id": mockSessionID.uuidString.lowercased(), "status": "error", - "ddtags": "version:,env:", + "ddtags": "version:\(applicationVersion),env:\(environment)", "view": ["referrer": "", "url": "https://datadoghq.dev/browser-sdk-test-playground"] ] @@ -57,11 +61,15 @@ class WebLogEventConsumerTests: XCTestCase { let mockSessionID = UUID(uuidString: "e9796469-c2a1-43d6-b0f6-65c47d33cf5f")! mockContextProvider.context.sessionID = RUMUUID(rawValue: mockSessionID) mockDateCorrector.correctionOffset = 123 + let applicationVersion = String.mockRandom() + let environment = String.mockRandom() let eventConsumer = WebLogEventConsumer( userLogsWriter: mockUserLogsWriter, internalLogsWriter: mockInternalLogsWriter, dateCorrector: mockDateCorrector, - rumContextProvider: mockContextProvider + rumContextProvider: mockContextProvider, + applicationVersion: applicationVersion, + environment: environment ) let webLogEvent: JSON = [ @@ -79,7 +87,7 @@ class WebLogEventConsumerTests: XCTestCase { "application_id": "123456", "session_id": mockSessionID.uuidString.lowercased(), "status": "error", - "ddtags": "version:,env:", + "ddtags": "version:\(applicationVersion),env:\(environment)", "view": ["referrer": "", "url": "https://datadoghq.dev/browser-sdk-test-playground"] ] @@ -94,11 +102,18 @@ class WebLogEventConsumerTests: XCTestCase { } func testWhenInvalidEventTypePassed_itIgnoresEvent() throws { + let previousUserLogger = userLogger + defer { userLogger = previousUserLogger } + let userLoggerOutput = LogOutputMock() + userLogger = .mockWith(logOutput: userLoggerOutput) + let eventConsumer = WebLogEventConsumer( userLogsWriter: mockUserLogsWriter, internalLogsWriter: mockInternalLogsWriter, dateCorrector: mockDateCorrector, - rumContextProvider: mockContextProvider + rumContextProvider: mockContextProvider, + applicationVersion: .mockRandom(), + environment: .mockRandom() ) let webLogEvent: JSON = [ @@ -111,14 +126,19 @@ class WebLogEventConsumerTests: XCTestCase { XCTAssertNil(mockUserLogsWriter.dataWritten) XCTAssertNil(mockInternalLogsWriter.dataWritten) + XCTAssertEqual(userLoggerOutput.recordedLog?.message, "🔥 Invalid Web Event Type: invalid_log") } func testWhenContextIsUnavailable_itPassesEventAsIs() throws { + let applicationVersion = String.mockRandom() + let environment = String.mockRandom() let eventConsumer = WebLogEventConsumer( userLogsWriter: mockUserLogsWriter, internalLogsWriter: mockInternalLogsWriter, dateCorrector: mockDateCorrector, - rumContextProvider: nil + rumContextProvider: nil, + applicationVersion: applicationVersion, + environment: environment ) let webLogEvent: JSON = [ @@ -130,7 +150,7 @@ class WebLogEventConsumerTests: XCTestCase { "view": ["referrer": "", "url": "https://datadoghq.dev/browser-sdk-test-playground"] ] var expectedWebLogEvent: JSON = webLogEvent - expectedWebLogEvent["ddtags"] = "version:,env:" + expectedWebLogEvent["ddtags"] = "version:\(applicationVersion),env:\(environment)" try eventConsumer.consume(event: webLogEvent, eventType: "log") From a05bc9a96371f5f951f1a9756fcdde52a1f47ceb Mon Sep 17 00:00:00 2001 From: Mert Buran Date: Wed, 29 Dec 2021 16:05:20 +0100 Subject: [PATCH 08/10] RUMM-1791 WebRUMEvents manipulated as JSON --- .../WebView/WebEventBridge.swift | 16 +++- .../WebView/WebRUMEventConsumer.swift | 83 +++++++++++-------- 2 files changed, 60 insertions(+), 39 deletions(-) diff --git a/Sources/Datadog/FeaturesIntegration/WebView/WebEventBridge.swift b/Sources/Datadog/FeaturesIntegration/WebView/WebEventBridge.swift index 994f153ec7..e75a752491 100644 --- a/Sources/Datadog/FeaturesIntegration/WebView/WebEventBridge.swift +++ b/Sources/Datadog/FeaturesIntegration/WebView/WebEventBridge.swift @@ -24,6 +24,7 @@ internal class WebEventBridge { static let eventTypeKey = "eventType" static let eventKey = "event" static let eventTypeLog = "log" + static let eventTypeInternalLog = "internal_log" } private let logEventConsumer: WebEventConsumer? @@ -46,10 +47,19 @@ internal class WebEventBridge { throw WebEventError.missingKey(key: Constants.eventKey) } - if eventType == Constants.eventTypeLog { - try logEventConsumer?.consume(event: wrappedEvent, eventType: eventType) + if eventType == Constants.eventTypeLog || + eventType == Constants.eventTypeInternalLog { + if let consumer = logEventConsumer { + try consumer.consume(event: wrappedEvent, eventType: eventType) + } else { + userLogger.warn("A WebView log is lost because Logging is disabled in iOS SDK") + } } else { - try rumEventConsumer?.consume(event: wrappedEvent, eventType: eventType) + if let consumer = rumEventConsumer { + try consumer.consume(event: wrappedEvent, eventType: eventType) + } else { + userLogger.warn("A WebView RUM event is lost because RUM is disabled in iOS SDK") + } } } diff --git a/Sources/Datadog/FeaturesIntegration/WebView/WebRUMEventConsumer.swift b/Sources/Datadog/FeaturesIntegration/WebView/WebRUMEventConsumer.swift index 5fda40e0b9..7530cd1ca2 100644 --- a/Sources/Datadog/FeaturesIntegration/WebView/WebRUMEventConsumer.swift +++ b/Sources/Datadog/FeaturesIntegration/WebView/WebRUMEventConsumer.swift @@ -11,6 +11,8 @@ internal class WebRUMEventConsumer: WebEventConsumer { private let dateCorrector: DateCorrectorType private let contextProvider: RUMContextProvider? + private let jsonDecoder = JSONDecoder() + init( dataWriter: Writer, dateCorrector: DateCorrectorType, @@ -22,59 +24,68 @@ internal class WebRUMEventConsumer: WebEventConsumer { } func consume(event: JSON, eventType: String) throws { - let eventData = try JSONSerialization.data(withJSONObject: event, options: []) - let jsonDecoder = JSONDecoder() let rumContext = contextProvider?.context + let mappedEvent = map(event: event, with: rumContext) - switch eventType { - case "view": - let viewEvent = try jsonDecoder.decode(RUMViewEvent.self, from: eventData) - let mappedViewEvent = mapIfNeeded(dataModel: viewEvent, context: rumContext, offset: getOffset(viewID: viewEvent.view.id)) - write(mappedViewEvent) - case "action": - let actionEvent = try jsonDecoder.decode(RUMActionEvent.self, from: eventData) - let mappedActionEvent = mapIfNeeded(dataModel: actionEvent, context: rumContext, offset: getOffset(viewID: actionEvent.view.id)) - write(mappedActionEvent) - case "resource": - let resourceEvent = try jsonDecoder.decode(RUMResourceEvent.self, from: eventData) - let mappedResourceEvent = mapIfNeeded(dataModel: resourceEvent, context: rumContext, offset: getOffset(viewID: resourceEvent.view.id)) - write(mappedResourceEvent) - case "error": - let errorEvent = try jsonDecoder.decode(RUMErrorEvent.self, from: eventData) - let mappedErrorEvent = mapIfNeeded(dataModel: errorEvent, context: rumContext, offset: getOffset(viewID: errorEvent.view.id)) - write(mappedErrorEvent) - case "long_task": - let longTaskEvent = try jsonDecoder.decode(RUMLongTaskEvent.self, from: eventData) - let mappedLongTaskEvent = mapIfNeeded(dataModel: longTaskEvent, context: rumContext, offset: getOffset(viewID: longTaskEvent.view.id)) - write(mappedLongTaskEvent) - default: - userLogger.error("🔥 Web RUM Event Error - Unknown event type: \(eventType)") - } - } + let jsonData = try JSONSerialization.data(withJSONObject: mappedEvent, options: []) + let encodableEvent = try jsonDecoder.decode(CodableValue.self, from: jsonData) - private func mapIfNeeded(dataModel: T, context: RUMContext?, offset: Offset) -> T { - // TODO: RUMM-1786 implement mutating session_id & application_id - return dataModel + dataWriter.write(value: encodableEvent) } - private func write(_ model: T) { - dataWriter.write(value: model) + private func map(event: JSON, with context: RUMContext?) -> JSON { + guard let context = context, + context.sessionID != .nullUUID else { + return event + } + + var mutableEvent = event + + if let date = mutableEvent["date"] as? Int { + let viewID = (mutableEvent["view"] as? JSON)?["id"] as? String + let serverTimeOffsetInNs = getOffset(viewID: viewID) + let correctedDate = Int64(date) + serverTimeOffsetInNs + mutableEvent["date"] = correctedDate + } + + if let context = contextProvider?.context { + if var application = mutableEvent["application"] as? JSON { + application["id"] = context.rumApplicationID + mutableEvent["application"] = application + } + if var session = mutableEvent["session"] as? JSON { + session["id"] = context.sessionID.toRUMDataFormat + mutableEvent["session"] = session + } + } + + if var dd = mutableEvent["_dd"] as? JSON, + var dd_sesion = dd["session"] as? [String: Int] { + dd_sesion["plan"] = 1 + dd["session"] = dd_sesion + mutableEvent["_dd"] = dd + } + + return mutableEvent } // MARK: - Time offsets - private typealias Offset = TimeInterval + private typealias Offset = Int64 private typealias ViewIDOffsetPair = (viewID: String, offset: Offset) private var viewIDOffsetPairs = [ViewIDOffsetPair]() - private func getOffset(viewID: String) -> Offset { - purgeOffsets() + private func getOffset(viewID: String?) -> Offset { + guard let viewID = viewID else { + return 0 + } + purgeOffsets() let found = viewIDOffsetPairs.first { $0.viewID == viewID } if let found = found { return found.offset } - let offset = dateCorrector.currentCorrection.serverTimeOffset + let offset = dateCorrector.currentCorrection.serverTimeOffset.toInt64Nanoseconds viewIDOffsetPairs.insert((viewID: viewID, offset: offset), at: 0) return offset } From 29bf40b7fb3136654a3164551509f8bc6914fa72 Mon Sep 17 00:00:00 2001 From: Mert Buran Date: Thu, 30 Dec 2021 13:40:00 +0100 Subject: [PATCH 09/10] RUMM-1791 WebRUMEventConsumer made more flexible WebRUMEvents are decorated as raw JSONs instead of serializing them into RUMDataModels --- .../WKUserContentController+Datadog.swift | 8 +- .../WebView/WebEventBridge.swift | 21 +- .../WebView/WebLogEventConsumer.swift | 10 +- .../WebView/WebRUMEventConsumer.swift | 4 +- .../RUM/WebView/WebEventBridgeTests.swift | 47 ++-- .../WebView/WebLogEventConsumerTests.swift | 40 +--- .../WebView/WebRUMEventConsumerTests.swift | 216 +++++------------- 7 files changed, 112 insertions(+), 234 deletions(-) diff --git a/Sources/Datadog/FeaturesIntegration/WebView/WKUserContentController+Datadog.swift b/Sources/Datadog/FeaturesIntegration/WebView/WKUserContentController+Datadog.swift index cc8ed0531b..8d9a188f3a 100644 --- a/Sources/Datadog/FeaturesIntegration/WebView/WKUserContentController+Datadog.swift +++ b/Sources/Datadog/FeaturesIntegration/WebView/WKUserContentController+Datadog.swift @@ -18,9 +18,9 @@ public extension WKUserContentController { let contextProvider = (Global.rum as? RUMMonitor)?.contextProvider - var logEventConsumer: WebLogEventConsumer? = nil + var logEventConsumer: DefaultWebLogEventConsumer? = nil if let loggingFeature = LoggingFeature.instance { - logEventConsumer = WebLogEventConsumer( + logEventConsumer = DefaultWebLogEventConsumer( userLogsWriter: loggingFeature.storage.writer, internalLogsWriter: InternalMonitoringFeature.instance?.logsStorage.writer, dateCorrector: loggingFeature.dateCorrector, @@ -30,9 +30,9 @@ public extension WKUserContentController { ) } - var rumEventConsumer: WebRUMEventConsumer? = nil + var rumEventConsumer: DefaultWebRUMEventConsumer? = nil if let rumFeature = RUMFeature.instance { - rumEventConsumer = WebRUMEventConsumer( + rumEventConsumer = DefaultWebRUMEventConsumer( dataWriter: rumFeature.storage.writer, dateCorrector: rumFeature.dateCorrector, contextProvider: contextProvider diff --git a/Sources/Datadog/FeaturesIntegration/WebView/WebEventBridge.swift b/Sources/Datadog/FeaturesIntegration/WebView/WebEventBridge.swift index e75a752491..6be1dd93a0 100644 --- a/Sources/Datadog/FeaturesIntegration/WebView/WebEventBridge.swift +++ b/Sources/Datadog/FeaturesIntegration/WebView/WebEventBridge.swift @@ -8,8 +8,12 @@ import Foundation internal typealias JSON = [String: Any] -internal protocol WebEventConsumer { - func consume(event: JSON, eventType: String) throws +internal protocol WebLogEventConsumer { + func consume(event: JSON, internalLog: Bool) throws +} + +internal protocol WebRUMEventConsumer { + func consume(event: JSON) throws } internal enum WebEventError: Error, Equatable { @@ -27,10 +31,10 @@ internal class WebEventBridge { static let eventTypeInternalLog = "internal_log" } - private let logEventConsumer: WebEventConsumer? - private let rumEventConsumer: WebEventConsumer? + private let logEventConsumer: WebLogEventConsumer? + private let rumEventConsumer: WebRUMEventConsumer? - init(logEventConsumer: WebEventConsumer?, rumEventConsumer: WebEventConsumer?) { + init(logEventConsumer: WebLogEventConsumer?, rumEventConsumer: WebRUMEventConsumer?) { self.logEventConsumer = logEventConsumer self.rumEventConsumer = rumEventConsumer } @@ -50,13 +54,16 @@ internal class WebEventBridge { if eventType == Constants.eventTypeLog || eventType == Constants.eventTypeInternalLog { if let consumer = logEventConsumer { - try consumer.consume(event: wrappedEvent, eventType: eventType) + try consumer.consume( + event: wrappedEvent, + internalLog: (eventType == Constants.eventTypeInternalLog) + ) } else { userLogger.warn("A WebView log is lost because Logging is disabled in iOS SDK") } } else { if let consumer = rumEventConsumer { - try consumer.consume(event: wrappedEvent, eventType: eventType) + try consumer.consume(event: wrappedEvent) } else { userLogger.warn("A WebView RUM event is lost because RUM is disabled in iOS SDK") } diff --git a/Sources/Datadog/FeaturesIntegration/WebView/WebLogEventConsumer.swift b/Sources/Datadog/FeaturesIntegration/WebView/WebLogEventConsumer.swift index ea6213dc49..53902f06e0 100644 --- a/Sources/Datadog/FeaturesIntegration/WebView/WebLogEventConsumer.swift +++ b/Sources/Datadog/FeaturesIntegration/WebView/WebLogEventConsumer.swift @@ -6,7 +6,7 @@ import Foundation -internal class WebLogEventConsumer: WebEventConsumer { +internal class DefaultWebLogEventConsumer: WebLogEventConsumer { private struct Constants { static let logEventType = "log" static let internalLogEventType = "internal_log" @@ -51,7 +51,7 @@ internal class WebLogEventConsumer: WebEventConsumer { self.environment = environment } - func consume(event: JSON, eventType: String) throws { + func consume(event: JSON, internalLog: Bool) throws { var mutableEvent = event if let existingTags = mutableEvent[Constants.ddTagsKey] as? String, !existingTags.isEmpty { @@ -74,12 +74,10 @@ internal class WebLogEventConsumer: WebEventConsumer { let jsonData = try JSONSerialization.data(withJSONObject: mutableEvent, options: []) let encodableEvent = try jsonDecoder.decode(CodableValue.self, from: jsonData) - if eventType == Constants.logEventType { - userLogsWriter.write(value: encodableEvent) - } else if eventType == Constants.internalLogEventType { + if internalLog { internalLogsWriter?.write(value: encodableEvent) } else { - userLogger.error("🔥 Invalid Web Event Type: \(eventType)") + userLogsWriter.write(value: encodableEvent) } } } diff --git a/Sources/Datadog/FeaturesIntegration/WebView/WebRUMEventConsumer.swift b/Sources/Datadog/FeaturesIntegration/WebView/WebRUMEventConsumer.swift index 7530cd1ca2..cdfcff6b01 100644 --- a/Sources/Datadog/FeaturesIntegration/WebView/WebRUMEventConsumer.swift +++ b/Sources/Datadog/FeaturesIntegration/WebView/WebRUMEventConsumer.swift @@ -6,7 +6,7 @@ import Foundation -internal class WebRUMEventConsumer: WebEventConsumer { +internal class DefaultWebRUMEventConsumer: WebRUMEventConsumer { private let dataWriter: Writer private let dateCorrector: DateCorrectorType private let contextProvider: RUMContextProvider? @@ -23,7 +23,7 @@ internal class WebRUMEventConsumer: WebEventConsumer { self.contextProvider = contextProvider } - func consume(event: JSON, eventType: String) throws { + func consume(event: JSON) throws { let rumContext = contextProvider?.context let mappedEvent = map(event: event, with: rumContext) diff --git a/Tests/DatadogTests/Datadog/RUM/WebView/WebEventBridgeTests.swift b/Tests/DatadogTests/Datadog/RUM/WebView/WebEventBridgeTests.swift index 3ffde28db5..757f96db3f 100644 --- a/Tests/DatadogTests/Datadog/RUM/WebView/WebEventBridgeTests.swift +++ b/Tests/DatadogTests/Datadog/RUM/WebView/WebEventBridgeTests.swift @@ -7,11 +7,21 @@ import XCTest @testable import Datadog -fileprivate class MockEventConsumer: WebEventConsumer { - private(set) var consumedEvents: [(event: JSON, eventType: String)] = [] +fileprivate class MockEventConsumer: WebLogEventConsumer, WebRUMEventConsumer { + private(set) var consumedLogEvents: [JSON] = [] + private(set) var consumedInternalLogEvents: [JSON] = [] + private(set) var consumedRUMEvents: [JSON] = [] - func consume(event: JSON, eventType: String) { - consumedEvents.append((event: event, eventType: eventType)) + func consume(event: JSON, internalLog: Bool) throws { + if internalLog { + consumedInternalLogEvents.append(event) + } else { + consumedLogEvents.append(event) + } + } + + func consume(event: JSON) throws { + consumedRUMEvents.append(event) } } @@ -58,13 +68,16 @@ class WebEventBridgeTests: XCTestCase { """ try eventBridge.consume(messageLog) - XCTAssertEqual(mockLogEventConsumer.consumedEvents.count, 1) - XCTAssertEqual(mockRUMEventConsumer.consumedEvents.count, 0) + XCTAssertEqual(mockLogEventConsumer.consumedLogEvents.count, 1) + XCTAssertEqual(mockLogEventConsumer.consumedInternalLogEvents.count, 0) + XCTAssertEqual(mockLogEventConsumer.consumedRUMEvents.count, 0) + XCTAssertEqual(mockRUMEventConsumer.consumedLogEvents.count, 0) + XCTAssertEqual(mockRUMEventConsumer.consumedInternalLogEvents.count, 0) + XCTAssertEqual(mockRUMEventConsumer.consumedRUMEvents.count, 0) - let consumedEvent = try XCTUnwrap(mockLogEventConsumer.consumedEvents.first) - XCTAssertEqual(consumedEvent.eventType, "log") - XCTAssertEqual(consumedEvent.event["session_id"] as? String, "0110cab4-7471-480e-aa4e-7ce039ced355") - XCTAssertEqual((consumedEvent.event["view"] as? JSON)?["url"] as? String, "https://datadoghq.dev/browser-sdk-test-playground") + let consumedEvent = try XCTUnwrap(mockLogEventConsumer.consumedLogEvents.first) + XCTAssertEqual(consumedEvent["session_id"] as? String, "0110cab4-7471-480e-aa4e-7ce039ced355") + XCTAssertEqual((consumedEvent["view"] as? JSON)?["url"] as? String, "https://datadoghq.dev/browser-sdk-test-playground") } func testWhenEventTypeIsNonLog_itGoesToRUMEventConsumer() throws { @@ -73,12 +86,14 @@ class WebEventBridgeTests: XCTestCase { """ try eventBridge.consume(messageRUM) - XCTAssertEqual(mockLogEventConsumer.consumedEvents.count, 0) - XCTAssertEqual(mockRUMEventConsumer.consumedEvents.count, 1) + XCTAssertEqual( + mockLogEventConsumer.consumedLogEvents.count + mockLogEventConsumer.consumedInternalLogEvents.count, + 0 + ) + XCTAssertEqual(mockRUMEventConsumer.consumedRUMEvents.count, 1) - let consumedEvent = try XCTUnwrap(mockRUMEventConsumer.consumedEvents.first) - XCTAssertEqual(consumedEvent.eventType, "view") - XCTAssertEqual((consumedEvent.event["session"] as? JSON)?["id"] as? String, "0110cab4-7471-480e-aa4e-7ce039ced355") - XCTAssertEqual((consumedEvent.event["view"] as? JSON)?["url"] as? String, "http://localhost:8080/test.html") + let consumedEvent = try XCTUnwrap(mockRUMEventConsumer.consumedRUMEvents.first) + XCTAssertEqual((consumedEvent["session"] as? JSON)?["id"] as? String, "0110cab4-7471-480e-aa4e-7ce039ced355") + XCTAssertEqual((consumedEvent["view"] as? JSON)?["url"] as? String, "http://localhost:8080/test.html") } } diff --git a/Tests/DatadogTests/Datadog/RUM/WebView/WebLogEventConsumerTests.swift b/Tests/DatadogTests/Datadog/RUM/WebView/WebLogEventConsumerTests.swift index 006ee76704..be1c9ea46e 100644 --- a/Tests/DatadogTests/Datadog/RUM/WebView/WebLogEventConsumerTests.swift +++ b/Tests/DatadogTests/Datadog/RUM/WebView/WebLogEventConsumerTests.swift @@ -19,7 +19,7 @@ class WebLogEventConsumerTests: XCTestCase { mockDateCorrector.correctionOffset = 123 let applicationVersion = String.mockRandom() let environment = String.mockRandom() - let eventConsumer = WebLogEventConsumer( + let eventConsumer = DefaultWebLogEventConsumer( userLogsWriter: mockUserLogsWriter, internalLogsWriter: mockInternalLogsWriter, dateCorrector: mockDateCorrector, @@ -47,7 +47,7 @@ class WebLogEventConsumerTests: XCTestCase { "view": ["referrer": "", "url": "https://datadoghq.dev/browser-sdk-test-playground"] ] - try eventConsumer.consume(event: webLogEvent, eventType: "log") + try eventConsumer.consume(event: webLogEvent, internalLog: false) let data = try JSONEncoder().encode(mockUserLogsWriter.dataWritten as? CodableValue) let writtenJSON = try XCTUnwrap(try JSONSerialization.jsonObject(with: data, options: []) as? JSON) @@ -63,7 +63,7 @@ class WebLogEventConsumerTests: XCTestCase { mockDateCorrector.correctionOffset = 123 let applicationVersion = String.mockRandom() let environment = String.mockRandom() - let eventConsumer = WebLogEventConsumer( + let eventConsumer = DefaultWebLogEventConsumer( userLogsWriter: mockUserLogsWriter, internalLogsWriter: mockInternalLogsWriter, dateCorrector: mockDateCorrector, @@ -91,7 +91,7 @@ class WebLogEventConsumerTests: XCTestCase { "view": ["referrer": "", "url": "https://datadoghq.dev/browser-sdk-test-playground"] ] - try eventConsumer.consume(event: webLogEvent, eventType: "internal_log") + try eventConsumer.consume(event: webLogEvent, internalLog: true) let data = try JSONEncoder().encode(mockInternalLogsWriter.dataWritten as? CodableValue) let writtenJSON = try XCTUnwrap(try JSONSerialization.jsonObject(with: data, options: []) as? JSON) @@ -101,38 +101,10 @@ class WebLogEventConsumerTests: XCTestCase { XCTAssertNil(mockUserLogsWriter.dataWritten) } - func testWhenInvalidEventTypePassed_itIgnoresEvent() throws { - let previousUserLogger = userLogger - defer { userLogger = previousUserLogger } - let userLoggerOutput = LogOutputMock() - userLogger = .mockWith(logOutput: userLoggerOutput) - - let eventConsumer = WebLogEventConsumer( - userLogsWriter: mockUserLogsWriter, - internalLogsWriter: mockInternalLogsWriter, - dateCorrector: mockDateCorrector, - rumContextProvider: mockContextProvider, - applicationVersion: .mockRandom(), - environment: .mockRandom() - ) - - let webLogEvent: JSON = [ - "date": 1_635_932_927_012, - "message": "console error: error", - "status": "error" - ] - - try eventConsumer.consume(event: webLogEvent, eventType: "invalid_log") - - XCTAssertNil(mockUserLogsWriter.dataWritten) - XCTAssertNil(mockInternalLogsWriter.dataWritten) - XCTAssertEqual(userLoggerOutput.recordedLog?.message, "🔥 Invalid Web Event Type: invalid_log") - } - func testWhenContextIsUnavailable_itPassesEventAsIs() throws { let applicationVersion = String.mockRandom() let environment = String.mockRandom() - let eventConsumer = WebLogEventConsumer( + let eventConsumer = DefaultWebLogEventConsumer( userLogsWriter: mockUserLogsWriter, internalLogsWriter: mockInternalLogsWriter, dateCorrector: mockDateCorrector, @@ -152,7 +124,7 @@ class WebLogEventConsumerTests: XCTestCase { var expectedWebLogEvent: JSON = webLogEvent expectedWebLogEvent["ddtags"] = "version:\(applicationVersion),env:\(environment)" - try eventConsumer.consume(event: webLogEvent, eventType: "log") + try eventConsumer.consume(event: webLogEvent, internalLog: false) let data = try JSONEncoder().encode(mockUserLogsWriter.dataWritten as? CodableValue) let writtenJSON = try XCTUnwrap(try JSONSerialization.jsonObject(with: data, options: []) as? JSON) diff --git a/Tests/DatadogTests/Datadog/RUM/WebView/WebRUMEventConsumerTests.swift b/Tests/DatadogTests/Datadog/RUM/WebView/WebRUMEventConsumerTests.swift index 8357572f08..3ab570ddb5 100644 --- a/Tests/DatadogTests/Datadog/RUM/WebView/WebRUMEventConsumerTests.swift +++ b/Tests/DatadogTests/Datadog/RUM/WebView/WebRUMEventConsumerTests.swift @@ -13,207 +13,93 @@ class WebRUMEventConsumerTests: XCTestCase { let mockDateCorrector = DateCorrectorMock() let mockContextProvider = RUMContextProviderMock(context: .mockWith(rumApplicationID: "123456")) - private func buildWebRUMViewEvent() -> JSON { - return [ - "application": ["id": "xxx"], - "date": 1_635_933_113_708, - "service": "super", - "session": ["id": "0110cab4-7471-480e-aa4e-7ce039ced355", "type": "user"], - "type": "view", - "view": [ - "action": ["count": 0], - "cumulative_layout_shift": 0, - "dom_complete": 152_800_000, - "dom_content_loaded": 118_300_000, - "dom_interactive": 116_400_000, - "error": ["count": 0], - "first_contentful_paint": 121_300_000, - "id": "64308fd4-83f9-48cb-b3e1-1e91f6721230", - "in_foreground_periods": [], - "is_active": true, - "largest_contentful_paint": 121_299_000, - "load_event": 152_800_000, - "loading_time": 152_800_000, - "loading_type": "initial_load", - "long_task": ["count": 0], - "referrer": "", - "resource": ["count": 3], - "time_spent": 3_120_000_000, - "url": "http://localhost:8080/test.html" - ], - "_dd": [ - "document_version": 2, - "drift": 0, - "format_version": 2, - "session": ["plan": 2] - ] - ] - } - - func testWhenValidWebRUMViewEventPassedWithWrongEventType_itThrowsError() throws { - let mockSessionID = UUID(uuidString: "e9796469-c2a1-43d6-b0f6-65c47d33cf5f")! - mockContextProvider.context.sessionID = RUMUUID(rawValue: mockSessionID) - mockDateCorrector.correctionOffset = 123 - let eventConsumer = WebRUMEventConsumer(dataWriter: mockWriter, dateCorrector: mockDateCorrector, contextProvider: mockContextProvider) - - let webRUMViewEvent = buildWebRUMViewEvent() - - XCTAssertThrowsError(try eventConsumer.consume(event: webRUMViewEvent, eventType: "action")) - } - - func testWhenValidWebRUMViewEventPassed_itDecoratesAndPassesToWriter() throws { - let mockSessionID = UUID(uuidString: "e9796469-c2a1-43d6-b0f6-65c47d33cf5f")! - mockContextProvider.context.sessionID = RUMUUID(rawValue: mockSessionID) - mockDateCorrector.correctionOffset = 123 - let eventConsumer = WebRUMEventConsumer(dataWriter: mockWriter, dateCorrector: mockDateCorrector, contextProvider: mockContextProvider) - - let webRUMViewEvent = buildWebRUMViewEvent() - try eventConsumer.consume(event: webRUMViewEvent, eventType: "view") - - let writtenRUMViewEvent = try XCTUnwrap(mockWriter.dataWritten as? RUMViewEvent) - XCTAssertEqual(writtenRUMViewEvent.view.id, "64308fd4-83f9-48cb-b3e1-1e91f6721230") - XCTAssertEqual(writtenRUMViewEvent.view.loadingTime, 152_800_000) - } - - func testWhenValidWebRUMActionEventPassed_itDecoratesAndPassesToWriter() throws { + func testWhenValidWebRUMEventPassed_itDecoratesAndPassesToWriter() throws { let mockSessionID = UUID(uuidString: "e9796469-c2a1-43d6-b0f6-65c47d33cf5f")! mockContextProvider.context.sessionID = RUMUUID(rawValue: mockSessionID) mockDateCorrector.correctionOffset = 123 - let eventConsumer = WebRUMEventConsumer(dataWriter: mockWriter, dateCorrector: mockDateCorrector, contextProvider: mockContextProvider) + let eventConsumer = DefaultWebRUMEventConsumer(dataWriter: mockWriter, dateCorrector: mockDateCorrector, contextProvider: mockContextProvider) - let webRUMActionEvent: JSON = [ + let webRUMEvent: JSON = [ "_dd": [ - "format_version": 2, - "drift": 1, - "session": ["plan": 2], - "browser_sdk_version": "3.10.1" + "session": ["plan": 2] ], "application": ["id": "75d50c62-8b66-403c-a453-aaa1c44d64bd"], "date": 1_640_252_823_292, "service": "shopist-web-ui", - "session": ["id": "00000000-aaaa-0000-aaaa-000000000000", "type": "user", "has_replay": true], + "session": ["id": "00000000-aaaa-0000-aaaa-000000000000"], "view": [ - "url": "https://foo.bar/department/chairs/product/2", - "referrer": "https://foo.bar/department/chairs", - "id": "00413060-599f-4a77-80de-5d3beab3da2e", - "in_foreground": true - ], - "action": [ - "id": "e73c32c2-e748-4873-b621-debd7f674c0d", - "target": ["name": "ADD TO CART"], - "type": "click", - "error": ["count": 0], - "loading_time": 5_000_000, - "long_task": ["count": 0], - "resource": ["count": 0] + "id": "00413060-599f-4a77-80de-5d3beab3da2e" ], "type": "action" ] - - try eventConsumer.consume(event: webRUMActionEvent, eventType: "action") - - let writtenRUMActionEvent = try XCTUnwrap(mockWriter.dataWritten as? RUMActionEvent) - XCTAssertEqual(writtenRUMActionEvent.view.id, "00413060-599f-4a77-80de-5d3beab3da2e") - } - - func testWhenValidWebRUMResourceEventPassed_itDecoratesAndPassesToWriter() throws { - let mockSessionID = UUID(uuidString: "e9796469-c2a1-43d6-b0f6-65c47d33cf5f")! - mockContextProvider.context.sessionID = RUMUUID(rawValue: mockSessionID) - mockDateCorrector.correctionOffset = 123 - let eventConsumer = WebRUMEventConsumer(dataWriter: mockWriter, dateCorrector: mockDateCorrector, contextProvider: mockContextProvider) - - let webRUMResourceEvent: JSON = [ + let expectedWebRUMEvent: JSON = [ "_dd": [ - "format_version": 2, - "drift": 0, - "session": ["plan": 2], - "browser_sdk_version": "3.10.1" + "session": ["plan": 1] ], - "application": ["id": "75d50c62-8b66-403c-a453-aaa1c44d64bd"], - "date": 1_640_252_561_077, + "application": ["id": mockContextProvider.context.rumApplicationID], + "date": 1_640_252_823_292 + 123.toInt64Nanoseconds, "service": "shopist-web-ui", - "session": [ - "id": "00000000-aaaa-0000-aaaa-000000000000", - "type": "user", - "has_replay": true - ], + "session": ["id": mockContextProvider.context.sessionID.toRUMDataFormat], "view": [ - "url": "https://foo.bar/", - "referrer": "", - "id": "2aac5419-a626-4098-b1b5-39154f8ea8f3" + "id": "00413060-599f-4a77-80de-5d3beab3da2e" ], - "resource": [ - "id": "6df22efd-78b4-454f-b44f-3ac8846e5311", - "type": "image", - "url": "https://foo.bar/_nuxt/img/bedding.8af1600.jpg", - "duration": 369_000_000, - "download": ["duration": 351_000_000, "start": 18_000_000], - "first_byte": ["duration": 17_000_000, "start": 1_000_000] - ], - "type": "resource" + "type": "action" ] - try eventConsumer.consume(event: webRUMResourceEvent, eventType: "resource") + try eventConsumer.consume(event: webRUMEvent) + + let data = try JSONEncoder().encode(mockWriter.dataWritten as? CodableValue) + let writtenJSON = try XCTUnwrap(try JSONSerialization.jsonObject(with: data, options: []) as? JSON) - let writtenRUMResourceEvent = try XCTUnwrap(mockWriter.dataWritten as? RUMResourceEvent) - XCTAssertEqual(writtenRUMResourceEvent.view.id, "2aac5419-a626-4098-b1b5-39154f8ea8f3") + AssertDictionariesEqual(writtenJSON, expectedWebRUMEvent) } - func testWhenValidWebRUMErrorEventPassed_itDecoratesAndPassesToWriter() throws { - let mockSessionID = UUID(uuidString: "e9796469-c2a1-43d6-b0f6-65c47d33cf5f")! - mockContextProvider.context.sessionID = RUMUUID(rawValue: mockSessionID) - mockDateCorrector.correctionOffset = 123 - let eventConsumer = WebRUMEventConsumer(dataWriter: mockWriter, dateCorrector: mockDateCorrector, contextProvider: mockContextProvider) + func testWhenValidWebRUMEventPassedWithoutRUMContext_itPassesToWriter() throws { + let eventConsumer = DefaultWebRUMEventConsumer( + dataWriter: mockWriter, + dateCorrector: mockDateCorrector, + contextProvider: nil + ) - let webRUMErrorEvent: JSON = [ + let webRUMEvent: JSON = [ "_dd": [ - "format_version": 2, - "drift": 0, - "session": ["plan": 2], - "browser_sdk_version": "3.10.1" + "session": ["plan": 2] ], "application": ["id": "75d50c62-8b66-403c-a453-aaa1c44d64bd"], - "date": 1_640_252_666_129, + "date": 1_640_252_823_292, "service": "shopist-web-ui", - "session": [ - "id": "00000000-aaaa-0000-aaaa-000000000000", - "type": "user", - "has_replay": true - ], + "session": ["id": "00000000-aaaa-0000-aaaa-000000000000"], "view": [ - "url": "https://foo.bar/department/chairs/product/2", - "referrer": "https://foo.bar/department/chairs", - "id": "00413060-599f-4a77-80de-5d3beab3da2e", - "in_foreground": true + "id": "00413060-599f-4a77-80de-5d3beab3da2e" ], - "error": [ - "id": "3de88670-be12-4a30-91c8-378f8ccb8a75", - "message": "Provided [\"type\":\"network error\",\"status\":404]", - "source": "custom", - "stack": "No stack, consider using an instance of Error", - "handling_stack": "Error: \n at @ https://foo.bar/_nuxt/app.30bb4c9.js:2:47460\n at promiseReactionJob @ [native code]", - "handling": "handled" - ], - "type": "error" + "type": "action" ] - try eventConsumer.consume(event: webRUMErrorEvent, eventType: "error") + try eventConsumer.consume(event: webRUMEvent) + + let data = try JSONEncoder().encode(mockWriter.dataWritten as? CodableValue) + let writtenJSON = try XCTUnwrap(try JSONSerialization.jsonObject(with: data, options: []) as? JSON) - let writtenRUMErrorEvent = try XCTUnwrap(mockWriter.dataWritten as? RUMErrorEvent) - XCTAssertEqual(writtenRUMErrorEvent.view.id, "00413060-599f-4a77-80de-5d3beab3da2e") + AssertDictionariesEqual(writtenJSON, webRUMEvent) } - func testWhenInvalidEventTypeIsPassed_itLogsToUserLogger() throws { - let previousUserLogger = userLogger - defer { userLogger = previousUserLogger } - let output = LogOutputMock() - userLogger = .mockWith(logOutput: output) + func testWhenUnknownWebRUMEventPassed_itPassesToWriter() throws { + let eventConsumer = DefaultWebRUMEventConsumer( + dataWriter: mockWriter, + dateCorrector: mockDateCorrector, + contextProvider: mockContextProvider + ) + + let unknownWebRUMEvent: JSON = [ + "new_key": "new_value", + "type": "unknown" + ] + + try eventConsumer.consume(event: unknownWebRUMEvent) - let eventConsumer = WebRUMEventConsumer(dataWriter: mockWriter, dateCorrector: mockDateCorrector, contextProvider: mockContextProvider) - try eventConsumer.consume(event: [:], eventType: "unknown_event_type") + let data = try JSONEncoder().encode(mockWriter.dataWritten as? CodableValue) + let writtenJSON = try XCTUnwrap(try JSONSerialization.jsonObject(with: data, options: []) as? JSON) - XCTAssertEqual(output.recordedLog?.status, .error) - let userLogMessage = try XCTUnwrap(output.recordedLog?.message) - XCTAssertEqual(userLogMessage, "🔥 Web RUM Event Error - Unknown event type: unknown_event_type") + AssertDictionariesEqual(writtenJSON, unknownWebRUMEvent) } } From c9be2e16acc272170df069034e18da40741f43c4 Mon Sep 17 00:00:00 2001 From: Mert Buran Date: Fri, 31 Dec 2021 10:39:24 +0100 Subject: [PATCH 10/10] RUMM-1791 PR comments addressed Missing test case added: sampled out native session --- .../WebView/WebRUMEventConsumerTests.swift | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/Tests/DatadogTests/Datadog/RUM/WebView/WebRUMEventConsumerTests.swift b/Tests/DatadogTests/Datadog/RUM/WebView/WebRUMEventConsumerTests.swift index 3ab570ddb5..02b16ab9af 100644 --- a/Tests/DatadogTests/Datadog/RUM/WebView/WebRUMEventConsumerTests.swift +++ b/Tests/DatadogTests/Datadog/RUM/WebView/WebRUMEventConsumerTests.swift @@ -83,6 +83,27 @@ class WebRUMEventConsumerTests: XCTestCase { AssertDictionariesEqual(writtenJSON, webRUMEvent) } + func testWhenNativeSessionIsSampledOut_itPassesWebEventToWriter() throws { + mockContextProvider.context.sessionID = RUMUUID.nullUUID + let eventConsumer = DefaultWebRUMEventConsumer( + dataWriter: mockWriter, + dateCorrector: mockDateCorrector, + contextProvider: mockContextProvider + ) + + let webRUMEvent: JSON = [ + "new_key": "new_value", + "type": "unknown" + ] + + try eventConsumer.consume(event: webRUMEvent) + + let data = try JSONEncoder().encode(mockWriter.dataWritten as? CodableValue) + let writtenJSON = try XCTUnwrap(try JSONSerialization.jsonObject(with: data, options: []) as? JSON) + + AssertDictionariesEqual(writtenJSON, webRUMEvent) + } + func testWhenUnknownWebRUMEventPassed_itPassesToWriter() throws { let eventConsumer = DefaultWebRUMEventConsumer( dataWriter: mockWriter,