-
Notifications
You must be signed in to change notification settings - Fork 134
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
🚩RUM-1755 Add config overrides for debug launch arguments #679
Conversation
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
29457b0
to
6e3da95
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This looks great 🚀 well done with the configuration mutation and the console warning!
I have a suggestion re. the MobileDevice
singleton, LMKWYT!
|
||
// 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 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we can simplify this and avoid having to use a second singleton definition by creating a convenience init()
, like:
convenience init() {
#if !targetEnvironment(simulator)
// Real device
self.init(
uiDevice: UIDevice.current,
processInfo: ProcessInfo.processInfo,
notificationCenter: .default
)
#else
// iOS Simulator - battery monitoring doesn't work on Simulator, so return "always OK" value
self.init(
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
}
/// Returns current mobile device.
static var current = MobileDevice()
#if DD_SDK_COMPILED_FOR_TESTING
static func clearForTesting() {
current = MobileDevice()
}
#endif
Also the clearForTesting
might not be even necessary if we do:
func testSomething() {
MobileDevice.current = .mock()
defer { MobileDevice.current = .init() }
...
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That's a good call. I was originally thinking ok making set
only visible to testing, but that wasn't really possible so your solution ends up being a lot cleaner given what's possible.
Remove the _singleton private variable in favor of a statically initialized singleton.
@maxep Updated with your suggestions. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good contribution 👍, although I wonder if we couldn't achieve this in a simpler way. Here we propose a few new concepts like MobileDevice
singleton or Datadog.Configuration
mutability and I'm not sure if adding a debug utility is enough reason for this. I left separate comments, I wonder what's your take on these topics 🙂🙌.
Also, please leverage our GH PR template for next PRs 🚀 (what/how sections), as it's part of our contribution guide and really helps visiting past topics in the future 🙏.
enableBatteryStatusMonitoring: {}, | ||
resetBatteryStatusMonitoring: {}, | ||
currentBatteryStatus: { BatteryStatus(state: .full, level: 1, isLowPowerModeEnabled: false) } | ||
) | ||
#endif | ||
} | ||
|
||
static var current = MobileDevice() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I wonder if we have enough reasons for making MobileDevice
a singleton. Our existing architecture highly leverages dependency injection for keeping minimal number of different instances and MobileDevice
is already managed as single dependency created once and passed downstream to all modules through FeaturesCommonDependencies
. Changing it to singleton comes with additional cost:
- we need to manage its global state in tests, which can cause flakiness (if we want to go this path, it requires at least adding an integrity check for
MobileDevice
as we do with other globals); - as we're planning to support multiple SDK instances in V2, we'd need to refactor this anyway (to use DI).
That said, I'd rather vote on keeping MobileDevice
an instance-based component. To avoid confusion, we could even remove MobileDevice.current
API and only allow MobileDevice()
. I think the .current
is a leftover after series of refactors we've been doing to this class recently.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍 I'm largely against singletons as well. I'll look for a way to remove it cleanly in this PR if we want. I was mostly looking to disturb as little as possible for this particular PR.
Sources/Datadog/Datadog.swift
Outdated
// 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) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think that mobileDevice.processInfo.arguments.contains(_:)
reveals the Inappropriate Intimacy problem. From MobileDevice
class comment, I disagree that it should describe launch arguments:
/// Describes current mobile device.
internal class MobileDevice {
The intention for MobileDevice
is to provide our own abstraction for accessing specifics of user device, including model name, OS version or battery information. I think "process launch arguments" (and/or ENVs) are different class of problem and rather deserve separate component (at simplest case, direct use of ProcessInfo
would be IMO more cohesive and less coupled). WDYT @buranmert @maxep @fuzzybinary ?
// 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") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Very good idea with these prints 👌💪
Sources/Datadog/Datadog.swift
Outdated
let rebuilder = Configuration.Builder(configuration: mutableConfiguration) | ||
.set(rumSessionsSamplingRate: 100) | ||
.set(batchSize: .small) | ||
.set(uploadFrequency: .frequent) | ||
mutableConfiguration = rebuilder.build() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Instead of introducing mutability to Datadog.Configuration
(so, a new concept), I think we could leverage existing FeaturesConfiguration
and its config resolution skill:
extension FeaturesConfiguration {
/// Builds and validates the configuration for all features.
///
/// It takes two types received from the user: `Datadog.Configuration` and `AppContext` and blends them together
/// with resolving defaults and ensuring the configuration consistency.
///
/// Throws an error on invalid user input, i.e. broken custom URL.
/// Prints a warning if configuration is inconsistent, i.e. RUM is enabled, but RUM Application ID was not specified.
init(configuration: Datadog.Configuration, appContext: AppContext) throws {
The idea behind FeaturesConfiguration
is to take different sorts of configuration:
- user-specified
Datadog.Configuration
, AppContext
inferred from system
and create a single representation of config for all features. This includes operations like defaults resolution and validation. How about introducing third source of configuration to this init()
and let it take process info into account as well?
@ncreated Thinking about the best way to approach this, I'm thinking putting ProcessInfo into AppContext might actually be the simplest / best approach here that allows us use the system supplied default or inject a fake ProcessInfo without cluttering the public API. Thoughts? |
@fuzzybinary I think this is really good idea 👌! Extending "app context" with "launch arguments" sounds conceptually correct, and if thinking of multiple SDK instances - each would have it's own context, so that will be easy to scope this feature to the right instance of |
@ncreated Made the changes, and removed I did leave |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks 💯🚀! And really well done in testing 👍.
I'm adding needs-doc
label, so we can track this for future documentation work (usually we update docs later, after release).
186f16b
to
e83b919
Compare
Also move configuration manipulation into FeaturesConfiguration where that is possible.
e83b919
to
2c437f4
Compare
Adds two launch arguments:
DD_DEBUG sets sampling to 100%, verbosity to debug, and upload frequency to frequent
DD_DEBUG_RUM enables Datadog.debugRUM
All of these settings are changed during initialization to override whatever configuration the user set