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

[RUM-5393] Tracking non-fatal ANRs and app hangs #715

Merged
merged 1 commit into from
Oct 9, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -215,3 +215,15 @@ internal fun ReadableArray.toList(): List<*> {

return list
}

/**
* Returns the boolean for the given key, or null if the entry is
* not in the map.
*/
internal fun ReadableMap.getBooleanOrNull(key: String): Boolean? {
return if (hasKey(key)) {
getBoolean(key)
} else {
null
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ import java.net.Proxy
* @param firstPartyHosts List of backend hosts to enable tracing with.
* @param bundleLogsWithRum Enables RUM correlation with logs.
* @param bundleLogsWithTraces Enables Traces correlation with logs.
* @param trackNonFatalAnrs Enables tracking of non-fatal ANRs on Android.
*/
data class DdSdkConfiguration(
val clientToken: String,
Expand Down Expand Up @@ -64,7 +65,8 @@ data class DdSdkConfiguration(
val serviceName: String? = null,
val firstPartyHosts: Map<String, Set<TracingHeaderType>>? = null,
val bundleLogsWithRum: Boolean? = null,
val bundleLogsWithTraces: Boolean? = null
val bundleLogsWithTraces: Boolean? = null,
val trackNonFatalAnrs: Boolean? = null
)

internal data class JSONConfigurationFile(
Expand Down Expand Up @@ -95,7 +97,8 @@ internal data class JSONDdSdkConfiguration(
val serviceName: String? = null,
val firstPartyHosts: List<JSONFirstPartyHost>? = null,
val bundleLogsWithRum: Boolean? = null,
val bundleLogsWithTraces: Boolean? = null
val bundleLogsWithTraces: Boolean? = null,
val trackNonFatalAnrs: Boolean? = null
)

internal data class JSONProxyConfiguration(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,8 @@ internal fun ReadableMap.asDdSdkConfiguration(): DdSdkConfiguration {
serviceName = getString("serviceName"),
firstPartyHosts = getArray("firstPartyHosts")?.asFirstPartyHosts(),
bundleLogsWithRum = getBoolean("bundleLogsWithRum"),
bundleLogsWithTraces = getBoolean("bundleLogsWithTraces")
bundleLogsWithTraces = getBoolean("bundleLogsWithTraces"),
trackNonFatalAnrs = getBooleanOrNull("trackNonFatalAnrs")
)
}

Expand Down Expand Up @@ -169,7 +170,8 @@ internal fun JSONDdSdkConfiguration.asDdSdkConfiguration(): DdSdkConfiguration {
this.serviceName,
this.firstPartyHosts?.asFirstPartyHosts(),
this.bundleLogsWithRum ?: DefaultConfiguration.bundleLogsWithRum,
this.bundleLogsWithTraces ?: DefaultConfiguration.bundleLogsWithTraces
this.bundleLogsWithTraces ?: DefaultConfiguration.bundleLogsWithTraces,
this.trackNonFatalAnrs
)
}

Expand Down Expand Up @@ -231,6 +233,7 @@ internal fun DdSdkConfiguration.toReadableMap(): ReadableMap {
uploadFrequency?.let { map.putString("uploadFrequency", it) }
batchSize?.let { map.putString("batchSize", it) }
trackBackgroundEvents?.let { map.putBoolean("trackBackgroundEvents", it) }
trackNonFatalAnrs?.let { map.putBoolean("trackNonFatalAnrs", it) }
additionalConfig?.let { map.putMap("additionalConfig", it.toWritableMap()) }
return map
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,7 @@ class DdSdkNativeInitialization internal constructor(
?: DdSdkImplementation.DEFAULT_APP_VERSION
}

@Suppress("ComplexMethod")
private fun buildRumConfiguration(configuration: DdSdkConfiguration): RumConfiguration {
val configBuilder =
RumConfiguration.Builder(
Expand Down Expand Up @@ -196,6 +197,10 @@ class DdSdkNativeInitialization internal constructor(
configBuilder.useCustomEndpoint(it)
}

configuration.trackNonFatalAnrs?.let {
configBuilder.trackNonFatalAnrs(it)
}

return configBuilder.build()
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -241,6 +241,34 @@ internal class DdSdkBridgeExtTest {
assertThat(list).isEmpty()
}

@Test
fun `M returns a boolean W getBooleanOrNull { entry in the map }`() {
// Given
val readableMap = mapOf(
"testKey" to true
).toReadableMap()

// When
val value = readableMap.getBooleanOrNull("testKey")

// Then
assertThat(value).isTrue()
}

@Test
fun `M returns null W getBooleanOrNull { entry not in the map }`() {
// Given
val readableMap = mapOf(
"dummy" to false
).toReadableMap()

// When
val value = readableMap.getBooleanOrNull("testKey")

// Then
assertThat(value).isNull()
}

private fun getTestMap(): MutableMap<String, Any?> = mutableMapOf(
"null" to null,
"int" to 1,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,8 @@ fun DdSdkConfiguration.toReadableJavaOnlyMap(): ReadableMap {
map.put("bundleLogsWithRum", bundleLogsWithRum)
map.put("bundleLogsWithTraces", bundleLogsWithTraces)

trackNonFatalAnrs?.let { map.put("trackNonFatalAnrs", it) }

return map.toReadableMap()
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,8 @@ class DdSdkConfigurationForgeryFactory : ForgeryFactory<DdSdkConfiguration> {
serviceName = forge.aNullable { forge.anAlphabeticalString() },
firstPartyHosts = null,
bundleLogsWithRum = forge.aBool(),
bundleLogsWithTraces = forge.aBool()
bundleLogsWithTraces = forge.aBool(),
trackNonFatalAnrs = forge.aBool()
)
}
}
8 changes: 8 additions & 0 deletions packages/core/datadog-configuration.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -233,6 +233,14 @@
"bundleLogsWithTraces": {
"description": "Enables Traces correlation with logs.",
"type": "boolean"
},
"appHangThreshold": {
"description": "The app hang threshold in seconds for non-fatal app hangs on iOS.",
"type": "number"
},
"trackNonFatalAnrs": {
"description": "Enables tracking of non-fatal ANRs on Android.",
"type": "boolean"
}
},
"required": [
Expand Down
6 changes: 5 additions & 1 deletion packages/core/ios/Sources/DdSdkConfiguration.swift
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ import DatadogRUM
- firstPartyHosts: List of backend hosts to enable tracing with.
- bundleLogsWithRum: Correlates logs with RUM.
- bundleLogsWithTraces: Correlates logs with traces.
- appHangThreshold: The threshold for non-fatal app hangs reporting in seconds.
*/
@objc(DdSdkConfiguration)
public class DdSdkConfiguration: NSObject {
Expand Down Expand Up @@ -68,6 +69,7 @@ public class DdSdkConfiguration: NSObject {
public var resourceTracingSamplingRate: Double? = nil
public var bundleLogsWithRum: Bool
public var bundleLogsWithTraces: Bool
public var appHangThreshold: Double? = nil

public init(
clientToken: String,
Expand Down Expand Up @@ -96,7 +98,8 @@ public class DdSdkConfiguration: NSObject {
firstPartyHosts: [String: Set<TracingHeaderType>]?,
resourceTracingSamplingRate: Double?,
bundleLogsWithRum: Bool,
bundleLogsWithTraces: Bool
bundleLogsWithTraces: Bool,
appHangThreshold: Double?
) {
self.clientToken = clientToken
self.env = env
Expand Down Expand Up @@ -125,6 +128,7 @@ public class DdSdkConfiguration: NSObject {
self.resourceTracingSamplingRate = resourceTracingSamplingRate
self.bundleLogsWithRum = bundleLogsWithRum
self.bundleLogsWithTraces = bundleLogsWithTraces
self.appHangThreshold = appHangThreshold
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,7 @@ public class DdSdkNativeInitialization: NSObject {
trackFrustrations: configuration.trackFrustrations ?? true,
trackBackgroundEvents: configuration.trackBackgroundEvents ?? false,
longTaskThreshold: longTaskThreshold,
appHangThreshold: configuration.appHangThreshold,
vitalsUpdateFrequency: configuration.vitalsUpdateFrequency,
resourceEventMapper: { resourceEvent in
if resourceEvent.context?.contextInfo[InternalConfigurationAttributes.dropResource] != nil {
Expand Down
8 changes: 6 additions & 2 deletions packages/core/ios/Sources/RNDdSdkConfiguration.swift
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ extension NSDictionary {
let resourceTracingSamplingRate = object(forKey: "resourceTracingSamplingRate") as? Double
let bundleLogsWithRum = object(forKey: "bundleLogsWithRum") as? Bool
let bundleLogsWithTraces = object(forKey: "bundleLogsWithTraces") as? Bool
let appHangThreshold = object(forKey: "appHangThreshold") as? Double

return DdSdkConfiguration(
clientToken: (clientToken != nil) ? clientToken! : String(),
Expand Down Expand Up @@ -67,7 +68,8 @@ extension NSDictionary {
firstPartyHosts: firstPartyHosts?.asFirstPartyHosts(),
resourceTracingSamplingRate: resourceTracingSamplingRate,
bundleLogsWithRum: bundleLogsWithRum ?? DefaultConfiguration.bundleLogsWithRum,
bundleLogsWithTraces: bundleLogsWithTraces ?? DefaultConfiguration.bundleLogsWithTraces
bundleLogsWithTraces: bundleLogsWithTraces ?? DefaultConfiguration.bundleLogsWithTraces,
appHangThreshold: appHangThreshold
)
}

Expand Down Expand Up @@ -231,6 +233,7 @@ extension Dictionary where Key == String, Value == AnyObject {
let resourceTracingSamplingRate = configuration["resourceTracingSamplingRate"] as? Double
let bundleLogsWithRum = configuration["bundleLogsWithRum"] as? Bool
let bundleLogsWithTraces = configuration["bundleLogsWithTraces"] as? Bool
let appHangThreshold = configuration["appHangThreshold"] as? Double

return DdSdkConfiguration(
clientToken: clientToken ?? String(),
Expand Down Expand Up @@ -262,7 +265,8 @@ extension Dictionary where Key == String, Value == AnyObject {
firstPartyHosts: firstPartyHosts?.asFirstPartyHosts() ?? DefaultConfiguration.firstPartyHosts,
resourceTracingSamplingRate: resourceTracingSamplingRate ?? DefaultConfiguration.resourceTracingSamplingRate,
bundleLogsWithRum: bundleLogsWithRum ?? DefaultConfiguration.bundleLogsWithRum,
bundleLogsWithTraces: bundleLogsWithTraces ?? DefaultConfiguration.bundleLogsWithTraces
bundleLogsWithTraces: bundleLogsWithTraces ?? DefaultConfiguration.bundleLogsWithTraces,
appHangThreshold: appHangThreshold
)
}
}
Expand Down
6 changes: 4 additions & 2 deletions packages/core/ios/Tests/DdSdkTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -984,7 +984,8 @@ extension DdSdkConfiguration {
firstPartyHosts: [String: Set<TracingHeaderType>]? = nil,
resourceTracingSamplingRate: Double? = nil,
bundleLogsWithRum: Bool = true,
bundleLogsWithTraces: Bool = true
bundleLogsWithTraces: Bool = true,
appHangThreshold: Double? = nil
) -> DdSdkConfiguration {
DdSdkConfiguration(
clientToken: clientToken as String,
Expand Down Expand Up @@ -1013,7 +1014,8 @@ extension DdSdkConfiguration {
firstPartyHosts: firstPartyHosts,
resourceTracingSamplingRate: resourceTracingSamplingRate,
bundleLogsWithRum: bundleLogsWithRum,
bundleLogsWithTraces: bundleLogsWithTraces
bundleLogsWithTraces: bundleLogsWithTraces,
appHangThreshold: appHangThreshold
)
}
}
Expand Down
4 changes: 3 additions & 1 deletion packages/core/src/DdSdkReactNative.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -279,7 +279,9 @@ export class DdSdkReactNative {
configuration.serviceName,
formatFirstPartyHosts(configuration.firstPartyHosts),
configuration.bundleLogsWithRum,
configuration.bundleLogsWithTraces
configuration.bundleLogsWithTraces,
configuration.trackNonFatalAnrs,
configuration.appHangThreshold
);
};

Expand Down
24 changes: 24 additions & 0 deletions packages/core/src/DdSdkReactNativeConfiguration.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -267,6 +267,30 @@ export class DdSdkReactNativeConfiguration {
*/
public bundleLogsWithTraces: boolean = DEFAULTS.bundleLogsWithTraces;

/**
* Enables tracking of non-fatal ANRs on Android.

Choose a reason for hiding this comment

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

I'm wondering if we can call out these platform specific features in a better and more consistent way? Basically using the same language in both in the same place so that it's not missed...

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I was wondering the same thing. We could align on some good naming for these parameters, do you have any ideas?

Choose a reason for hiding this comment

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

Maybe "This option is specific to Platform" as the second line and remove any other mention of platforms?

* By default, the reporting of non-fatal ANRs on Android 30+ is disabled because it would
* create too much noise over fatal ANRs. On Android 29 and below, however,
* the reporting of non-fatal ANRs is enabled by default,
* as fatal ANRs cannot be reported on those versions.
*/
public trackNonFatalAnrs?: boolean;

/**
* The app hang threshold in seconds for non-fatal app hangs on iOS.
*
* App hangs are an iOS-specific type of error that happens when the application is unresponsive for too long.
* By default, app hangs reporting is disabled, but you can enable it and set your
* own threshold to monitor app hangs that last more than a specified
* duration by using the this parameter.
*
* Set the `appHangThreshold` parameter to the minimal duration you want
* app hangs to be reported. For example, enter 0.25 to report hangs lasting at least 250 ms.
* See [Configure the app hang threshold](https://docs.datadoghq.com/real_user_monitoring/error_tracking/mobile/ios/?tab=cocoapods#configure-the-app-hang-threshold)
* for more guidance on what to set this value to.
*/
public appHangThreshold?: number;

/**
* Specifies a custom prop to name RUM actions on elements having an `onPress` prop.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ describe('DatadogProvider', () => {
"additionalConfiguration": {
"_dd.source": "react-native",
},
"appHangThreshold": undefined,
"applicationId": "fakeApplicationId",
"batchSize": "MEDIUM",
"bundleLogsWithRum": true,
Expand Down Expand Up @@ -94,6 +95,7 @@ describe('DatadogProvider', () => {
"telemetrySampleRate": 20,
"trackBackgroundEvents": false,
"trackFrustrations": true,
"trackNonFatalAnrs": undefined,
"trackingConsent": "granted",
"uploadFrequency": "AVERAGE",
"verbosity": undefined,
Expand Down
4 changes: 3 additions & 1 deletion packages/core/src/types.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,9 @@ export class DdSdkConfiguration {
propagatorTypes: string[];
}[],
readonly bundleLogsWithRum: boolean,
readonly bundleLogsWithTraces: boolean
readonly bundleLogsWithTraces: boolean,
readonly trackNonFatalAnrs: boolean | undefined,
readonly appHangThreshold: number | undefined
) {}
}

Expand Down
Loading