diff --git a/Datadog/Datadog.xcodeproj/project.pbxproj b/Datadog/Datadog.xcodeproj/project.pbxproj index f28fad4b17..886b6b2181 100644 --- a/Datadog/Datadog.xcodeproj/project.pbxproj +++ b/Datadog/Datadog.xcodeproj/project.pbxproj @@ -1695,6 +1695,8 @@ E2AA55E82C32C6D9002FEF28 /* ApplicationNotifications.swift in Sources */ = {isa = PBXBuildFile; fileRef = E2AA55E62C32C6D9002FEF28 /* ApplicationNotifications.swift */; }; E2AA55EA2C32C76A002FEF28 /* WatchKitExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = E2AA55E92C32C76A002FEF28 /* WatchKitExtensions.swift */; }; E2AA55EC2C32C78B002FEF28 /* WatchKitExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = E2AA55E92C32C76A002FEF28 /* WatchKitExtensions.swift */; }; + F6E106542C75E0D000716DC6 /* LogsDataModels+objc.swift in Sources */ = {isa = PBXBuildFile; fileRef = F6E106532C75E0D000716DC6 /* LogsDataModels+objc.swift */; }; + F6E106552C75E0D000716DC6 /* LogsDataModels+objc.swift in Sources */ = {isa = PBXBuildFile; fileRef = F6E106532C75E0D000716DC6 /* LogsDataModels+objc.swift */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -3051,6 +3053,7 @@ E2AA55E62C32C6D9002FEF28 /* ApplicationNotifications.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ApplicationNotifications.swift; sourceTree = ""; }; E2AA55E92C32C76A002FEF28 /* WatchKitExtensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WatchKitExtensions.swift; sourceTree = ""; }; F637AED12697404200516F32 /* UIKitRUMUserActionsPredicate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIKitRUMUserActionsPredicate.swift; sourceTree = ""; }; + F6E106532C75E0D000716DC6 /* LogsDataModels+objc.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "LogsDataModels+objc.swift"; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -6279,6 +6282,7 @@ isa = PBXGroup; children = ( 61133C0C2423983800786299 /* Logs+objc.swift */, + F6E106532C75E0D000716DC6 /* LogsDataModels+objc.swift */, ); path = Logs; sourceTree = ""; @@ -8278,6 +8282,7 @@ 9E55407C25812D1C00F6E3AD /* RUM+objc.swift in Sources */, D2A434AA2A8E40A20028E329 /* SessionReplay+objc.swift in Sources */, 615A4A8D24A356A000233986 /* OTSpanContext+objc.swift in Sources */, + F6E106542C75E0D000716DC6 /* LogsDataModels+objc.swift in Sources */, 61133C112423983800786299 /* DatadogConfiguration+objc.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -9528,6 +9533,7 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + F6E106552C75E0D000716DC6 /* LogsDataModels+objc.swift in Sources */, D2CB6F9927C5217A00A62B57 /* Casting.swift in Sources */, D2CB6F9A27C5217A00A62B57 /* RUMDataModels+objc.swift in Sources */, D2CB6F9B27C5217A00A62B57 /* DDSpanContext+objc.swift in Sources */, diff --git a/DatadogCore/Tests/DatadogObjc/DDLogsTests.swift b/DatadogCore/Tests/DatadogObjc/DDLogsTests.swift index 8eb14f2f6e..f4a5eac7a7 100644 --- a/DatadogCore/Tests/DatadogObjc/DDLogsTests.swift +++ b/DatadogCore/Tests/DatadogObjc/DDLogsTests.swift @@ -271,6 +271,24 @@ class DDLogsTests: XCTestCase { XCTAssertEqual(objcConfig.configuration.remoteSampleRate, 50) XCTAssertNotNil(objcConfig.configuration.consoleLogFormat) } + + func testEventMapping() throws { + let logsConfiguration = DDLogsConfiguration() + logsConfiguration.setEventMapper { logEvent in + logEvent.message = "custom-log-message" + logEvent.attributes.userAttributes["custom-attribute"] = "custom-value" + return logEvent + } + DDLogs.enable(with: logsConfiguration) + + let objcLogger = DDLogger.create() + + objcLogger.debug("message") + + let logMatchers = try core.waitAndReturnLogMatchers() + logMatchers[0].assertMessage(equals: "custom-log-message") + logMatchers[0].assertAttributes(equal: ["custom-attribute": "custom-value"]) + } } // swiftlint:enable multiline_arguments_brackets // swiftlint:enable compiler_protocol_init diff --git a/DatadogObjc/Sources/Logs/Logs+objc.swift b/DatadogObjc/Sources/Logs/Logs+objc.swift index a41972f72d..342edbc774 100644 --- a/DatadogObjc/Sources/Logs/Logs+objc.swift +++ b/DatadogObjc/Sources/Logs/Logs+objc.swift @@ -71,6 +71,18 @@ public class DDLogsConfiguration: NSObject { customEndpoint: customEndpoint ) } + + /// Sets the custom mapper for `DDLogEvent`. This can be used to modify logs before they are send to Datadog. + /// + /// The implementation should obtain a mutable version of the `DDLogEvent`, modify it and return it. Returning `nil` will result + /// with dropping the Log event entirely, so it won't be send to Datadog. + @objc + public func setEventMapper(_ mapper: @escaping (DDLogEvent) -> DDLogEvent?) { + configuration.eventMapper = { swiftEvent in + let objcEvent = DDLogEvent(swiftModel: swiftEvent) + return mapper(objcEvent)?.swiftModel + } + } } @objc diff --git a/DatadogObjc/Sources/Logs/LogsDataModels+objc.swift b/DatadogObjc/Sources/Logs/LogsDataModels+objc.swift new file mode 100644 index 0000000000..7c8333bd24 --- /dev/null +++ b/DatadogObjc/Sources/Logs/LogsDataModels+objc.swift @@ -0,0 +1,486 @@ +/* + * 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-Present Datadog, Inc. + */ + +import Foundation +import DatadogLogs +import DatadogInternal + +@objc +public class DDLogEvent: NSObject { + internal var swiftModel: LogEvent + + internal init(swiftModel: LogEvent) { + self.swiftModel = swiftModel + } + + @objc public var date: Date { + swiftModel.date + } + + @objc public var status: DDLogEventStatus { + .init(swift: swiftModel.status) + } + + @objc public var message: String { + set { swiftModel.message = newValue } + get { swiftModel.message } + } + + @objc public var error: DDLogEventError? { + if swiftModel.error != nil { + .init(root: self) + } else { + nil + } + } + + @objc public var serviceName: String { + swiftModel.serviceName + } + + @objc public var environment: String { + swiftModel.environment + } + + @objc public var loggerName: String { + swiftModel.loggerName + } + + @objc public var loggerVersion: String { + swiftModel.loggerVersion + } + + @objc public var threadName: String? { + swiftModel.threadName + } + + @objc public var applicationVersion: String { + swiftModel.applicationVersion + } + + @objc public var applicationBuildNumber: String { + swiftModel.applicationBuildNumber + } + + @objc public var buildId: String? { + swiftModel.buildId + } + + @objc public var variant: String? { + swiftModel.variant + } + + @objc public var dd: DDLogEventDd { + .init(root: self) + } + + @objc public var os: DDLogEventOperatingSystem { + .init(root: self) + } + + @objc public var userInfo: DDLogEventUserInfo { + .init(root: self) + } + + @objc public var networkConnectionInfo: DDLogEventNetworkConnectionInfo? { + if swiftModel.networkConnectionInfo != nil { + .init(root: self) + } else { + nil + } + } + + @objc public var mobileCarrierInfo: DDLogEventCarrierInfo? { + if swiftModel.mobileCarrierInfo != nil { + .init(root: self) + } else { + nil + } + } + + @objc public var attributes: DDLogEventAttributes { + .init(root: self) + } + + @objc public var tags: [String]? { + set { swiftModel.tags = newValue } + get { swiftModel.tags } + } +} + +@objc +public enum DDLogEventStatus: Int { + internal init(swift: LogEvent.Status) { + switch swift { + case .debug: self = .debug + case .info: self = .info + case .notice: self = .notice + case .warn: self = .warn + case .error: self = .error + case .critical: self = .critical + case .emergency: self = .emergency + } + } + + internal var toSwift: LogEvent.Status { + switch self { + case .debug: return .debug + case .info: return .info + case .notice: return .notice + case .warn: return .warn + case .error: return .error + case .critical: return .critical + case .emergency: return .emergency + } + } + + case debug + case info + case notice + case warn + case error + case critical + case emergency +} + +@objc +public class DDLogEventAttributes: NSObject { + internal var root: DDLogEvent + + internal init(root: DDLogEvent) { + self.root = root + } + + @objc public var userAttributes: [String: Any] { + set { root.swiftModel.attributes.userAttributes = newValue.dd.swiftAttributes } + get { root.swiftModel.attributes.userAttributes.dd.objCAttributes } + } +} + +@objc +public class DDLogEventUserInfo: NSObject { + internal var root: DDLogEvent + + internal init(root: DDLogEvent) { + self.root = root + } + + @objc public var id: String? { + root.swiftModel.userInfo.id + } + + @objc public var name: String? { + root.swiftModel.userInfo.name + } + + @objc public var email: String? { + root.swiftModel.userInfo.email + } + + @objc public var extraInfo: [String: Any] { + set { root.swiftModel.userInfo.extraInfo = newValue.dd.swiftAttributes } + get { root.swiftModel.userInfo.extraInfo.dd.objCAttributes } + } +} + +@objc +public class DDLogEventError: NSObject { + internal var root: DDLogEvent + + internal init(root: DDLogEvent) { + self.root = root + } + + @objc public var kind: String? { + set { root.swiftModel.error?.kind = newValue } + get { root.swiftModel.error?.kind } + } + + @objc public var message: String? { + set { root.swiftModel.error?.message = newValue } + get { root.swiftModel.error?.message } + } + + @objc public var stack: String? { + set { root.swiftModel.error?.stack = newValue } + get { root.swiftModel.error?.stack } + } + + @objc public var sourceType: String { + // swiftlint:disable force_unwrapping + set { root.swiftModel.error!.sourceType = newValue } + get { root.swiftModel.error!.sourceType } + // swiftlint:enable force_unwrapping + } + + @objc public var fingerprint: String? { + set { root.swiftModel.error?.fingerprint = newValue } + get { root.swiftModel.error?.fingerprint } + } + + @objc public var binaryImages: [DDLogEventBinaryImage]? { + set { root.swiftModel.error?.binaryImages = newValue?.map { $0.swiftModel } } + get { root.swiftModel.error?.binaryImages?.map { DDLogEventBinaryImage(swiftModel: $0) } } + } +} + +@objc +public class DDLogEventBinaryImage: NSObject { + internal let swiftModel: LogEvent.Error.BinaryImage + + internal init(swiftModel: LogEvent.Error.BinaryImage) { + self.swiftModel = swiftModel + } + + @objc public var arch: String? { + swiftModel.arch + } + + @objc public var isSystem: Bool { + swiftModel.isSystem + } + + @objc public var loadAddress: String? { + swiftModel.loadAddress + } + + @objc public var maxAddress: String? { + swiftModel.maxAddress + } + + @objc public var name: String { + swiftModel.name + } + + @objc public var uuid: String { + swiftModel.uuid + } +} + +@objc +public class DDLogEventOperatingSystem: NSObject { + internal let root: DDLogEvent + + internal init(root: DDLogEvent) { + self.root = root + } + + @objc public var name: String { + root.swiftModel.os.name + } + + @objc public var version: String { + root.swiftModel.os.version + } + + @objc public var build: String? { + root.swiftModel.os.build + } +} + +@objc +public class DDLogEventDd: NSObject { + internal let root: DDLogEvent + + internal init(root: DDLogEvent) { + self.root = root + } + + @objc public var device: DDLogEventDeviceInfo { + .init(root: root) + } +} + +@objc +public class DDLogEventDeviceInfo: NSObject { + internal let root: DDLogEvent + + internal init(root: DDLogEvent) { + self.root = root + } + + @objc public var brand: String { + root.swiftModel.dd.device.brand + } + + @objc public var name: String { + root.swiftModel.dd.device.name + } + + @objc public var model: String { + root.swiftModel.dd.device.model + } + + @objc public var architecture: String { + root.swiftModel.dd.device.architecture + } +} + +@objc +public class DDLogEventNetworkConnectionInfo: NSObject { + internal let root: DDLogEvent + + internal init(root: DDLogEvent) { + self.root = root + } + + @objc public var reachability: DDLogEventReachability { + // swiftlint:disable force_unwrapping + .init(swift: root.swiftModel.networkConnectionInfo!.reachability) + // swiftlint:enable force_unwrapping + } + + @objc public var availableInterfaces: [Int]? { + root.swiftModel.networkConnectionInfo?.availableInterfaces?.map { DDLogEventInterface(swift: $0).rawValue } + } + + @objc public var supportsIPv4: NSNumber? { + root.swiftModel.networkConnectionInfo?.supportsIPv4 as NSNumber? + } + + @objc public var supportsIPv6: NSNumber? { + root.swiftModel.networkConnectionInfo?.supportsIPv6 as NSNumber? + } + + @objc public var isExpensive: NSNumber? { + root.swiftModel.networkConnectionInfo?.isExpensive as NSNumber? + } + + @objc public var isConstrained: NSNumber? { + root.swiftModel.networkConnectionInfo?.isConstrained as NSNumber? + } +} + +@objc +public enum DDLogEventReachability: Int { + internal init(swift: NetworkConnectionInfo.Reachability) { + switch swift { + case .yes: self = .yes + case .maybe: self = .maybe + case .no: self = .no + } + } + + internal var toSwift: NetworkConnectionInfo.Reachability { + switch self { + case .yes: return .yes + case .maybe: return .maybe + case .no: return .no + } + } + + case yes + case maybe + case no +} + +@objc +public enum DDLogEventInterface: Int { + internal init(swift: NetworkConnectionInfo.Interface) { + switch swift { + case .wifi: self = .wifi + case .wiredEthernet: self = .wiredEthernet + case .cellular: self = .cellular + case .loopback: self = .loopback + case .other: self = .other + } + } + + internal var toSwift: NetworkConnectionInfo.Interface { + switch self { + case .wifi: return .wifi + case .wiredEthernet: return .wiredEthernet + case .cellular: return .cellular + case .loopback: return .loopback + case .other: return .other + } + } + + case wifi + case wiredEthernet + case cellular + case loopback + case other +} + +@objc +public class DDLogEventCarrierInfo: NSObject { + internal let root: DDLogEvent + + internal init(root: DDLogEvent) { + self.root = root + } + + @objc public var carrierName: String? { + root.swiftModel.mobileCarrierInfo?.carrierName + } + + @objc public var carrierISOCountryCode: String? { + root.swiftModel.mobileCarrierInfo?.carrierISOCountryCode + } + + @objc public var carrierAllowsVOIP: Bool { + // swiftlint:disable force_unwrapping + root.swiftModel.mobileCarrierInfo!.carrierAllowsVOIP + // swiftlint:enable force_unwrapping + } + + @objc public var radioAccessTechnology: DDLogEventRadioAccessTechnology { + // swiftlint:disable force_unwrapping + .init(swift: root.swiftModel.mobileCarrierInfo!.radioAccessTechnology) + // swiftlint:enable force_unwrapping + } +} + +@objc +public enum DDLogEventRadioAccessTechnology: Int { + internal init(swift: CarrierInfo.RadioAccessTechnology) { + switch swift { + case .GPRS: self = .GPRS + case .Edge: self = .Edge + case .WCDMA: self = .WCDMA + case .HSDPA: self = .HSDPA + case .HSUPA: self = .HSUPA + case .CDMA1x: self = .CDMA1x + case .CDMAEVDORev0: self = .CDMAEVDORev0 + case .CDMAEVDORevA: self = .CDMAEVDORevA + case .CDMAEVDORevB: self = .CDMAEVDORevB + case .eHRPD: self = .eHRPD + case .LTE: self = .LTE + case .unknown: self = .unknown + } + } + + internal var toSwift: CarrierInfo.RadioAccessTechnology { + switch self { + case .GPRS: return .GPRS + case .Edge: return .Edge + case .WCDMA: return .WCDMA + case .HSDPA: return .HSDPA + case .HSUPA: return .HSUPA + case .CDMA1x: return .CDMA1x + case .CDMAEVDORev0: return .CDMAEVDORev0 + case .CDMAEVDORevA: return .CDMAEVDORevA + case .CDMAEVDORevB: return .CDMAEVDORevB + case .eHRPD: return .eHRPD + case .LTE: return .LTE + case .unknown: return .unknown + } + } + + case GPRS + case Edge + case WCDMA + case HSDPA + case HSUPA + case CDMA1x + case CDMAEVDORev0 + case CDMAEVDORevA + case CDMAEVDORevB + case eHRPD + case LTE + case unknown +} diff --git a/api-surface-objc b/api-surface-objc index 5bd385f0f1..42e2742185 100644 --- a/api-surface-objc +++ b/api-surface-objc @@ -89,6 +89,7 @@ public enum DDLogLevel: Int public class DDLogsConfiguration: NSObject @objc public var customEndpoint: URL? public init(customEndpoint: URL? = nil) + public func setEventMapper(_ mapper: @escaping (DDLogEvent) -> DDLogEvent?) public class DDLogs: NSObject public static func enable(with configuration: DDLogsConfiguration = .init()) public static func addAttribute(forKey key: String, value: Any) @@ -129,6 +130,102 @@ public class DDLogger: NSObject public func add(tag: String) public func remove(tag: String) public static func create(with configuration: DDLoggerConfiguration = .init()) -> DDLogger +public class DDLogEvent: NSObject + @objc public var date: Date + @objc public var status: DDLogEventStatus + @objc public var message: String + @objc public var error: DDLogEventError? + @objc public var serviceName: String + @objc public var environment: String + @objc public var loggerName: String + @objc public var loggerVersion: String + @objc public var threadName: String? + @objc public var applicationVersion: String + @objc public var applicationBuildNumber: String + @objc public var buildId: String? + @objc public var variant: String? + @objc public var dd: DDLogEventDd + @objc public var os: DDLogEventOperatingSystem + @objc public var userInfo: DDLogEventUserInfo + @objc public var networkConnectionInfo: DDLogEventNetworkConnectionInfo? + @objc public var mobileCarrierInfo: DDLogEventCarrierInfo? + @objc public var attributes: DDLogEventAttributes + @objc public var tags: [String]? +public enum DDLogEventStatus: Int + case debug + case info + case notice + case warn + case error + case critical + case emergency +public class DDLogEventAttributes: NSObject + @objc public var userAttributes: [String: Any] +public class DDLogEventUserInfo: NSObject + @objc public var id: String? + @objc public var name: String? + @objc public var email: String? + @objc public var extraInfo: [String: Any] +public class DDLogEventError: NSObject + @objc public var kind: String? + @objc public var message: String? + @objc public var stack: String? + @objc public var sourceType: String + @objc public var fingerprint: String? + @objc public var binaryImages: [DDLogEventBinaryImage]? +public class DDLogEventBinaryImage: NSObject + @objc public var arch: String? + @objc public var isSystem: Bool + @objc public var loadAddress: String? + @objc public var maxAddress: String? + @objc public var name: String + @objc public var uuid: String +public class DDLogEventOperatingSystem: NSObject + @objc public var name: String + @objc public var version: String + @objc public var build: String? +public class DDLogEventDd: NSObject + @objc public var device: DDLogEventDeviceInfo +public class DDLogEventDeviceInfo: NSObject + @objc public var brand: String + @objc public var name: String + @objc public var model: String + @objc public var architecture: String +public class DDLogEventNetworkConnectionInfo: NSObject + @objc public var reachability: DDLogEventReachability + @objc public var availableInterfaces: [Int]? + @objc public var supportsIPv4: NSNumber? + @objc public var supportsIPv6: NSNumber? + @objc public var isExpensive: NSNumber? + @objc public var isConstrained: NSNumber? +public enum DDLogEventReachability: Int + case yes + case maybe + case no +public enum DDLogEventInterface: Int + case wifi + case wiredEthernet + case cellular + case loopback + case other +public class DDLogEventCarrierInfo: NSObject + @objc public var carrierName: String? + @objc public var carrierISOCountryCode: String? + @objc public var carrierAllowsVOIP: Bool + @objc public var radioAccessTechnology: DDLogEventRadioAccessTechnology +public enum DDLogEventRadioAccessTechnology: Int + case GPRS + case Edge + case WCDMA + case HSDPA + case HSUPA + case CDMA1x + case CDMAEVDORev0 + case CDMAEVDORevA + case CDMAEVDORevB + case eHRPD + case LTE + case unknown public protocol OTSpan var context: OTSpanContext var tracer: OTTracer @@ -626,6 +723,7 @@ public enum DDRUMErrorEventErrorCategory: Int case appHang case exception case watchdogTermination + case memoryWarning public class DDRUMErrorEventErrorCauses: NSObject @objc public var message: String @objc public var source: DDRUMErrorEventErrorCausesSource @@ -1766,6 +1864,7 @@ public class DDTelemetryConfigurationEventTelemetryConfiguration: NSObject @objc public var sessionReplaySampleRate: NSNumber? @objc public var sessionSampleRate: NSNumber? @objc public var silentMultipleInit: NSNumber? + @objc public var startRecordingImmediately: NSNumber? @objc public var startSessionReplayRecordingManually: NSNumber? @objc public var storeContextsAcrossPages: NSNumber? @objc public var telemetryConfigurationSampleRate: NSNumber? diff --git a/api-surface-swift b/api-surface-swift index babe781148..49b8d3db02 100644 --- a/api-surface-swift +++ b/api-surface-swift @@ -541,6 +541,7 @@ public struct RUMErrorEvent: RUMDataModel case appHang = "App Hang" case exception = "Exception" case watchdogTermination = "Watchdog Termination" + case memoryWarning = "Memory Warning" public struct Causes: Codable public var message: String public let source: Source @@ -1320,6 +1321,7 @@ public struct TelemetryConfigurationEvent: RUMDataModel public var sessionReplaySampleRate: Int64? public let sessionSampleRate: Int64? public let silentMultipleInit: Bool? + public var startRecordingImmediately: Bool? public var startSessionReplayRecordingManually: Bool? public let storeContextsAcrossPages: Bool? public let telemetryConfigurationSampleRate: Int64? @@ -1588,7 +1590,7 @@ public extension RUMMonitorProtocol func stopView(viewController: UIViewController,attributes: [AttributeKey: AttributeValue] = [:]) func startView(key: String,name: String? = nil,attributes: [AttributeKey: AttributeValue] = [:]) func stopView(key: String,attributes: [AttributeKey: AttributeValue] = [:]) - func addError(message: String,type: String? = nil,stack: String? = nil,source: RUMErrorSource = .custom,attributes: [AttributeKey: AttributeValue] = [:],file: StaticString? = #filePath,line: UInt? = #line) + func addError(message: String,type: String? = nil,stack: String? = nil,source: RUMErrorSource = .custom,attributes: [AttributeKey: AttributeValue] = [:],file: StaticString? = #fileID,line: UInt? = #line) func addError(error: Error,source: RUMErrorSource = .custom,attributes: [AttributeKey: AttributeValue] = [:]) func startResource(resourceKey: String,request: URLRequest,attributes: [AttributeKey: AttributeValue] = [:]) func startResource(resourceKey: String,url: URL,attributes: [AttributeKey: AttributeValue] = [:]) @@ -2048,11 +2050,14 @@ public enum DDSessionReplayConfigurationPrivacyLevel: Int case mask case maskUserInput public enum SessionReplay - public static func enable(with configuration: SessionReplay.Configuration, in core: DatadogCoreProtocol = CoreRegistry.default) + public static func enable(with configuration: SessionReplay.Configuration,in core: DatadogCoreProtocol = CoreRegistry.default) + public static func startRecording(in core: DatadogCoreProtocol = CoreRegistry.default) + public static func stopRecording(in core: DatadogCoreProtocol = CoreRegistry.default) [?] extension SessionReplay public struct Configuration public var replaySampleRate: Float public var defaultPrivacyLevel: SessionReplayPrivacyLevel + public var startRecordingImmediately: Bool public var customEndpoint: URL? - public init(replaySampleRate: Float,defaultPrivacyLevel: SessionReplayPrivacyLevel = .mask,customEndpoint: URL? = nil) + public init(replaySampleRate: Float,defaultPrivacyLevel: SessionReplayPrivacyLevel = .mask,startRecordingImmediately: Bool = true,customEndpoint: URL? = nil) public mutating func setAdditionalNodeRecorders(_ additionalNodeRecorders: [SessionReplayNodeRecorder])