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
4 changes: 4 additions & 0 deletions Datadog/Datadog.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -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 */; };
Expand Down Expand Up @@ -1167,6 +1168,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 +3297,7 @@
children = (
9EB4B86B27510AF90041CD03 /* WebEventBridgeTests.swift */,
9EAF0CF5275A21100044E8CA /* WKUserContentController+DatadogTests.swift */,
9EA8A7F72768A72B007D6FDB /* WebLogEventConsumerTests.swift */,
);
path = WebView;
sourceTree = "<group>";
Expand Down Expand Up @@ -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 */,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,32 @@ 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: WebLogEventConsumer? = nil
if let loggingFeature = LoggingFeature.instance {
logEventConsumer = WebLogEventConsumer(
userLogsWriter: loggingFeature.storage.writer,
ncreated marked this conversation as resolved.
Show resolved Hide resolved
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(
logEventConsumer: WebLogEventConsumer(),
rumEventConsumer: WebRUMEventConsumer()
logEventConsumer: logEventConsumer,
rumEventConsumer: rumEventConsumer
)
)
add(messageHandler, name: bridgeName)
Expand Down Expand Up @@ -73,9 +95,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
12 changes: 6 additions & 6 deletions Sources/Datadog/FeaturesIntegration/WebView/WebEventBridge.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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
}
Expand All @@ -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)
}
Copy link
Member

@ncreated ncreated Dec 29, 2021

Choose a reason for hiding this comment

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

Because we pass Browser events to Logging / RUM feature storage, in case of Logging or RUM being disabled we will be sending Browser SDK events to void. This will result with losing observability and entire set of telemetry from webviews, depending on native SDK configuration. This requires additional handling - @mariusc83 @buranmert @xgouchet what's our strategy for this?

I see 3 options:

  • we can send events to their own storage - independent from Logging / RUM feature;
  • we can inform the user on dropping event due to misconfiguration:
if eventType == Constants.eventTypeLog {
   if let logEventConsumer = logEventConsumer {
      try logEventConsumer.consume(event: wrappedEvent, eventType: eventType)
   } else {
      userLogger.warn(/* warn that event will be lost because Logging is disabled in native SDK */)
   }
} else {
   if let rumEventConsumer = rumEventConsumer {
      try rumEventConsumer(event: wrappedEvent, eventType: eventType)
   } else {
      userLogger.warn(/* warn that event will be lost because RUM is disabled in native SDK */)
   }
}
  • we can register selectively for receiving only Logs / only RUM / both Logs and RUM events from Browser SDK.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

ℹ️ we decided to go with userLogger.warn option at today's daily

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@ncreated should we warn the user every time a new event is received (that might spam their console)?
or only once when WebEventConsumer is created?

}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,70 @@
import Foundation

internal class WebLogEventConsumer: WebEventConsumer {
func consume(event: [String: Any], eventType: String) {
// TODO: RUMM-1791 implement event consumers
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 ?? ""
ncreated marked this conversation as resolved.
Show resolved Hide resolved

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 {
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 eventType == Constants.logEventType {
userLogsWriter.write(value: encodableEvent)
} else if eventType == Constants.internalLogEventType {
internalLogsWriter?.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,92 @@

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: 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
}

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<T: RUMDataModel>(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<T: RUMDataModel>(_ model: T) {
dataWriter.write(value: model)
}

// MARK: - Time offsets

private typealias Offset = TimeInterval
private typealias ViewIDOffsetPair = (viewID: String, offset: Offset)
private var viewIDOffsetPairs = [ViewIDOffsetPair]()

private func getOffset(viewID: String) -> Offset {
purgeOffsets()

let found = viewIDOffsetPairs.first { $0.viewID == viewID }
if let found = found {
return found.offset
}
let offset = dateCorrector.currentCorrection.serverTimeOffset
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