Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

RUMM-1791 WebEventConsumers added #683

Merged
8 changes: 8 additions & 0 deletions Datadog/Datadog.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -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 */; };
Expand All @@ -502,6 +503,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 */; };
Expand Down Expand Up @@ -1150,6 +1152,7 @@
9E2EF44E2694FA14008A7DAE /* VitalInfoSamplerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VitalInfoSamplerTests.swift; sourceTree = "<group>"; };
9E359F4D26CD518D001E25E9 /* LongTaskObserver.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LongTaskObserver.swift; sourceTree = "<group>"; };
9E36D92124373EA700BFBDB7 /* SwiftExtensionsTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SwiftExtensionsTests.swift; sourceTree = "<group>"; };
9E53889B2773C4B300A7DC42 /* WebRUMEventConsumerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WebRUMEventConsumerTests.swift; sourceTree = "<group>"; };
9E544A4E24753C6E00E83072 /* MethodSwizzler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MethodSwizzler.swift; sourceTree = "<group>"; };
9E544A5024753DDE00E83072 /* MethodSwizzlerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MethodSwizzlerTests.swift; sourceTree = "<group>"; };
9E55407B25812D1C00F6E3AD /* RUMMonitor+objc.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "RUMMonitor+objc.swift"; sourceTree = "<group>"; };
Expand All @@ -1167,6 +1170,7 @@
9E9EB37624468CE90002C80B /* Datadog.modulemap */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.module-map"; path = Datadog.modulemap; sourceTree = "<group>"; };
9EA3CA6826775A3500B16871 /* VitalRefreshRateReader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VitalRefreshRateReader.swift; sourceTree = "<group>"; };
9EA8A7F0275E1518007D6FDB /* HostsSanitizerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HostsSanitizerTests.swift; sourceTree = "<group>"; };
9EA8A7F72768A72B007D6FDB /* WebLogEventConsumerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WebLogEventConsumerTests.swift; sourceTree = "<group>"; };
9EAF0CF5275A21100044E8CA /* WKUserContentController+DatadogTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "WKUserContentController+DatadogTests.swift"; sourceTree = "<group>"; };
9EAF0CF7275A2FDC0044E8CA /* HostsSanitizer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HostsSanitizer.swift; sourceTree = "<group>"; };
9EB4B861274E79D50041CD03 /* WKUserContentController+Datadog.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "WKUserContentController+Datadog.swift"; sourceTree = "<group>"; };
Expand Down Expand Up @@ -3295,6 +3299,8 @@
children = (
9EB4B86B27510AF90041CD03 /* WebEventBridgeTests.swift */,
9EAF0CF5275A21100044E8CA /* WKUserContentController+DatadogTests.swift */,
9EA8A7F72768A72B007D6FDB /* WebLogEventConsumerTests.swift */,
9E53889B2773C4B300A7DC42 /* WebRUMEventConsumerTests.swift */,
);
path = WebView;
sourceTree = "<group>";
Expand Down Expand Up @@ -4114,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 */,
Expand Down Expand Up @@ -4216,6 +4223,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 */,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,33 @@ public extension WKUserContentController {
internal func __addDatadogMessageHandler(allowedWebViewHosts: Set<String>, hostsSanitizer: HostsSanitizing) {
let bridgeName = DatadogMessageHandler.name

let contextProvider = (Global.rum as? RUMMonitor)?.contextProvider
ncreated marked this conversation as resolved.
Show resolved Hide resolved

var logEventConsumer: DefaultWebLogEventConsumer? = nil
if let loggingFeature = LoggingFeature.instance {
logEventConsumer = DefaultWebLogEventConsumer(
userLogsWriter: loggingFeature.storage.writer,
ncreated marked this conversation as resolved.
Show resolved Hide resolved
internalLogsWriter: InternalMonitoringFeature.instance?.logsStorage.writer,
dateCorrector: loggingFeature.dateCorrector,
rumContextProvider: contextProvider,
applicationVersion: loggingFeature.configuration.common.applicationVersion,
environment: loggingFeature.configuration.common.environment
)
}

var rumEventConsumer: DefaultWebRUMEventConsumer? = nil
if let rumFeature = RUMFeature.instance {
rumEventConsumer = DefaultWebRUMEventConsumer(
dataWriter: rumFeature.storage.writer,
dateCorrector: rumFeature.dateCorrector,
contextProvider: contextProvider
)
}

let messageHandler = DatadogMessageHandler(
eventBridge: WebEventBridge(
logEventConsumer: WebLogEventConsumer(),
rumEventConsumer: WebRUMEventConsumer()
logEventConsumer: logEventConsumer,
rumEventConsumer: rumEventConsumer
)
)
add(messageHandler, name: bridgeName)
Expand Down Expand Up @@ -57,10 +80,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)
)
Expand All @@ -73,9 +96,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)")
}
Expand Down
33 changes: 25 additions & 8 deletions Sources/Datadog/FeaturesIntegration/WebView/WebEventBridge.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,12 @@ import Foundation

