@grafana/faro-react-native 1.2.0 → 1.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/android/build.gradle +118 -36
- package/android/consumer-proguard-rules.pro +2 -0
- package/android/src/main/java/com/grafana/faro/reactnative/ANRTracker.kt +61 -9
- package/android/src/main/java/com/grafana/faro/reactnative/AnrThreadDumpParser.kt +70 -0
- package/android/src/main/java/com/grafana/faro/reactnative/ApplicationExitTraceReader.kt +142 -0
- package/android/src/main/java/com/grafana/faro/reactnative/FaroAnrCache.kt +163 -0
- package/android/src/main/java/com/grafana/faro/reactnative/FaroAnrReporter.kt +163 -0
- package/android/src/main/java/com/grafana/faro/reactnative/FaroCrashReporter.kt +243 -50
- package/android/src/main/java/com/grafana/faro/reactnative/FaroCrashTraceCache.kt +75 -0
- package/android/src/main/java/com/grafana/faro/reactnative/FaroNativeCrashTrace.kt +39 -0
- package/android/src/main/java/com/grafana/faro/reactnative/FaroReactNativeModule.kt +99 -8
- package/android/src/main/java/com/grafana/faro/reactnative/FaroReactNativePackage.kt +1 -0
- package/android/src/main/java/com/grafana/faro/reactnative/FaroUncaughtExceptionHandler.kt +62 -0
- package/android/src/main/java/com/grafana/faro/reactnative/TombstoneBacktraceFormatter.kt +117 -0
- package/android/src/main/java/com/grafana/faro/reactnative/TombstoneModels.kt +35 -0
- package/android/src/main/java/com/grafana/faro/reactnative/TombstoneWireParser.kt +235 -0
- package/android/src/main/proto/tombstone.proto +62 -0
- package/android/src/test/java/com/grafana/faro/reactnative/AnrStackFiltersTest.kt +28 -0
- package/android/src/test/java/com/grafana/faro/reactnative/AnrThreadDumpParserTest.kt +38 -0
- package/android/src/test/java/com/grafana/faro/reactnative/FaroAnrCacheTest.kt +81 -0
- package/android/src/test/java/com/grafana/faro/reactnative/TombstoneWireParserTest.kt +51 -0
- package/dist/cjs/config/getRNInstrumentations.js +3 -1
- package/dist/cjs/config/getRNInstrumentations.js.map +1 -1
- package/dist/cjs/config/makeRNConfig.js +48 -9
- package/dist/cjs/config/makeRNConfig.js.map +1 -1
- package/dist/cjs/config/types.js.map +1 -1
- package/dist/cjs/generated/faroRNPackageMeta.js +1 -1
- package/dist/cjs/generated/faroRNPackageMeta.js.map +1 -1
- package/dist/cjs/index.js +3 -2
- package/dist/cjs/index.js.map +1 -1
- package/dist/cjs/initialize.js +14 -8
- package/dist/cjs/initialize.js.map +1 -1
- package/dist/cjs/instrumentations/anr/anrStack.js +19 -0
- package/dist/cjs/instrumentations/anr/anrStack.js.map +1 -0
- package/dist/cjs/instrumentations/anr/instrumentation.js +374 -72
- package/dist/cjs/instrumentations/anr/instrumentation.js.map +1 -1
- package/dist/cjs/instrumentations/anr/types.js.map +1 -1
- package/dist/cjs/instrumentations/console/index.js +5 -1
- package/dist/cjs/instrumentations/console/index.js.map +1 -1
- package/dist/cjs/instrumentations/crashReporting/{instrumentation.js → BaseCrashReportingInstrumentation.js} +110 -103
- package/dist/cjs/instrumentations/crashReporting/BaseCrashReportingInstrumentation.js.map +1 -0
- package/dist/cjs/instrumentations/crashReporting/NoOpCrashReportingInstrumentation.js +41 -0
- package/dist/cjs/instrumentations/crashReporting/NoOpCrashReportingInstrumentation.js.map +1 -0
- package/dist/cjs/instrumentations/crashReporting/android/AndroidCrashReportingInstrumentation.js +87 -0
- package/dist/cjs/instrumentations/crashReporting/android/AndroidCrashReportingInstrumentation.js.map +1 -0
- package/dist/cjs/instrumentations/crashReporting/android/crashErrorMessage.js +123 -0
- package/dist/cjs/instrumentations/crashReporting/android/crashErrorMessage.js.map +1 -0
- package/dist/cjs/instrumentations/crashReporting/android/index.js +12 -0
- package/dist/cjs/instrumentations/crashReporting/android/index.js.map +1 -0
- package/dist/cjs/instrumentations/crashReporting/android/nativeTombstoneTrace.js +11 -0
- package/dist/cjs/instrumentations/crashReporting/android/nativeTombstoneTrace.js.map +1 -0
- package/dist/cjs/instrumentations/crashReporting/android/parseAndroidCrashTrace.js +181 -0
- package/dist/cjs/instrumentations/crashReporting/android/parseAndroidCrashTrace.js.map +1 -0
- package/dist/cjs/instrumentations/crashReporting/index.js +35 -2
- package/dist/cjs/instrumentations/crashReporting/index.js.map +1 -1
- package/dist/cjs/instrumentations/crashReporting/ios/IosCrashReportingInstrumentation.js +78 -0
- package/dist/cjs/instrumentations/crashReporting/ios/IosCrashReportingInstrumentation.js.map +1 -0
- package/dist/cjs/instrumentations/crashReporting/ios/crashErrorMessage.js +103 -0
- package/dist/cjs/instrumentations/crashReporting/ios/crashErrorMessage.js.map +1 -0
- package/dist/cjs/instrumentations/crashReporting/ios/index.js +11 -0
- package/dist/cjs/instrumentations/crashReporting/ios/index.js.map +1 -0
- package/dist/cjs/instrumentations/crashReporting/ios/parseIosCrashTrace.js +48 -0
- package/dist/cjs/instrumentations/crashReporting/ios/parseIosCrashTrace.js.map +1 -0
- package/dist/cjs/instrumentations/crashReporting/types.js.map +1 -1
- package/dist/cjs/instrumentations/errors/index.js +2 -1
- package/dist/cjs/instrumentations/errors/index.js.map +1 -1
- package/dist/cjs/instrumentations/session/installationId.js +91 -0
- package/dist/cjs/instrumentations/session/installationId.js.map +1 -0
- package/dist/cjs/instrumentations/session/sessionAttributes.js +152 -89
- package/dist/cjs/instrumentations/session/sessionAttributes.js.map +1 -1
- package/dist/cjs/metas/appBuildIdentity.js +95 -0
- package/dist/cjs/metas/appBuildIdentity.js.map +1 -0
- package/dist/cjs/testUtils/mockDeviceInfo.js +5 -0
- package/dist/cjs/testUtils/mockDeviceInfo.js.map +1 -1
- package/dist/cjs/testUtils/setupTests.js +7 -0
- package/dist/cjs/testUtils/setupTests.js.map +1 -1
- package/dist/esm/config/getRNInstrumentations.js +3 -1
- package/dist/esm/config/getRNInstrumentations.js.map +1 -1
- package/dist/esm/config/makeRNConfig.js +49 -10
- package/dist/esm/config/makeRNConfig.js.map +1 -1
- package/dist/esm/config/types.js.map +1 -1
- package/dist/esm/generated/faroRNPackageMeta.js +1 -1
- package/dist/esm/generated/faroRNPackageMeta.js.map +1 -1
- package/dist/esm/index.js +1 -1
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/initialize.js +11 -5
- package/dist/esm/initialize.js.map +1 -1
- package/dist/esm/instrumentations/anr/anrStack.js +16 -0
- package/dist/esm/instrumentations/anr/anrStack.js.map +1 -0
- package/dist/esm/instrumentations/anr/instrumentation.js +260 -67
- package/dist/esm/instrumentations/anr/instrumentation.js.map +1 -1
- package/dist/esm/instrumentations/anr/types.js.map +1 -1
- package/dist/esm/instrumentations/console/index.js +5 -1
- package/dist/esm/instrumentations/console/index.js.map +1 -1
- package/dist/esm/instrumentations/crashReporting/{instrumentation.js → BaseCrashReportingInstrumentation.js} +76 -94
- package/dist/esm/instrumentations/crashReporting/BaseCrashReportingInstrumentation.js.map +1 -0
- package/dist/esm/instrumentations/crashReporting/NoOpCrashReportingInstrumentation.js +18 -0
- package/dist/esm/instrumentations/crashReporting/NoOpCrashReportingInstrumentation.js.map +1 -0
- package/dist/esm/instrumentations/crashReporting/android/AndroidCrashReportingInstrumentation.js +56 -0
- package/dist/esm/instrumentations/crashReporting/android/AndroidCrashReportingInstrumentation.js.map +1 -0
- package/dist/esm/instrumentations/crashReporting/android/crashErrorMessage.js +116 -0
- package/dist/esm/instrumentations/crashReporting/android/crashErrorMessage.js.map +1 -0
- package/dist/esm/instrumentations/crashReporting/android/index.js +4 -0
- package/dist/esm/instrumentations/crashReporting/android/index.js.map +1 -0
- package/dist/esm/instrumentations/crashReporting/android/nativeTombstoneTrace.js +8 -0
- package/dist/esm/instrumentations/crashReporting/android/nativeTombstoneTrace.js.map +1 -0
- package/dist/esm/instrumentations/crashReporting/android/parseAndroidCrashTrace.js +174 -0
- package/dist/esm/instrumentations/crashReporting/android/parseAndroidCrashTrace.js.map +1 -0
- package/dist/esm/instrumentations/crashReporting/index.js +35 -1
- package/dist/esm/instrumentations/crashReporting/index.js.map +1 -1
- package/dist/esm/instrumentations/crashReporting/ios/IosCrashReportingInstrumentation.js +56 -0
- package/dist/esm/instrumentations/crashReporting/ios/IosCrashReportingInstrumentation.js.map +1 -0
- package/dist/esm/instrumentations/crashReporting/ios/crashErrorMessage.js +98 -0
- package/dist/esm/instrumentations/crashReporting/ios/crashErrorMessage.js.map +1 -0
- package/dist/esm/instrumentations/crashReporting/ios/index.js +4 -0
- package/dist/esm/instrumentations/crashReporting/ios/index.js.map +1 -0
- package/dist/esm/instrumentations/crashReporting/ios/parseIosCrashTrace.js +44 -0
- package/dist/esm/instrumentations/crashReporting/ios/parseIosCrashTrace.js.map +1 -0
- package/dist/esm/instrumentations/crashReporting/types.js.map +1 -1
- package/dist/esm/instrumentations/errors/index.js +2 -1
- package/dist/esm/instrumentations/errors/index.js.map +1 -1
- package/dist/esm/instrumentations/session/installationId.js +48 -0
- package/dist/esm/instrumentations/session/installationId.js.map +1 -0
- package/dist/esm/instrumentations/session/sessionAttributes.js +83 -38
- package/dist/esm/instrumentations/session/sessionAttributes.js.map +1 -1
- package/dist/esm/metas/appBuildIdentity.js +56 -0
- package/dist/esm/metas/appBuildIdentity.js.map +1 -0
- package/dist/esm/testUtils/mockDeviceInfo.js +5 -0
- package/dist/esm/testUtils/mockDeviceInfo.js.map +1 -1
- package/dist/esm/testUtils/setupTests.js +7 -0
- package/dist/esm/testUtils/setupTests.js.map +1 -1
- package/dist/types/config/makeRNConfig.d.ts +4 -3
- package/dist/types/config/types.d.ts +2 -1
- package/dist/types/generated/faroRNPackageMeta.d.ts +1 -1
- package/dist/types/index.d.ts +2 -2
- package/dist/types/initialize.d.ts +4 -2
- package/dist/types/instrumentations/anr/anrStack.d.ts +7 -0
- package/dist/types/instrumentations/anr/instrumentation.d.ts +23 -28
- package/dist/types/instrumentations/anr/types.d.ts +5 -1
- package/dist/types/instrumentations/appState/index.d.ts +1 -1
- package/dist/types/instrumentations/console/index.d.ts +1 -1
- package/dist/types/instrumentations/crashReporting/BaseCrashReportingInstrumentation.d.ts +56 -0
- package/dist/types/instrumentations/crashReporting/NoOpCrashReportingInstrumentation.d.ts +12 -0
- package/dist/types/instrumentations/crashReporting/android/AndroidCrashReportingInstrumentation.d.ts +26 -0
- package/dist/types/instrumentations/crashReporting/android/crashErrorMessage.d.ts +23 -0
- package/dist/types/instrumentations/crashReporting/android/index.d.ts +4 -0
- package/dist/types/instrumentations/crashReporting/android/nativeTombstoneTrace.d.ts +2 -0
- package/dist/types/instrumentations/crashReporting/android/parseAndroidCrashTrace.d.ts +45 -0
- package/dist/types/instrumentations/crashReporting/index.d.ts +25 -2
- package/dist/types/instrumentations/crashReporting/ios/IosCrashReportingInstrumentation.d.ts +28 -0
- package/dist/types/instrumentations/crashReporting/ios/crashErrorMessage.d.ts +14 -0
- package/dist/types/instrumentations/crashReporting/ios/index.d.ts +4 -0
- package/dist/types/instrumentations/crashReporting/ios/parseIosCrashTrace.d.ts +38 -0
- package/dist/types/instrumentations/crashReporting/types.d.ts +18 -51
- package/dist/types/instrumentations/frameMonitoring/instrumentation.d.ts +1 -1
- package/dist/types/instrumentations/http/index.d.ts +1 -1
- package/dist/types/instrumentations/performance/index.d.ts +1 -1
- package/dist/types/instrumentations/session/index.d.ts +1 -1
- package/dist/types/instrumentations/session/installationId.d.ts +1 -0
- package/dist/types/instrumentations/session/sessionAttributes.d.ts +13 -4
- package/dist/types/instrumentations/startup/index.d.ts +1 -1
- package/dist/types/instrumentations/userActions/index.d.ts +1 -1
- package/dist/types/instrumentations/view/index.d.ts +1 -1
- package/dist/types/instrumentations/xhr/index.d.ts +1 -1
- package/dist/types/metas/appBuildIdentity.d.ts +16 -0
- package/dist/types/testUtils/mockFaroReactNativeTracing.d.ts +1 -1
- package/dist/types/transports/console/transport.d.ts +1 -1
- package/dist/types/transports/fetch/transport.d.ts +1 -1
- package/dist/types/transports/offline/transport.d.ts +1 -1
- package/ios/faro-upload-composed-source-map.sh +8 -2
- package/package.json +5 -5
- package/src/config/getRNInstrumentations.ts +5 -1
- package/src/config/makeRNConfig.test.ts +59 -3
- package/src/config/makeRNConfig.ts +48 -9
- package/src/config/types.ts +2 -1
- package/src/generated/faroRNPackageMeta.ts +1 -1
- package/src/index.ts +2 -1
- package/src/initialize.test.ts +71 -15
- package/src/initialize.ts +11 -5
- package/src/instrumentations/anr/anrStack.test.ts +27 -0
- package/src/instrumentations/anr/anrStack.ts +19 -0
- package/src/instrumentations/anr/instrumentation.test.ts +45 -0
- package/src/instrumentations/anr/instrumentation.ts +301 -67
- package/src/instrumentations/anr/types.ts +7 -1
- package/src/instrumentations/console/index.ts +5 -1
- package/src/instrumentations/console/instrumentation.test.ts +5 -0
- package/src/instrumentations/crashReporting/{instrumentation.ts → BaseCrashReportingInstrumentation.ts} +119 -93
- package/src/instrumentations/crashReporting/NoOpCrashReportingInstrumentation.ts +23 -0
- package/src/instrumentations/crashReporting/android/AndroidCrashReportingInstrumentation.ts +62 -0
- package/src/instrumentations/crashReporting/android/crashErrorMessage.test.ts +182 -0
- package/src/instrumentations/crashReporting/android/crashErrorMessage.ts +135 -0
- package/src/instrumentations/crashReporting/android/index.ts +4 -0
- package/src/instrumentations/crashReporting/android/nativeTombstoneTrace.test.ts +19 -0
- package/src/instrumentations/crashReporting/android/nativeTombstoneTrace.ts +8 -0
- package/src/instrumentations/crashReporting/android/parseAndroidCrashTrace.test.ts +232 -0
- package/src/instrumentations/crashReporting/android/parseAndroidCrashTrace.ts +235 -0
- package/src/instrumentations/crashReporting/index.ts +43 -2
- package/src/instrumentations/crashReporting/instrumentation.test.ts +42 -0
- package/src/instrumentations/crashReporting/ios/IosCrashReportingInstrumentation.ts +62 -0
- package/src/instrumentations/crashReporting/ios/crashErrorMessage.test.ts +170 -0
- package/src/instrumentations/crashReporting/ios/crashErrorMessage.ts +111 -0
- package/src/instrumentations/crashReporting/ios/index.ts +4 -0
- package/src/instrumentations/crashReporting/ios/parseIosCrashTrace.test.ts +86 -0
- package/src/instrumentations/crashReporting/ios/parseIosCrashTrace.ts +83 -0
- package/src/instrumentations/crashReporting/types.ts +19 -58
- package/src/instrumentations/errors/index.ts +2 -1
- package/src/instrumentations/errors/instrumentation.test.ts +8 -2
- package/src/instrumentations/errors/stackTraceParser.test.ts +1 -1
- package/src/instrumentations/session/installationId.ts +43 -0
- package/src/instrumentations/session/sessionAttributes.test.ts +260 -30
- package/src/instrumentations/session/sessionAttributes.ts +112 -22
- package/src/metas/appBuildIdentity.test.ts +48 -0
- package/src/metas/appBuildIdentity.ts +50 -0
- package/src/testUtils/mockDeviceInfo.ts +5 -0
- package/src/testUtils/setupTests.ts +7 -0
- package/dist/cjs/instrumentations/crashReporting/instrumentation.js.map +0 -1
- package/dist/esm/instrumentations/crashReporting/instrumentation.js.map +0 -1
- package/dist/types/instrumentations/crashReporting/instrumentation.d.ts +0 -58
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
package com.grafana.faro.reactnative
|
|
2
|
+
|
|
3
|
+
import android.content.Context
|
|
4
|
+
import android.content.SharedPreferences
|
|
5
|
+
import org.json.JSONArray
|
|
6
|
+
import org.json.JSONObject
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Persists pending ANR payloads between detection and JS delivery.
|
|
10
|
+
*
|
|
11
|
+
* ANRs are written with [commit] on the ANR tracker thread as soon as the 5s
|
|
12
|
+
* threshold fires, so a process kill before the React bridge can emit still
|
|
13
|
+
* leaves a recoverable record for the next launch.
|
|
14
|
+
*/
|
|
15
|
+
internal object FaroAnrCache {
|
|
16
|
+
private const val PREFS_NAME = "com.grafana.faro.anr_cache"
|
|
17
|
+
private const val KEY_PENDING_ANRS = "pending_anrs"
|
|
18
|
+
private const val KEY_DETECTED_ANR_TIMESTAMPS = "detected_anr_timestamps"
|
|
19
|
+
private const val MAX_PENDING_ENTRIES = 50
|
|
20
|
+
private const val MAX_DETECTED_TIMESTAMP_ENTRIES = 50
|
|
21
|
+
/** Match window between ANRTracker detection time and ApplicationExitInfo exit time. */
|
|
22
|
+
private const val MAX_TIMESTAMP_DELTA_MS = 10_000L
|
|
23
|
+
|
|
24
|
+
fun savePendingAnr(context: Context, payload: String) {
|
|
25
|
+
val newObj = try {
|
|
26
|
+
JSONObject(payload)
|
|
27
|
+
} catch (_: Exception) {
|
|
28
|
+
return
|
|
29
|
+
}
|
|
30
|
+
val newTimestamp = newObj.optLong("timestamp", 0L)
|
|
31
|
+
if (newTimestamp == 0L) {
|
|
32
|
+
return
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
val prefs = context.applicationContext.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE)
|
|
36
|
+
val array = JSONArray(prefs.getString(KEY_PENDING_ANRS, "[]") ?: "[]")
|
|
37
|
+
|
|
38
|
+
for (i in 0 until array.length()) {
|
|
39
|
+
val existing = array.optJSONObject(i) ?: continue
|
|
40
|
+
val existingTimestamp = existing.optLong("timestamp", 0L)
|
|
41
|
+
if (existingTimestamp == newTimestamp) {
|
|
42
|
+
return
|
|
43
|
+
}
|
|
44
|
+
if (isWithinDetectionWindow(existingTimestamp, newTimestamp)) {
|
|
45
|
+
// Collapse repeated tracker detections for the same blocked period.
|
|
46
|
+
return
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
array.put(newObj)
|
|
51
|
+
trimOldestEntries(array)
|
|
52
|
+
|
|
53
|
+
recordDetectionTimestamp(prefs, newTimestamp)
|
|
54
|
+
|
|
55
|
+
// commit() not apply(): the process may be killed immediately after ANR detection.
|
|
56
|
+
prefs.edit()
|
|
57
|
+
.putString(KEY_PENDING_ANRS, array.toString())
|
|
58
|
+
.commit()
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Returns true when Faro already recorded an ANR near [exitTimestampMs].
|
|
63
|
+
* Used to skip ApplicationExitInfo crash replays of the same blocked-main-thread event.
|
|
64
|
+
*/
|
|
65
|
+
fun hasNearbyAnrDetection(context: Context, exitTimestampMs: Long): Boolean {
|
|
66
|
+
if (exitTimestampMs <= 0L) {
|
|
67
|
+
return false
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
for (payload in getPendingAnrs(context)) {
|
|
71
|
+
val timestamp = try {
|
|
72
|
+
JSONObject(payload).optLong("timestamp", 0L)
|
|
73
|
+
} catch (_: Exception) {
|
|
74
|
+
0L
|
|
75
|
+
}
|
|
76
|
+
if (isWithinDetectionWindow(timestamp, exitTimestampMs)) {
|
|
77
|
+
return true
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
val prefs = context.applicationContext.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE)
|
|
82
|
+
val detected = JSONArray(prefs.getString(KEY_DETECTED_ANR_TIMESTAMPS, "[]") ?: "[]")
|
|
83
|
+
for (i in 0 until detected.length()) {
|
|
84
|
+
val timestamp = detected.optLong(i, 0L)
|
|
85
|
+
if (isWithinDetectionWindow(timestamp, exitTimestampMs)) {
|
|
86
|
+
return true
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
return false
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
private fun recordDetectionTimestamp(prefs: SharedPreferences, timestampMs: Long) {
|
|
94
|
+
val detected = JSONArray(prefs.getString(KEY_DETECTED_ANR_TIMESTAMPS, "[]") ?: "[]")
|
|
95
|
+
for (i in 0 until detected.length()) {
|
|
96
|
+
if (detected.optLong(i, 0L) == timestampMs) {
|
|
97
|
+
return
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
detected.put(timestampMs)
|
|
101
|
+
while (detected.length() > MAX_DETECTED_TIMESTAMP_ENTRIES) {
|
|
102
|
+
detected.remove(0)
|
|
103
|
+
}
|
|
104
|
+
prefs.edit()
|
|
105
|
+
.putString(KEY_DETECTED_ANR_TIMESTAMPS, detected.toString())
|
|
106
|
+
.commit()
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
private fun isWithinDetectionWindow(anrTimestampMs: Long, exitTimestampMs: Long): Boolean {
|
|
110
|
+
if (anrTimestampMs <= 0L) {
|
|
111
|
+
return false
|
|
112
|
+
}
|
|
113
|
+
return kotlin.math.abs(exitTimestampMs - anrTimestampMs) <= MAX_TIMESTAMP_DELTA_MS
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
fun getPendingAnrs(context: Context): List<String> {
|
|
117
|
+
val prefs = context.applicationContext.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE)
|
|
118
|
+
val array = JSONArray(prefs.getString(KEY_PENDING_ANRS, "[]") ?: "[]")
|
|
119
|
+
val result = ArrayList<String>(array.length())
|
|
120
|
+
for (i in 0 until array.length()) {
|
|
121
|
+
val obj = array.optJSONObject(i) ?: continue
|
|
122
|
+
result.add(obj.toString())
|
|
123
|
+
}
|
|
124
|
+
return result
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/** Records an ANR exit timestamp so [FaroCrashReporter] skips duplicate crash rows. */
|
|
128
|
+
fun recordDetectionTimestamp(context: Context, timestampMs: Long) {
|
|
129
|
+
if (timestampMs <= 0L) {
|
|
130
|
+
return
|
|
131
|
+
}
|
|
132
|
+
val prefs = context.applicationContext.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE)
|
|
133
|
+
recordDetectionTimestamp(prefs, timestampMs)
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
fun acknowledgeAnrs(context: Context, timestamps: Collection<Long>) {
|
|
137
|
+
if (timestamps.isEmpty()) {
|
|
138
|
+
return
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
val timestampSet = timestamps.toSet()
|
|
142
|
+
val prefs = context.applicationContext.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE)
|
|
143
|
+
val array = JSONArray(prefs.getString(KEY_PENDING_ANRS, "[]") ?: "[]")
|
|
144
|
+
val remaining = JSONArray()
|
|
145
|
+
|
|
146
|
+
for (i in 0 until array.length()) {
|
|
147
|
+
val obj = array.optJSONObject(i) ?: continue
|
|
148
|
+
if (obj.optLong("timestamp", 0L) !in timestampSet) {
|
|
149
|
+
remaining.put(obj)
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
prefs.edit()
|
|
154
|
+
.putString(KEY_PENDING_ANRS, remaining.toString())
|
|
155
|
+
.commit()
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
private fun trimOldestEntries(array: JSONArray) {
|
|
159
|
+
while (array.length() > MAX_PENDING_ENTRIES) {
|
|
160
|
+
array.remove(0)
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
}
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
package com.grafana.faro.reactnative
|
|
2
|
+
|
|
3
|
+
import android.app.ActivityManager
|
|
4
|
+
import android.app.ApplicationExitInfo
|
|
5
|
+
import android.content.Context
|
|
6
|
+
import android.os.Build
|
|
7
|
+
import org.json.JSONObject
|
|
8
|
+
import java.io.BufferedReader
|
|
9
|
+
import java.io.InputStreamReader
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Reports ANRs from ApplicationExitInfo (Android 11+), Sentry AnrV2 style.
|
|
13
|
+
*
|
|
14
|
+
* Kept separate from [FaroCrashReporter] so ANR timeout rows are not replayed as
|
|
15
|
+
* generic previous-session `crash` events (no message / duplicate rows).
|
|
16
|
+
*/
|
|
17
|
+
object FaroAnrReporter {
|
|
18
|
+
|
|
19
|
+
private const val PREFS_NAME = "com.grafana.faro.anr_reporter"
|
|
20
|
+
private const val KEY_LAST_PROCESSED_TIMESTAMP = "last_processed_anr_timestamp"
|
|
21
|
+
|
|
22
|
+
@JvmStatic
|
|
23
|
+
fun getAnrReports(context: Context): List<String>? {
|
|
24
|
+
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.R) {
|
|
25
|
+
return null
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
val activityManager = context.getSystemService(Context.ACTIVITY_SERVICE) as? ActivityManager
|
|
29
|
+
?: return null
|
|
30
|
+
|
|
31
|
+
val exitInfoList = activityManager.getHistoricalProcessExitReasons(
|
|
32
|
+
context.packageName,
|
|
33
|
+
0,
|
|
34
|
+
10,
|
|
35
|
+
)
|
|
36
|
+
|
|
37
|
+
if (exitInfoList.isEmpty()) {
|
|
38
|
+
return null
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
val prefs = context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE)
|
|
42
|
+
val lastProcessedTimestamp = prefs.getLong(KEY_LAST_PROCESSED_TIMESTAMP, 0)
|
|
43
|
+
|
|
44
|
+
val reports = mutableListOf<String>()
|
|
45
|
+
var latestTimestamp = lastProcessedTimestamp
|
|
46
|
+
|
|
47
|
+
val anrExits = exitInfoList
|
|
48
|
+
.filter { it.timestamp > lastProcessedTimestamp && it.reason == ApplicationExitInfo.REASON_ANR }
|
|
49
|
+
.groupBy { it.timestamp }
|
|
50
|
+
.mapValues { (_, exits) -> pickBestAnrExit(exits) }
|
|
51
|
+
.toSortedMap()
|
|
52
|
+
|
|
53
|
+
for ((timestamp, exitInfo) in anrExits) {
|
|
54
|
+
if (exitInfo.timestamp > latestTimestamp) {
|
|
55
|
+
latestTimestamp = exitInfo.timestamp
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
val json = exportAnrAsJSON(context, exitInfo) ?: continue
|
|
59
|
+
reports.add(json)
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
if (latestTimestamp > lastProcessedTimestamp) {
|
|
63
|
+
prefs.edit()
|
|
64
|
+
.putLong(KEY_LAST_PROCESSED_TIMESTAMP, latestTimestamp)
|
|
65
|
+
.apply()
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
return if (reports.isEmpty()) null else reports
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
private fun pickBestAnrExit(exits: List<ApplicationExitInfo>): ApplicationExitInfo {
|
|
72
|
+
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.R) {
|
|
73
|
+
return exits.first()
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
return exits.maxByOrNull { exit ->
|
|
77
|
+
var score = 0
|
|
78
|
+
if (getTraceInfo(exit).isNotEmpty()) {
|
|
79
|
+
score += 2
|
|
80
|
+
}
|
|
81
|
+
val description = exit.description?.trim().orEmpty()
|
|
82
|
+
if (description.isNotEmpty()) {
|
|
83
|
+
score += 1
|
|
84
|
+
}
|
|
85
|
+
score
|
|
86
|
+
} ?: exits.first()
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
private fun exportAnrAsJSON(context: Context, exitInfo: ApplicationExitInfo): String? {
|
|
90
|
+
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.R) {
|
|
91
|
+
return null
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
val fullTrace = getTraceInfo(exitInfo)
|
|
95
|
+
if (fullTrace.isEmpty()) {
|
|
96
|
+
// Align with Sentry: skip ANRs without an actionable trace stream.
|
|
97
|
+
return null
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
val mainStack = AnrThreadDumpParser.extractMainThreadStack(fullTrace)
|
|
101
|
+
if (mainStack.isEmpty() || AnrStackFilters.isCrashHandlerStack(mainStack)) {
|
|
102
|
+
return null
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
val rawDescription = exitInfo.description?.trim().orEmpty()
|
|
106
|
+
val description = rawDescription.ifEmpty { "Application Not Responding" }
|
|
107
|
+
|
|
108
|
+
FaroAnrCache.recordDetectionTimestamp(context, exitInfo.timestamp)
|
|
109
|
+
|
|
110
|
+
// duration is the configured watchdog threshold (set via startANRTracking), not the
|
|
111
|
+
// measured main-thread block time — AppExitInfo does not expose exact freeze duration.
|
|
112
|
+
// TODO: consider renaming the JSON field to detectionThresholdMs for clarity.
|
|
113
|
+
return JSONObject().apply {
|
|
114
|
+
put("type", "ANR")
|
|
115
|
+
put("timestamp", exitInfo.timestamp)
|
|
116
|
+
put("duration", ANRTracker.timeout)
|
|
117
|
+
put("stacktrace", mainStack)
|
|
118
|
+
put("description", description)
|
|
119
|
+
put("source", "AppExitInfo")
|
|
120
|
+
put("processName", exitInfo.processName ?: "")
|
|
121
|
+
put("pid", exitInfo.pid)
|
|
122
|
+
}.toString()
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
private fun getTraceInfo(exitInfo: ApplicationExitInfo): String {
|
|
126
|
+
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.R) {
|
|
127
|
+
return ""
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
return try {
|
|
131
|
+
val traceInputStream = exitInfo.traceInputStream ?: return ""
|
|
132
|
+
val reader = BufferedReader(InputStreamReader(traceInputStream))
|
|
133
|
+
val trace = StringBuilder()
|
|
134
|
+
var lineCount = 0
|
|
135
|
+
val maxLines = 500
|
|
136
|
+
|
|
137
|
+
reader.useLines { lines ->
|
|
138
|
+
for (line in lines) {
|
|
139
|
+
if (lineCount >= maxLines) break
|
|
140
|
+
trace.appendLine(line)
|
|
141
|
+
lineCount++
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
trace.toString().trim()
|
|
146
|
+
} catch (_: Exception) {
|
|
147
|
+
""
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* Reject watchdog snapshots taken while the process is tearing down after a crash.
|
|
154
|
+
* Uses only Android framework and Faro SDK frame names.
|
|
155
|
+
*/
|
|
156
|
+
internal object AnrStackFilters {
|
|
157
|
+
fun isCrashHandlerStack(stack: String): Boolean {
|
|
158
|
+
val normalized = stack.lowercase()
|
|
159
|
+
return normalized.contains("handleapplicationcrash") ||
|
|
160
|
+
normalized.contains("killapplicationhandler") ||
|
|
161
|
+
normalized.contains("farouncaughtexceptionhandler")
|
|
162
|
+
}
|
|
163
|
+
}
|