diff --git a/DatadogCore/Tests/Datadog/Core/_InternalProxyTests.swift b/DatadogCore/Tests/Datadog/Core/_InternalProxyTests.swift index 8b170cfb67..c083873ab7 100644 --- a/DatadogCore/Tests/Datadog/Core/_InternalProxyTests.swift +++ b/DatadogCore/Tests/Datadog/Core/_InternalProxyTests.swift @@ -24,7 +24,7 @@ class _InternalProxyTests: XCTestCase { // Then XCTAssertEqual(dd.telemetry.messages.count, 2) - guard case .debug(_, let receivedMessage) = dd.telemetry.messages.first else { + guard case .debug(_, let receivedMessage, _) = dd.telemetry.messages.first else { return XCTFail("A debug should be send to `DD.telemetry`.") } XCTAssertEqual(receivedMessage, randomDebugMessage) diff --git a/DatadogCore/Tests/Datadog/InternalTelemetryTests.swift b/DatadogCore/Tests/Datadog/InternalTelemetryTests.swift index 471275372a..e8d44ab140 100644 --- a/DatadogCore/Tests/Datadog/InternalTelemetryTests.swift +++ b/DatadogCore/Tests/Datadog/InternalTelemetryTests.swift @@ -24,7 +24,7 @@ class InternalTelemetryTests: XCTestCase { // Then XCTAssertEqual(dd.telemetry.messages.count, 1) - guard case .debug(let receivedId, let receivedMessage) = dd.telemetry.messages.first else { + guard case .debug(let receivedId, let receivedMessage, _) = dd.telemetry.messages.first else { return XCTFail("A debug should be send to `DD.telemetry`.") } diff --git a/DatadogCore/Tests/Datadog/Mocks/RUMDataModelMocks.swift b/DatadogCore/Tests/Datadog/Mocks/RUMDataModelMocks.swift index 9b675f6770..0a266cf1cd 100644 --- a/DatadogCore/Tests/Datadog/Mocks/RUMDataModelMocks.swift +++ b/DatadogCore/Tests/Datadog/Mocks/RUMDataModelMocks.swift @@ -108,6 +108,7 @@ extension RUMViewEvent: RandomMockable { return RUMViewEvent( dd: .init( browserSdkVersion: nil, + configuration: nil, documentVersion: .mockRandom(), pageStates: nil, replayStats: nil, @@ -188,6 +189,7 @@ extension RUMResourceEvent: RandomMockable { return RUMResourceEvent( dd: .init( browserSdkVersion: nil, + configuration: nil, discarded: nil, rulePsr: nil, session: .init(plan: .plan1), @@ -255,6 +257,7 @@ extension RUMActionEvent: RandomMockable { ) ), browserSdkVersion: nil, + configuration: nil, session: .init(plan: .plan1) ), action: .init( @@ -307,6 +310,7 @@ extension RUMErrorEvent: RandomMockable { return RUMErrorEvent( dd: .init( browserSdkVersion: nil, + configuration: nil, session: .init(plan: .plan1) ), action: .init(id: .mockRandom()), @@ -377,6 +381,7 @@ extension RUMLongTaskEvent: RandomMockable { return RUMLongTaskEvent( dd: .init( browserSdkVersion: nil, + configuration: nil, discarded: nil, session: .init(plan: .plan1) ), diff --git a/DatadogInternal/Sources/Telemetry/Telemetry.swift b/DatadogInternal/Sources/Telemetry/Telemetry.swift index 538a6e9790..2490d56b02 100644 --- a/DatadogInternal/Sources/Telemetry/Telemetry.swift +++ b/DatadogInternal/Sources/Telemetry/Telemetry.swift @@ -32,7 +32,7 @@ public struct ConfigurationTelemetry: Equatable { } public enum TelemetryMessage { - case debug(id: String, message: String) + case debug(id: String, message: String, attributes: [String: Encodable]?) case error(id: String, message: String, kind: String?, stack: String?) case configuration(ConfigurationTelemetry) } @@ -52,8 +52,9 @@ extension Telemetry { /// - Parameters: /// - id: Identity of the debug log, this can be used to prevent duplicates. /// - message: The debug message. - public func debug(id: String, message: String) { - send(telemetry: .debug(id: id, message: message)) + /// - attributes: Custom attributes attached to the log (optional). + public func debug(id: String, message: String, attributes: [String: Encodable]? = nil) { + send(telemetry: .debug(id: id, message: message, attributes: attributes)) } /// Collect execution error. @@ -81,10 +82,11 @@ extension Telemetry { /// /// - Parameters: /// - message: The debug message. + /// - attributes: Custom attributes attached to the log (optional). /// - file: The current file name. /// - line: The line number in file. - public func debug(_ message: String, file: String = #file, line: Int = #line) { - debug(id: "\(file):\(line):\(message)", message: message) + public func debug(_ message: String, attributes: [String: Encodable]? = nil, file: String = #file, line: Int = #line) { + debug(id: "\(file):\(line):\(message)", message: message, attributes: attributes) } /// Collect execution error. diff --git a/DatadogInternal/Tests/Telemetry/TelemetryTests.swift b/DatadogInternal/Tests/Telemetry/TelemetryTests.swift index 12f03c5b6b..247ec6d358 100644 --- a/DatadogInternal/Tests/Telemetry/TelemetryTests.swift +++ b/DatadogInternal/Tests/Telemetry/TelemetryTests.swift @@ -13,14 +13,14 @@ class TelemetryTests: XCTestCase { func testTelemetryDebug() { // Given class TelemetryTest: Telemetry { - var debug: (id: String, message: String)? + var debug: (id: String, message: String, attributes: [String: Encodable]?)? func send(telemetry: DatadogInternal.TelemetryMessage) { - guard case .debug(let id, let message) = telemetry else { + guard case let .debug(id, message, attributes) = telemetry else { return } - debug = (id: id, message: message) + debug = (id: id, message: message, attributes: attributes) } } @@ -32,12 +32,13 @@ class TelemetryTests: XCTestCase { // When #sourceLocation(file: "File.swift", line: 1) - telemetry.debug("debug message") + telemetry.debug("debug message", attributes: ["foo": "bar"]) #sourceLocation() // Then XCTAssertEqual(telemetry.debug?.id, "File.swift:1:debug message") XCTAssertEqual(telemetry.debug?.message, "debug message") + XCTAssertEqual(telemetry.debug?.attributes as? [String: String], ["foo": "bar"]) } func testTelemetryErrorFormatting() { @@ -46,7 +47,7 @@ class TelemetryTests: XCTestCase { var error: (id: String, message: String, kind: String?, stack: String?)? func send(telemetry: DatadogInternal.TelemetryMessage) { - guard case .error(let id, let message, let kind, let stack) = telemetry else { + guard case let .error(id, message, kind, stack) = telemetry else { return } @@ -202,7 +203,7 @@ class TelemetryTests: XCTestCase { telemetry.debug("debug") // Then - guard case .debug(_, let message) = receiver.telemetry else { + guard case .debug(_, let message, _) = receiver.telemetry else { return XCTFail("A debug should be send to core.") } XCTAssertEqual(message, "debug") diff --git a/DatadogObjc/Sources/RUM/RUMDataModels+objc.swift b/DatadogObjc/Sources/RUM/RUMDataModels+objc.swift index b8e7e74ea1..f064777ee0 100644 --- a/DatadogObjc/Sources/RUM/RUMDataModels+objc.swift +++ b/DatadogObjc/Sources/RUM/RUMDataModels+objc.swift @@ -109,6 +109,10 @@ public class DDRUMActionEventDD: NSObject { root.swiftModel.dd.browserSdkVersion } + @objc public var configuration: DDRUMActionEventDDConfiguration? { + root.swiftModel.dd.configuration != nil ? DDRUMActionEventDDConfiguration(root: root) : nil + } + @objc public var formatVersion: NSNumber { root.swiftModel.dd.formatVersion as NSNumber } @@ -173,6 +177,23 @@ public class DDRUMActionEventDDActionTarget: NSObject { } } +@objc +public class DDRUMActionEventDDConfiguration: NSObject { + internal let root: DDRUMActionEvent + + internal init(root: DDRUMActionEvent) { + self.root = root + } + + @objc public var sessionReplaySampleRate: NSNumber { + root.swiftModel.dd.configuration!.sessionReplaySampleRate as NSNumber + } + + @objc public var sessionSampleRate: NSNumber { + root.swiftModel.dd.configuration!.sessionSampleRate as NSNumber + } +} + @objc public class DDRUMActionEventDDSession: NSObject { internal let root: DDRUMActionEvent @@ -916,6 +937,10 @@ public class DDRUMErrorEventDD: NSObject { root.swiftModel.dd.browserSdkVersion } + @objc public var configuration: DDRUMErrorEventDDConfiguration? { + root.swiftModel.dd.configuration != nil ? DDRUMErrorEventDDConfiguration(root: root) : nil + } + @objc public var formatVersion: NSNumber { root.swiftModel.dd.formatVersion as NSNumber } @@ -925,6 +950,23 @@ public class DDRUMErrorEventDD: NSObject { } } +@objc +public class DDRUMErrorEventDDConfiguration: NSObject { + internal let root: DDRUMErrorEvent + + internal init(root: DDRUMErrorEvent) { + self.root = root + } + + @objc public var sessionReplaySampleRate: NSNumber { + root.swiftModel.dd.configuration!.sessionReplaySampleRate as NSNumber + } + + @objc public var sessionSampleRate: NSNumber { + root.swiftModel.dd.configuration!.sessionSampleRate as NSNumber + } +} + @objc public class DDRUMErrorEventDDSession: NSObject { internal let root: DDRUMErrorEvent @@ -1886,6 +1928,10 @@ public class DDRUMLongTaskEventDD: NSObject { root.swiftModel.dd.browserSdkVersion } + @objc public var configuration: DDRUMLongTaskEventDDConfiguration? { + root.swiftModel.dd.configuration != nil ? DDRUMLongTaskEventDDConfiguration(root: root) : nil + } + @objc public var discarded: NSNumber? { root.swiftModel.dd.discarded as NSNumber? } @@ -1899,6 +1945,23 @@ public class DDRUMLongTaskEventDD: NSObject { } } +@objc +public class DDRUMLongTaskEventDDConfiguration: NSObject { + internal let root: DDRUMLongTaskEvent + + internal init(root: DDRUMLongTaskEvent) { + self.root = root + } + + @objc public var sessionReplaySampleRate: NSNumber { + root.swiftModel.dd.configuration!.sessionReplaySampleRate as NSNumber + } + + @objc public var sessionSampleRate: NSNumber { + root.swiftModel.dd.configuration!.sessionSampleRate as NSNumber + } +} + @objc public class DDRUMLongTaskEventDDSession: NSObject { internal let root: DDRUMLongTaskEvent @@ -2503,6 +2566,10 @@ public class DDRUMResourceEventDD: NSObject { root.swiftModel.dd.browserSdkVersion } + @objc public var configuration: DDRUMResourceEventDDConfiguration? { + root.swiftModel.dd.configuration != nil ? DDRUMResourceEventDDConfiguration(root: root) : nil + } + @objc public var discarded: NSNumber? { root.swiftModel.dd.discarded as NSNumber? } @@ -2528,6 +2595,23 @@ public class DDRUMResourceEventDD: NSObject { } } +@objc +public class DDRUMResourceEventDDConfiguration: NSObject { + internal let root: DDRUMResourceEvent + + internal init(root: DDRUMResourceEvent) { + self.root = root + } + + @objc public var sessionReplaySampleRate: NSNumber { + root.swiftModel.dd.configuration!.sessionReplaySampleRate as NSNumber + } + + @objc public var sessionSampleRate: NSNumber { + root.swiftModel.dd.configuration!.sessionSampleRate as NSNumber + } +} + @objc public class DDRUMResourceEventDDSession: NSObject { internal let root: DDRUMResourceEvent @@ -3441,6 +3525,10 @@ public class DDRUMViewEventDD: NSObject { root.swiftModel.dd.browserSdkVersion } + @objc public var configuration: DDRUMViewEventDDConfiguration? { + root.swiftModel.dd.configuration != nil ? DDRUMViewEventDDConfiguration(root: root) : nil + } + @objc public var documentVersion: NSNumber { root.swiftModel.dd.documentVersion as NSNumber } @@ -3462,6 +3550,23 @@ public class DDRUMViewEventDD: NSObject { } } +@objc +public class DDRUMViewEventDDConfiguration: NSObject { + internal let root: DDRUMViewEvent + + internal init(root: DDRUMViewEvent) { + self.root = root + } + + @objc public var sessionReplaySampleRate: NSNumber { + root.swiftModel.dd.configuration!.sessionReplaySampleRate as NSNumber + } + + @objc public var sessionSampleRate: NSNumber { + root.swiftModel.dd.configuration!.sessionSampleRate as NSNumber + } +} + @objc public class DDRUMViewEventDDPageStates: NSObject { internal var swiftModel: RUMViewEvent.DD.PageStates @@ -4810,6 +4915,10 @@ public class DDTelemetryDebugEventTelemetry: NSObject { @objc public var type: String? { root.swiftModel.telemetry.type } + + @objc public var telemetryInfo: [String: Any] { + root.swiftModel.telemetry.telemetryInfo.castToObjectiveC() + } } @objc @@ -5325,4 +5434,4 @@ public class DDTelemetryConfigurationEventView: NSObject { // swiftlint:enable force_unwrapping -// Generated from https://github.com/DataDog/rum-events-format/tree/1c5eaa897c065e5f790a5f8aaf6fc8782d706051 +// Generated from https://github.com/DataDog/rum-events-format/tree/2b1615693d269368ed91f061103ee98bfecafb00 diff --git a/DatadogRUM/Sources/DataModels/RUMDataModels.swift b/DatadogRUM/Sources/DataModels/RUMDataModels.swift index eb86a2e57d..c5f7fa14fc 100644 --- a/DatadogRUM/Sources/DataModels/RUMDataModels.swift +++ b/DatadogRUM/Sources/DataModels/RUMDataModels.swift @@ -95,6 +95,9 @@ public struct RUMActionEvent: RUMDataModel { /// Browser SDK version public let browserSdkVersion: String? + /// Subset of the SDK configuration options in use during its execution + public let configuration: Configuration? + /// Version of the RUM event format public let formatVersion: Int64 = 2 @@ -104,6 +107,7 @@ public struct RUMActionEvent: RUMDataModel { enum CodingKeys: String, CodingKey { case action = "action" case browserSdkVersion = "browser_sdk_version" + case configuration = "configuration" case formatVersion = "format_version" case session = "session" } @@ -154,6 +158,20 @@ public struct RUMActionEvent: RUMDataModel { } } + /// Subset of the SDK configuration options in use during its execution + public struct Configuration: Codable { + /// The percentage of sessions with RUM & Session Replay pricing tracked + public let sessionReplaySampleRate: Double + + /// The percentage of sessions tracked + public let sessionSampleRate: Double + + enum CodingKeys: String, CodingKey { + case sessionReplaySampleRate = "session_replay_sample_rate" + case sessionSampleRate = "session_sample_rate" + } + } + /// Session-related internal properties public struct Session: Codable { /// Session plan: 1 is the plan without replay, 2 is the plan with replay (deprecated) @@ -496,6 +514,9 @@ public struct RUMErrorEvent: RUMDataModel { /// Browser SDK version public let browserSdkVersion: String? + /// Subset of the SDK configuration options in use during its execution + public let configuration: Configuration? + /// Version of the RUM event format public let formatVersion: Int64 = 2 @@ -504,10 +525,25 @@ public struct RUMErrorEvent: RUMDataModel { enum CodingKeys: String, CodingKey { case browserSdkVersion = "browser_sdk_version" + case configuration = "configuration" case formatVersion = "format_version" case session = "session" } + /// Subset of the SDK configuration options in use during its execution + public struct Configuration: Codable { + /// The percentage of sessions with RUM & Session Replay pricing tracked + public let sessionReplaySampleRate: Double + + /// The percentage of sessions tracked + public let sessionSampleRate: Double + + enum CodingKeys: String, CodingKey { + case sessionReplaySampleRate = "session_replay_sample_rate" + case sessionSampleRate = "session_sample_rate" + } + } + /// Session-related internal properties public struct Session: Codable { /// Session plan: 1 is the plan without replay, 2 is the plan with replay (deprecated) @@ -938,6 +974,9 @@ public struct RUMLongTaskEvent: RUMDataModel { /// Browser SDK version public let browserSdkVersion: String? + /// Subset of the SDK configuration options in use during its execution + public let configuration: Configuration? + /// Whether the long task should be discarded or indexed public let discarded: Bool? @@ -949,11 +988,26 @@ public struct RUMLongTaskEvent: RUMDataModel { enum CodingKeys: String, CodingKey { case browserSdkVersion = "browser_sdk_version" + case configuration = "configuration" case discarded = "discarded" case formatVersion = "format_version" case session = "session" } + /// Subset of the SDK configuration options in use during its execution + public struct Configuration: Codable { + /// The percentage of sessions with RUM & Session Replay pricing tracked + public let sessionReplaySampleRate: Double + + /// The percentage of sessions tracked + public let sessionSampleRate: Double + + enum CodingKeys: String, CodingKey { + case sessionReplaySampleRate = "session_replay_sample_rate" + case sessionSampleRate = "session_sample_rate" + } + } + /// Session-related internal properties public struct Session: Codable { /// Session plan: 1 is the plan without replay, 2 is the plan with replay (deprecated) @@ -1195,6 +1249,9 @@ public struct RUMResourceEvent: RUMDataModel { /// Browser SDK version public let browserSdkVersion: String? + /// Subset of the SDK configuration options in use during its execution + public let configuration: Configuration? + /// Whether the resource should be discarded or indexed public let discarded: Bool? @@ -1215,6 +1272,7 @@ public struct RUMResourceEvent: RUMDataModel { enum CodingKeys: String, CodingKey { case browserSdkVersion = "browser_sdk_version" + case configuration = "configuration" case discarded = "discarded" case formatVersion = "format_version" case rulePsr = "rule_psr" @@ -1223,6 +1281,20 @@ public struct RUMResourceEvent: RUMDataModel { case traceId = "trace_id" } + /// Subset of the SDK configuration options in use during its execution + public struct Configuration: Codable { + /// The percentage of sessions with RUM & Session Replay pricing tracked + public let sessionReplaySampleRate: Double + + /// The percentage of sessions tracked + public let sessionSampleRate: Double + + enum CodingKeys: String, CodingKey { + case sessionReplaySampleRate = "session_replay_sample_rate" + case sessionSampleRate = "session_sample_rate" + } + } + /// Session-related internal properties public struct Session: Codable { /// Session plan: 1 is the plan without replay, 2 is the plan with replay (deprecated) @@ -1643,6 +1715,9 @@ public struct RUMViewEvent: RUMDataModel { /// Browser SDK version public let browserSdkVersion: String? + /// Subset of the SDK configuration options in use during its execution + public let configuration: Configuration? + /// Version of the update of the view event public let documentVersion: Int64 @@ -1660,6 +1735,7 @@ public struct RUMViewEvent: RUMDataModel { enum CodingKeys: String, CodingKey { case browserSdkVersion = "browser_sdk_version" + case configuration = "configuration" case documentVersion = "document_version" case formatVersion = "format_version" case pageStates = "page_states" @@ -1667,6 +1743,20 @@ public struct RUMViewEvent: RUMDataModel { case session = "session" } + /// Subset of the SDK configuration options in use during its execution + public struct Configuration: Codable { + /// The percentage of sessions with RUM & Session Replay pricing tracked + public let sessionReplaySampleRate: Double + + /// The percentage of sessions tracked + public let sessionSampleRate: Double + + enum CodingKeys: String, CodingKey { + case sessionReplaySampleRate = "session_replay_sample_rate" + case sessionSampleRate = "session_sample_rate" + } + } + /// Properties of the page state public struct PageStates: Codable { /// Duration in ns between start of the view and start of the page state @@ -2398,7 +2488,7 @@ public struct TelemetryDebugEvent: RUMDataModel { public let source: Source /// The telemetry log information - public let telemetry: Telemetry + public internal(set) var telemetry: Telemetry /// Telemetry event type. Should specify telemetry only. public let type: String = "telemetry" @@ -2484,7 +2574,9 @@ public struct TelemetryDebugEvent: RUMDataModel { /// Telemetry type public let type: String? = "log" - enum CodingKeys: String, CodingKey { + public internal(set) var telemetryInfo: [String: Encodable] + + enum StaticCodingKeys: String, CodingKey { case message = "message" case status = "status" case type = "type" @@ -2502,6 +2594,39 @@ public struct TelemetryDebugEvent: RUMDataModel { } } +extension TelemetryDebugEvent.Telemetry { + public func encode(to encoder: Encoder) throws { + // Encode static properties: + var staticContainer = encoder.container(keyedBy: StaticCodingKeys.self) + try staticContainer.encodeIfPresent(message, forKey: .message) + + // Encode dynamic properties: + var dynamicContainer = encoder.container(keyedBy: DynamicCodingKey.self) + try telemetryInfo.forEach { + let key = DynamicCodingKey($0) + try dynamicContainer.encode(AnyEncodable($1), forKey: key) + } + } + + public init(from decoder: Decoder) throws { + // Decode static properties: + let staticContainer = try decoder.container(keyedBy: StaticCodingKeys.self) + self.message = try staticContainer.decode(String.self, forKey: .message) + + // Decode other properties into [String: Codable] dictionary: + let dynamicContainer = try decoder.container(keyedBy: DynamicCodingKey.self) + let allStaticKeys = Set(staticContainer.allKeys.map { $0.stringValue }) + let dynamicKeys = dynamicContainer.allKeys.filter { !allStaticKeys.contains($0.stringValue) } + var dictionary: [String: Codable] = [:] + + try dynamicKeys.forEach { codingKey in + dictionary[codingKey.stringValue] = try dynamicContainer.decode(AnyCodable.self, forKey: codingKey) + } + + self.telemetryInfo = dictionary + } +} + /// Schema of all properties of a telemetry configuration event public struct TelemetryConfigurationEvent: RUMDataModel { /// Internal properties @@ -2664,7 +2789,7 @@ public struct TelemetryConfigurationEvent: RUMDataModel { /// A list of selected tracing propagators public let selectedTracingPropagators: [SelectedTracingPropagators]? - /// The percentage of sessions with Browser RUM & Session Replay pricing tracked + /// The percentage of sessions with RUM & Session Replay pricing tracked public var sessionReplaySampleRate: Int64? /// The percentage of sessions tracked @@ -3184,4 +3309,4 @@ public enum RUMMethod: String, Codable { case patch = "PATCH" } -// Generated from https://github.com/DataDog/rum-events-format/tree/1c5eaa897c065e5f790a5f8aaf6fc8782d706051 +// Generated from https://github.com/DataDog/rum-events-format/tree/2b1615693d269368ed91f061103ee98bfecafb00 diff --git a/DatadogRUM/Sources/Integrations/CrashReportReceiver.swift b/DatadogRUM/Sources/Integrations/CrashReportReceiver.swift index df4820c7ab..82d6ad5712 100644 --- a/DatadogRUM/Sources/Integrations/CrashReportReceiver.swift +++ b/DatadogRUM/Sources/Integrations/CrashReportReceiver.swift @@ -325,6 +325,7 @@ internal struct CrashReportReceiver: FeatureMessageReceiver { let event = RUMErrorEvent( dd: .init( browserSdkVersion: nil, + configuration: nil, session: .init(plan: .plan1) ), action: nil, @@ -377,6 +378,7 @@ internal struct CrashReportReceiver: FeatureMessageReceiver { return RUMViewEvent( dd: .init( browserSdkVersion: nil, + configuration: nil, documentVersion: original.dd.documentVersion + 1, pageStates: nil, replayStats: nil, @@ -453,6 +455,7 @@ internal struct CrashReportReceiver: FeatureMessageReceiver { return RUMViewEvent( dd: .init( browserSdkVersion: nil, + configuration: nil, documentVersion: 1, pageStates: nil, replayStats: nil, diff --git a/DatadogRUM/Sources/Integrations/TelemetryReceiver.swift b/DatadogRUM/Sources/Integrations/TelemetryReceiver.swift index 85c633e6dd..be4d617b66 100644 --- a/DatadogRUM/Sources/Integrations/TelemetryReceiver.swift +++ b/DatadogRUM/Sources/Integrations/TelemetryReceiver.swift @@ -62,9 +62,9 @@ internal final class TelemetryReceiver: FeatureMessageReceiver { /// - Returns: Always `true`. func receive(telemetry: TelemetryMessage, from core: DatadogCoreProtocol) -> Bool { switch telemetry { - case .debug(let id, let message): - debug(id: id, message: message, in: core) - case .error(let id, let message, let kind, let stack): + case let .debug(id, message, attributes): + debug(id: id, message: message, attributes: attributes, in: core) + case let .error(id, message, kind, stack): error(id: id, message: message, kind: kind, stack: stack, in: core) case .configuration(let configuration): send(configuration: configuration, in: core) @@ -82,16 +82,17 @@ internal final class TelemetryReceiver: FeatureMessageReceiver { /// - Parameters: /// - id: Identity of the debug log, this can be used to prevent duplicates. /// - message: The debug message. - func debug(id: String, message: String, in core: DatadogCoreProtocol) { + /// - attributes: Custom attributes attached to the log (optional). + func debug(id: String, message: String, attributes: [String: Encodable]?, in core: DatadogCoreProtocol) { let date = dateProvider.now record(event: id, in: core) { context, writer in - let attributes: [String: String]? = context.featuresAttributes["rum"]?.ids + let rum: [String: String]? = context.featuresAttributes["rum"]?.ids - let applicationId = attributes?[RUMContextAttributes.IDs.applicationID] - let sessionId = attributes?[RUMContextAttributes.IDs.sessionID] - let viewId = attributes?[RUMContextAttributes.IDs.viewID] - let actionId = attributes?[RUMContextAttributes.IDs.userActionID] + let applicationId = rum?[RUMContextAttributes.IDs.applicationID] + let sessionId = rum?[RUMContextAttributes.IDs.sessionID] + let viewId = rum?[RUMContextAttributes.IDs.viewID] + let actionId = rum?[RUMContextAttributes.IDs.userActionID] let event = TelemetryDebugEvent( dd: .init(), @@ -102,7 +103,10 @@ internal final class TelemetryReceiver: FeatureMessageReceiver { service: "dd-sdk-ios", session: sessionId.map { .init(id: $0) }, source: .init(rawValue: context.source) ?? .ios, - telemetry: .init(message: message), + telemetry: .init( + message: message, + telemetryInfo: attributes ?? [:] + ), version: context.sdkVersion, view: viewId.map { .init(id: $0) } ) diff --git a/DatadogRUM/Sources/RUMMonitor/Scopes/RUMResourceScope.swift b/DatadogRUM/Sources/RUMMonitor/Scopes/RUMResourceScope.swift index 0dead7deba..4a72cbfd61 100644 --- a/DatadogRUM/Sources/RUMMonitor/Scopes/RUMResourceScope.swift +++ b/DatadogRUM/Sources/RUMMonitor/Scopes/RUMResourceScope.swift @@ -136,6 +136,7 @@ internal class RUMResourceScope: RUMScope { let resourceEvent = RUMResourceEvent( dd: .init( browserSdkVersion: nil, + configuration: nil, discarded: nil, rulePsr: traceSamplingRate, session: .init(plan: .plan1), @@ -229,6 +230,7 @@ internal class RUMResourceScope: RUMScope { let errorEvent = RUMErrorEvent( dd: .init( browserSdkVersion: nil, + configuration: nil, session: .init(plan: .plan1) ), action: self.context.activeUserActionID.map { rumUUID in diff --git a/DatadogRUM/Sources/RUMMonitor/Scopes/RUMUserActionScope.swift b/DatadogRUM/Sources/RUMMonitor/Scopes/RUMUserActionScope.swift index 622793a989..806d2df6e2 100644 --- a/DatadogRUM/Sources/RUMMonitor/Scopes/RUMUserActionScope.swift +++ b/DatadogRUM/Sources/RUMMonitor/Scopes/RUMUserActionScope.swift @@ -141,6 +141,7 @@ internal class RUMUserActionScope: RUMScope, RUMContextProvider { dd: .init( action: nil, browserSdkVersion: nil, + configuration: nil, session: .init(plan: .plan1) ), action: .init( diff --git a/DatadogRUM/Sources/RUMMonitor/Scopes/RUMViewScope.swift b/DatadogRUM/Sources/RUMMonitor/Scopes/RUMViewScope.swift index 58793254de..ceb31779ac 100644 --- a/DatadogRUM/Sources/RUMMonitor/Scopes/RUMViewScope.swift +++ b/DatadogRUM/Sources/RUMMonitor/Scopes/RUMViewScope.swift @@ -368,6 +368,7 @@ internal class RUMViewScope: RUMScope, RUMContextProvider { dd: .init( action: nil, browserSdkVersion: nil, + configuration: nil, session: .init(plan: .plan1) ), action: .init( @@ -437,6 +438,7 @@ internal class RUMViewScope: RUMScope, RUMContextProvider { let viewEvent = RUMViewEvent( dd: .init( browserSdkVersion: nil, + configuration: nil, documentVersion: version.toInt64, pageStates: nil, replayStats: .init( @@ -535,6 +537,7 @@ internal class RUMViewScope: RUMScope, RUMContextProvider { let errorEvent = RUMErrorEvent( dd: .init( browserSdkVersion: nil, + configuration: nil, session: .init(plan: .plan1) ), action: self.context.activeUserActionID.map { rumUUID in @@ -598,6 +601,7 @@ internal class RUMViewScope: RUMScope, RUMContextProvider { let longTaskEvent = RUMLongTaskEvent( dd: .init( browserSdkVersion: nil, + configuration: nil, discarded: nil, session: .init(plan: .plan1) ), diff --git a/DatadogRUM/Tests/Integrations/TelemetryReceiverTests.swift b/DatadogRUM/Tests/Integrations/TelemetryReceiverTests.swift index 9f10612802..3648ccb643 100644 --- a/DatadogRUM/Tests/Integrations/TelemetryReceiverTests.swift +++ b/DatadogRUM/Tests/Integrations/TelemetryReceiverTests.swift @@ -42,7 +42,7 @@ class TelemetryReceiverTests: XCTestCase { ) // When - telemetry.debug("Hello world!") + telemetry.debug("Hello world!", attributes: ["foo": 42]) // Then let event = core.events(ofType: TelemetryDebugEvent.self).first @@ -51,6 +51,7 @@ class TelemetryReceiverTests: XCTestCase { XCTAssertEqual(event?.service, "dd-sdk-ios") XCTAssertEqual(event?.source.rawValue, core.context.source) XCTAssertEqual(event?.telemetry.message, "Hello world!") + XCTAssertEqual(event?.telemetry.telemetryInfo as? [String: Int], ["foo": 42]) } func testSendTelemetryError() { @@ -93,7 +94,7 @@ class TelemetryReceiverTests: XCTestCase { ]}) // When - telemetry.debug("telemetry debug") + telemetry.debug("telemetry debug", attributes: ["foo": 42]) // Then let event = core.events(ofType: TelemetryDebugEvent.self).first @@ -102,6 +103,7 @@ class TelemetryReceiverTests: XCTestCase { XCTAssertEqual(event?.session?.id, sessionId) XCTAssertEqual(event?.view?.id, viewId) XCTAssertEqual(event?.action?.id, actionId) + XCTAssertEqual(event?.telemetry.telemetryInfo as? [String: Int], ["foo": 42]) } func testSendTelemetryError_withRUMContext() throws { diff --git a/DatadogRUM/Tests/Mocks/RUMDataModelMocks.swift b/DatadogRUM/Tests/Mocks/RUMDataModelMocks.swift index dafc247d0c..004f3d9448 100644 --- a/DatadogRUM/Tests/Mocks/RUMDataModelMocks.swift +++ b/DatadogRUM/Tests/Mocks/RUMDataModelMocks.swift @@ -108,6 +108,7 @@ extension RUMViewEvent: RandomMockable { return RUMViewEvent( dd: .init( browserSdkVersion: nil, + configuration: nil, documentVersion: .mockRandom(), pageStates: nil, replayStats: nil, @@ -188,6 +189,7 @@ extension RUMResourceEvent: RandomMockable { return RUMResourceEvent( dd: .init( browserSdkVersion: nil, + configuration: nil, discarded: nil, rulePsr: nil, session: .init(plan: .plan1), @@ -255,6 +257,7 @@ extension RUMActionEvent: RandomMockable { ) ), browserSdkVersion: nil, + configuration: nil, session: .init(plan: .plan1) ), action: .init( @@ -307,6 +310,7 @@ extension RUMErrorEvent: RandomMockable { return RUMErrorEvent( dd: .init( browserSdkVersion: nil, + configuration: nil, session: .init(plan: .plan1) ), action: .init(id: .mockRandom()), @@ -377,6 +381,7 @@ extension RUMLongTaskEvent: RandomMockable { return RUMLongTaskEvent( dd: .init( browserSdkVersion: nil, + configuration: nil, discarded: nil, session: .init(plan: .plan1) ), diff --git a/TestUtilities/Mocks/TelemetryMocks.swift b/TestUtilities/Mocks/TelemetryMocks.swift index 5e48811833..d17612a535 100644 --- a/TestUtilities/Mocks/TelemetryMocks.swift +++ b/TestUtilities/Mocks/TelemetryMocks.swift @@ -66,8 +66,9 @@ public class TelemetryMock: Telemetry, CustomStringConvertible { messages.append(telemetry) switch telemetry { - case .debug(_, let message): - description.append("\n- [debug] \(message)") + case .debug(_, let message, let attributes): + let attributesString = attributes.map({ ", \($0)" }) ?? "" + description.append("\n- [debug] \(message)" + attributesString) case .error(_, let message, let kind, let stack): description.append("\n - [error] \(message), kind: \(kind ?? "nil"), stack: \(stack ?? "nil")") case .configuration(let configuration): diff --git a/tools/rum-models-generator/Sources/CodeGeneration/Print/SwiftPrinter.swift b/tools/rum-models-generator/Sources/CodeGeneration/Print/SwiftPrinter.swift index 69790c3557..cf91500bca 100644 --- a/tools/rum-models-generator/Sources/CodeGeneration/Print/SwiftPrinter.swift +++ b/tools/rum-models-generator/Sources/CodeGeneration/Print/SwiftPrinter.swift @@ -153,8 +153,9 @@ public class SwiftPrinter: BasePrinter, CodePrinter { /// This will only be effective for types that support dynamically coded properties. If all properties are statically coded, /// the `Codable` implementation is generated by compiler. private func printExtensionsWithCodableImplementation(_ swiftStruct: SwiftStruct, parentSwiftStructs: [SwiftStruct] = []) throws { - let staticallyCodedProperties = swiftStruct.properties.filter { $0.codingKey.isStatic } - let dynamicallyCodedProperties = swiftStruct.properties.filter { !$0.codingKey.isStatic } + let codedProperties = swiftStruct.properties.filter { $0.defaultValue == nil || $0.mutability != .immutable } + let staticallyCodedProperties = codedProperties.filter { $0.codingKey.isStatic } + let dynamicallyCodedProperties = codedProperties.filter { !$0.codingKey.isStatic } guard dynamicallyCodedProperties.count < 2 else { throw Exception.illegal( diff --git a/tools/rum-models-generator/Tests/CodeGenerationTests/Print/SwiftPrinterTests.swift b/tools/rum-models-generator/Tests/CodeGenerationTests/Print/SwiftPrinterTests.swift index 5f49aa4e3d..6194790f7b 100644 --- a/tools/rum-models-generator/Tests/CodeGenerationTests/Print/SwiftPrinterTests.swift +++ b/tools/rum-models-generator/Tests/CodeGenerationTests/Print/SwiftPrinterTests.swift @@ -229,7 +229,7 @@ final class SwiftPrinterTests: XCTestCase { mutability: .immutable, defaultValue: nil, codingKey: .static(value: "property_2") - ) + ), ], conformance: [codableProtocol] ) @@ -270,6 +270,15 @@ final class SwiftPrinterTests: XCTestCase { mutability: .immutable, defaultValue: nil, codingKey: .dynamic + ), + SwiftStruct.Property( + name: "ignoredProperty", + comment: "This property will be ignored in coding because it uses default value and is immutable", + type: SwiftPrimitive(), + isOptional: false, + mutability: .immutable, + defaultValue: "default value", + codingKey: .static(value: "ignoredProperty") ) ], conformance: [codableProtocol] @@ -282,6 +291,13 @@ final class SwiftPrinterTests: XCTestCase { public struct Foo: Codable { public let context: [String: Codable] + + /// This property will be ignored in coding because it uses default value and is immutable + public let ignoredProperty: String = "default value" + + enum StaticCodingKeys: String, CodingKey { + case ignoredProperty = "ignoredProperty" + } } extension Foo {