internal typealias JSON = [String: Any]

internal protocol WebEventConsumer {
func consume(event: JSON, eventType: String)
internal protocol WebLogEventConsumer {
func consume(event: JSON, internalLog: Bool) throws
}

internal protocol WebRUMEventConsumer {
func consume(event: JSON) throws
}

internal enum WebEventError: Error, Equatable {
Expand All @@ -24,12 +28,13 @@ internal class WebEventBridge {
static let eventTypeKey = "eventType"
static let eventKey = "event"
static let eventTypeLog = "log"
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
}
Expand All @@ -46,10 +51,22 @@ internal class WebEventBridge {
throw WebEventError.missingKey(key: Constants.eventKey)
}

if eventType == Constants.eventTypeLog {
logEventConsumer.consume(event: wrappedEvent, eventType: eventType)
if eventType == Constants.eventTypeLog ||
eventType == Constants.eventTypeInternalLog {
if let consumer = logEventConsumer {
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 {
rumEventConsumer.consume(event: wrappedEvent, eventType: eventType)
if let consumer = rumEventConsumer {
try consumer.consume(event: wrappedEvent)
} else {
userLogger.warn("A WebView RUM event is lost because RUM is disabled in iOS SDK")
}
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,78 @@

import Foundation

internal class WebLogEventConsumer: WebEventConsumer {
func consume(event: [String: Any], eventType: String) {
// TODO: RUMM-1791 implement event consumers
internal class DefaultWebLogEventConsumer: WebLogEventConsumer {
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 applicationVersion: String
private let environment: String

private let jsonDecoder = JSONDecoder()

private lazy var ddTags: String = {
let versionKey = LogEventEncoder.StaticCodingKeys.applicationVersion.rawValue
let versionValue = applicationVersion
let envKey = LogEventEncoder.StaticCodingKeys.environment.rawValue
let envValue = environment

return "\(versionKey):\(versionValue),\(envKey):\(envValue)"
}()

init(
userLogsWriter: Writer,
internalLogsWriter: Writer?,
dateCorrector: DateCorrectorType,
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, internalLog: Bool) throws {
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
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 internalLog {
internalLogsWriter?.write(value: encodableEvent)
} else {
userLogsWriter.write(value: encodableEvent)
}
buranmert marked this conversation as resolved.
Show resolved Hide resolved
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,93 @@

import Foundation

internal class WebRUMEventConsumer: WebEventConsumer {
func consume(event: [String: Any], eventType: String) {
// TODO: RUMM-1791 implement event consumers
internal class DefaultWebRUMEventConsumer: WebRUMEventConsumer {
private let dataWriter: Writer
private let dateCorrector: DateCorrectorType
private let contextProvider: RUMContextProvider?

private let jsonDecoder = JSONDecoder()

init(
dataWriter: Writer,
dateCorrector: DateCorrectorType,
contextProvider: RUMContextProvider?
) {
self.dataWriter = dataWriter
self.dateCorrector = dateCorrector
self.contextProvider = contextProvider
}

func consume(event: JSON) throws {
let rumContext = contextProvider?.context
let mappedEvent = map(event: event, with: rumContext)

let jsonData = try JSONSerialization.data(withJSONObject: mappedEvent, options: [])
let encodableEvent = try jsonDecoder.decode(CodableValue.self, from: jsonData)

dataWriter.write(value: encodableEvent)
}

private func map(event: JSON, with context: RUMContext?) -> JSON {
guard let context = context,
context.sessionID != .nullUUID else {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

context.sessionID == .nullID when the session is sampled-out

return event
}
Comment on lines +37 to +40
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"Given native RUM session rejected by sampler, when receiving event from WebView, it is sent anyway" seems to be critical (it impacts user billing) and not very obvious behaviour (in case of RUM disabled we drop events, but we send events no matter of RUM sampling). I don't see it covered in unit tests, meaning we can easily overlook it and change unawarely.


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 = Int64
private typealias ViewIDOffsetPair = (viewID: String, offset: Offset)
private var viewIDOffsetPairs = [ViewIDOffsetPair]()

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.toInt64Nanoseconds
viewIDOffsetPairs.insert((viewID: viewID, offset: offset), at: 0)
return offset
}

private func purgeOffsets() {
while viewIDOffsetPairs.count > 3 {
_ = viewIDOffsetPairs.popLast()
}
}
}
1 change: 1 addition & 0 deletions Sources/Datadog/Logging/Log/LogEventEncoder.swift
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,7 @@ internal struct LogEventEncoder {
case status
case message
case serviceName = "service"
case environment = "env"
case tags = "ddtags"

// MARK: - Error
Expand Down
Loading