From 6e3da955fb972e27ceb168f5bb15879856956777 Mon Sep 17 00:00:00 2001 From: Jeff Ward Date: Thu, 2 Dec 2021 09:19:44 -0500 Subject: [PATCH] =?UTF-8?q?=F0=9F=9A=A9RUM-1755=20Add=20config=20overrides?= =?UTF-8?q?=20for=20debug=20launch=20arguments?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds two launch arguments: DD_DEBUG sets sampling to 100%, verbosity to debug, and upload frequency to freqeunt DD_DEBUG_RUM enables Datadog.debugRUM All of these settings are changed during initialization to override whatever configuration the user set --- .../xcshareddata/xcschemes/Example.xcscheme | 10 +++ .../Datadog/Core/System/MobileDevice.swift | 63 +++++++++++++------ Sources/Datadog/Datadog.swift | 28 ++++++++- Sources/Datadog/DatadogConfiguration.swift | 5 ++ Tests/DatadogTests/Datadog/DatadogTests.swift | 63 +++++++++++++++++++ .../Datadog/Mocks/CoreMocks.swift | 2 + .../SystemFrameworks/FoundationMocks.swift | 6 +- 7 files changed, 157 insertions(+), 20 deletions(-) diff --git a/Datadog/Datadog.xcodeproj/xcshareddata/xcschemes/Example.xcscheme b/Datadog/Datadog.xcodeproj/xcshareddata/xcschemes/Example.xcscheme index e0b8de8a0d..6f3a6c31d6 100644 --- a/Datadog/Datadog.xcodeproj/xcshareddata/xcschemes/Example.xcscheme +++ b/Datadog/Datadog.xcodeproj/xcshareddata/xcschemes/Example.xcscheme @@ -50,6 +50,16 @@ ReferencedContainer = "container:Datadog.xcodeproj"> + + + + + + Void, resetBatteryStatusMonitoring: @escaping () -> Void, currentBatteryStatus: @escaping () -> BatteryStatus @@ -47,6 +49,7 @@ internal class MobileDevice { self.model = model self.osName = osName self.osVersion = osVersion + self.processInfo = processInfo self.enableBatteryStatusMonitoring = enableBatteryStatusMonitoring self.resetBatteryStatusMonitoring = resetBatteryStatusMonitoring self.currentBatteryStatus = currentBatteryStatus @@ -63,6 +66,7 @@ internal class MobileDevice { model: uiDevice.model, osName: uiDevice.systemName, osVersion: uiDevice.systemVersion, + processInfo: processInfo, enableBatteryStatusMonitoring: { uiDevice.isBatteryMonitoringEnabled = true }, resetBatteryStatusMonitoring: { uiDevice.isBatteryMonitoringEnabled = wasBatteryMonitoringEnabled }, currentBatteryStatus: { @@ -74,28 +78,51 @@ internal class MobileDevice { } ) } + + // MARK: - Singleton + private static var _instance: MobileDevice? + /// Returns current mobile device if `UIDevice` is available on this platform. /// On other platforms returns `nil`. static var current: MobileDevice { - #if !targetEnvironment(simulator) - // Real device - return MobileDevice( - uiDevice: UIDevice.current, - processInfo: ProcessInfo.processInfo, - notificationCenter: .default - ) - #else - // iOS Simulator - battery monitoring doesn't work on Simulator, so return "always OK" value - return MobileDevice( - model: UIDevice.current.model, - osName: UIDevice.current.systemName, - osVersion: UIDevice.current.systemVersion, - enableBatteryStatusMonitoring: {}, - resetBatteryStatusMonitoring: {}, - currentBatteryStatus: { BatteryStatus(state: .full, level: 1, isLowPowerModeEnabled: false) } - ) - #endif + get { + if let instance = _instance { + return instance + } + + #if !targetEnvironment(simulator) + // Real device + _instance = MobileDevice( + uiDevice: UIDevice.current, + processInfo: ProcessInfo.processInfo, + notificationCenter: .default + ) + #else + // iOS Simulator - battery monitoring doesn't work on Simulator, so return "always OK" value + _instance = MobileDevice( + model: UIDevice.current.model, + osName: UIDevice.current.systemName, + osVersion: UIDevice.current.systemVersion, + processInfo: ProcessInfo.processInfo, + enableBatteryStatusMonitoring: {}, + resetBatteryStatusMonitoring: {}, + currentBatteryStatus: { BatteryStatus(state: .full, level: 1, isLowPowerModeEnabled: false) } + ) + #endif + + // swiftlint:disable:next force_unwrapping + return _instance! + } + set(newInstance) { + _instance = newInstance + } + } + + #if DD_SDK_COMPILED_FOR_TESTING + static func clearForTesting() { + _instance = nil } + #endif private static func toBatteryState(_ uiDeviceBatteryState: UIDevice.BatteryState) -> BatteryStatus.State { switch uiDeviceBatteryState { diff --git a/Sources/Datadog/Datadog.swift b/Sources/Datadog/Datadog.swift index 1c09c18f03..7a1315912a 100644 --- a/Sources/Datadog/Datadog.swift +++ b/Sources/Datadog/Datadog.swift @@ -70,18 +70,40 @@ public class Datadog { trackingConsent: TrackingConsent, configuration: Configuration ) { + var mutableConfiguration = configuration + // TODO: RUMM-511 remove this warning #if targetEnvironment(macCatalyst) consolePrint("⚠️ Catalyst is not officially supported by Datadog SDK: some features may NOT be functional!") #endif + + let mobileDevice = MobileDevice.current + let debugOverride = mobileDevice.processInfo.arguments.contains(LaunchArguments.Debug) + if debugOverride { + consolePrint("⚠️ Overriding sampling, verbosity, and upload frequency due to \(LaunchArguments.Debug) launch argument") + let rebuilder = Configuration.Builder(configuration: mutableConfiguration) + .set(rumSessionsSamplingRate: 100) + .set(batchSize: .small) + .set(uploadFrequency: .frequent) + mutableConfiguration = rebuilder.build() + Datadog.verbosityLevel = .debug + } + do { try initializeOrThrow( initialTrackingConsent: trackingConsent, configuration: try FeaturesConfiguration( - configuration: configuration, + configuration: mutableConfiguration, appContext: appContext ) ) + + // Now that RUM is potentially initialized, override the debugRUM value + let debugRumOverride = mobileDevice.processInfo.arguments.contains(LaunchArguments.DebugRUM) + if debugRumOverride { + consolePrint("⚠️ Overriding RUM debugging due to \(LaunchArguments.DebugRUM) launch argument") + Datadog.debugRUM = true + } } catch { consolePrint("\(error)") } @@ -140,6 +162,10 @@ public class Datadog { } // MARK: - Internal + internal struct LaunchArguments { + static let Debug = "DD_DEBUG" + static let DebugRUM = "DD_DEBUG_RUM" + } internal static var instance: Datadog? diff --git a/Sources/Datadog/DatadogConfiguration.swift b/Sources/Datadog/DatadogConfiguration.swift index f66f4aad98..1070a5e8df 100644 --- a/Sources/Datadog/DatadogConfiguration.swift +++ b/Sources/Datadog/DatadogConfiguration.swift @@ -345,6 +345,11 @@ extension Datadog { ) } + /// Used internally to rebuild a configuration based on launch arguments + internal init(configuration: Configuration) { + self.configuration = configuration + } + /// Sets the Datadog server endpoint where data is sent. /// /// If set, it will override values set by any of these deprecated APIs: diff --git a/Tests/DatadogTests/Datadog/DatadogTests.swift b/Tests/DatadogTests/Datadog/DatadogTests.swift index 15b3c003b8..c97d79cb6b 100644 --- a/Tests/DatadogTests/Datadog/DatadogTests.swift +++ b/Tests/DatadogTests/Datadog/DatadogTests.swift @@ -18,6 +18,8 @@ class DatadogTests: XCTestCase { override func setUp() { super.setUp() + MobileDevice.clearForTesting() + XCTAssertFalse(Datadog.isInitialized) printFunction = PrintFunctionMock() consolePrint = printFunction.print @@ -329,6 +331,67 @@ class DatadogTests: XCTestCase { } } + func testSupplyingDebugLaunchArgument_itOverridesUserSettings() { + let mockProcessInfo = ProcessInfoMock( + arguments: [Datadog.LaunchArguments.Debug] + ) + + MobileDevice.current = MobileDevice.mockWith( + processInfo: mockProcessInfo + ) + + let configuration = rumBuilder + .set(uploadFrequency: .rare) + .set(rumSessionsSamplingRate: 20.0) + .set(batchSize: .medium) + .build() + + Datadog.initialize( + appContext: .mockAny(), + trackingConsent: .pending, + configuration: configuration + ) + + let expectedPerformancePreset = PerformancePreset( + batchSize: .small, + uploadFrequency: .frequent, + bundleType: .iOSApp + ) + XCTAssertEqual(RUMFeature.instance?.configuration.sessionSamplingRate, 100) + XCTAssertEqual(TracingFeature.instance?.configuration.common.performance, expectedPerformancePreset) + XCTAssertEqual(LoggingFeature.instance?.configuration.common.performance, expectedPerformancePreset) + XCTAssertEqual(Datadog.verbosityLevel, .debug) + + // Clear default verbosity after this test + Datadog.verbosityLevel = nil + Datadog.flushAndDeinitialize() + } + + func testSupplyingRumDebugLaunchArgument_itSetsRumDebug() { + let mockProcessInfo = ProcessInfoMock( + arguments: [Datadog.LaunchArguments.DebugRUM] + ) + + MobileDevice.current = MobileDevice.mockWith( + processInfo: mockProcessInfo + ) + + let configuration = rumBuilder + .build() + + Datadog.initialize( + appContext: .mockAny(), + trackingConsent: .pending, + configuration: configuration + ) + + XCTAssertTrue(Datadog.debugRUM) + + // Clear debug after test + Datadog.debugRUM = false + Datadog.flushAndDeinitialize() + } + // MARK: - Public APIs func testTrackingConsent() { diff --git a/Tests/DatadogTests/Datadog/Mocks/CoreMocks.swift b/Tests/DatadogTests/Datadog/Mocks/CoreMocks.swift index ad6b5aca02..76d79d06f2 100644 --- a/Tests/DatadogTests/Datadog/Mocks/CoreMocks.swift +++ b/Tests/DatadogTests/Datadog/Mocks/CoreMocks.swift @@ -786,6 +786,7 @@ extension MobileDevice { model: String = .mockAny(), osName: String = .mockAny(), osVersion: String = .mockAny(), + processInfo: ProcessInfo = ProcessInfoMock(), enableBatteryStatusMonitoring: @escaping () -> Void = {}, resetBatteryStatusMonitoring: @escaping () -> Void = {}, currentBatteryStatus: @escaping () -> BatteryStatus = { .mockAny() } @@ -794,6 +795,7 @@ extension MobileDevice { model: model, osName: osName, osVersion: osVersion, + processInfo: processInfo, enableBatteryStatusMonitoring: enableBatteryStatusMonitoring, resetBatteryStatusMonitoring: resetBatteryStatusMonitoring, currentBatteryStatus: currentBatteryStatus diff --git a/Tests/DatadogTests/Datadog/Mocks/SystemFrameworks/FoundationMocks.swift b/Tests/DatadogTests/Datadog/Mocks/SystemFrameworks/FoundationMocks.swift index 385bddcb40..46f4403461 100644 --- a/Tests/DatadogTests/Datadog/Mocks/SystemFrameworks/FoundationMocks.swift +++ b/Tests/DatadogTests/Datadog/Mocks/SystemFrameworks/FoundationMocks.swift @@ -408,12 +408,16 @@ extension URLRequest: AnyMockable { class ProcessInfoMock: ProcessInfo { private var _isLowPowerModeEnabled: Bool + private var _arguments: [String] - init(isLowPowerModeEnabled: Bool = .mockAny()) { + init(isLowPowerModeEnabled: Bool = .mockAny(), arguments: [String] = []) { _isLowPowerModeEnabled = isLowPowerModeEnabled + _arguments = arguments } override var isLowPowerModeEnabled: Bool { _isLowPowerModeEnabled } + + override var arguments: [String] { _arguments } } // MARK: - URLSession