@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
|
@@ -1,9 +1,11 @@
|
|
|
1
|
-
import { NativeModules, Platform } from 'react-native';
|
|
1
|
+
import { NativeEventEmitter, NativeModules, Platform } from 'react-native';
|
|
2
2
|
|
|
3
3
|
import { BaseInstrumentation, VERSION } from '@grafana/faro-core';
|
|
4
4
|
|
|
5
|
+
import { normalizeJavaStackTraceForRetrace, parseAndroidCrashTrace } from '../crashReporting/android';
|
|
5
6
|
import { ErrorMechanism } from '../errors/const';
|
|
6
7
|
|
|
8
|
+
import { isInvalidAnrCaptureStack } from './anrStack';
|
|
7
9
|
import type { ANREvent, ANRInstrumentationOptions } from './types';
|
|
8
10
|
|
|
9
11
|
/**
|
|
@@ -12,38 +14,26 @@ import type { ANREvent, ANRInstrumentationOptions } from './types';
|
|
|
12
14
|
const DEFAULT_TIMEOUT = 5000;
|
|
13
15
|
|
|
14
16
|
/**
|
|
15
|
-
* Default polling interval for ANR status
|
|
17
|
+
* Default polling interval for ANR status. Kept short so debug ANRs surface without
|
|
18
|
+
* waiting a full minute; native also emits onANRDetected for immediate delivery.
|
|
16
19
|
*/
|
|
17
|
-
const DEFAULT_POLLING_INTERVAL =
|
|
20
|
+
const DEFAULT_POLLING_INTERVAL = 10000;
|
|
21
|
+
|
|
22
|
+
/** Aligns with native FaroAnrCache.MAX_TIMESTAMP_DELTA_MS */
|
|
23
|
+
const TIMESTAMP_DEDUP_WINDOW_MS = 10_000;
|
|
24
|
+
|
|
25
|
+
/** ApplicationExitInfo ANR traces (Android 11 / API 30+). */
|
|
26
|
+
const APP_EXIT_INFO_MIN_SDK = 30;
|
|
18
27
|
|
|
19
28
|
/**
|
|
20
29
|
* ANR (Application Not Responding) Detection Instrumentation.
|
|
21
30
|
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
* **Note**: ANR detection is only available on Android. iOS does not have
|
|
27
|
-
* the same ANR concept as Android's system watchdog.
|
|
28
|
-
*
|
|
29
|
-
* Sends telemetry via Faro API:
|
|
30
|
-
* - Measurement: `anr` with `anr_count` value (for dashboards)
|
|
31
|
-
* - Error: Each ANR with `type: 'ANR'`, stack trace, duration, timestamp (Sentry-aligned)
|
|
31
|
+
* On Android 11+, historical ANRs are read from ApplicationExitInfo (Sentry AnrV2
|
|
32
|
+
* style) on the next launch. ANRTracker remains a pre-API-30 fallback and a
|
|
33
|
+
* persistence backup when the OS trace stream is empty.
|
|
32
34
|
*
|
|
33
|
-
*
|
|
34
|
-
*
|
|
35
|
-
* import { initializeFaro, ANRInstrumentation } from '@grafana/faro-react-native';
|
|
36
|
-
*
|
|
37
|
-
* initializeFaro({
|
|
38
|
-
* url: 'https://collector.example.com',
|
|
39
|
-
* instrumentations: [
|
|
40
|
-
* new ANRInstrumentation({
|
|
41
|
-
* timeout: 5000, // 5 second threshold
|
|
42
|
-
* pollingInterval: 60000, // Poll every 60 seconds
|
|
43
|
-
* }),
|
|
44
|
-
* ],
|
|
45
|
-
* });
|
|
46
|
-
* ```
|
|
35
|
+
* ANRs stay out of CrashReportingInstrumentation so they are not replayed as
|
|
36
|
+
* generic previous-session `crash` rows with empty messages.
|
|
47
37
|
*/
|
|
48
38
|
export class ANRInstrumentation extends BaseInstrumentation {
|
|
49
39
|
readonly name = '@grafana/faro-react-native:instrumentation-anr';
|
|
@@ -51,6 +41,9 @@ export class ANRInstrumentation extends BaseInstrumentation {
|
|
|
51
41
|
|
|
52
42
|
private readonly options: Required<ANRInstrumentationOptions>;
|
|
53
43
|
private pollingIntervalId: ReturnType<typeof setInterval> | null = null;
|
|
44
|
+
private anrEventSubscription: ReturnType<NativeEventEmitter['addListener']> | null = null;
|
|
45
|
+
private readonly reportedAnrTimestamps = new Set<number>();
|
|
46
|
+
private readonly preferAppExitInfoAnr: boolean;
|
|
54
47
|
|
|
55
48
|
constructor(options: ANRInstrumentationOptions = {}) {
|
|
56
49
|
super();
|
|
@@ -58,10 +51,10 @@ export class ANRInstrumentation extends BaseInstrumentation {
|
|
|
58
51
|
timeout: options.timeout ?? DEFAULT_TIMEOUT,
|
|
59
52
|
pollingInterval: options.pollingInterval ?? DEFAULT_POLLING_INTERVAL,
|
|
60
53
|
};
|
|
54
|
+
this.preferAppExitInfoAnr = Platform.OS === 'android' && Number(Platform.Version) >= APP_EXIT_INFO_MIN_SDK;
|
|
61
55
|
}
|
|
62
56
|
|
|
63
57
|
initialize(): void {
|
|
64
|
-
// ANR detection is only available on Android
|
|
65
58
|
if (Platform.OS !== 'android') {
|
|
66
59
|
this.logDebug('ANR detection is only available on Android');
|
|
67
60
|
return;
|
|
@@ -75,12 +68,18 @@ export class ANRInstrumentation extends BaseInstrumentation {
|
|
|
75
68
|
|
|
76
69
|
this.logDebug('Initializing ANR detection instrumentation');
|
|
77
70
|
|
|
78
|
-
|
|
71
|
+
void this.drainAllAnrSources(nativeModule);
|
|
72
|
+
|
|
79
73
|
this.startNativeTracking(nativeModule);
|
|
80
74
|
|
|
81
|
-
|
|
75
|
+
const emitter = new NativeEventEmitter(nativeModule);
|
|
76
|
+
this.anrEventSubscription = emitter.addListener('onANRDetected', (anrJson: string) => {
|
|
77
|
+
void this.handleLiveAnrEvent(nativeModule, anrJson);
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
void this.checkANRStatus(nativeModule);
|
|
82
81
|
this.pollingIntervalId = setInterval(() => {
|
|
83
|
-
this.checkANRStatus(nativeModule);
|
|
82
|
+
void this.checkANRStatus(nativeModule);
|
|
84
83
|
}, this.options.pollingInterval);
|
|
85
84
|
}
|
|
86
85
|
|
|
@@ -107,68 +106,303 @@ export class ANRInstrumentation extends BaseInstrumentation {
|
|
|
107
106
|
}
|
|
108
107
|
}
|
|
109
108
|
|
|
110
|
-
private async
|
|
109
|
+
private async drainAllAnrSources(nativeModule: typeof NativeModules.FaroReactNativeModule): Promise<void> {
|
|
110
|
+
await this.drainHistoricalAnrs(nativeModule);
|
|
111
|
+
await this.drainPendingANRs(nativeModule);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
private async drainHistoricalAnrs(nativeModule: typeof NativeModules.FaroReactNativeModule): Promise<void> {
|
|
115
|
+
if (typeof nativeModule.getHistoricalAnrReports !== 'function') {
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
118
|
+
|
|
111
119
|
try {
|
|
112
|
-
|
|
120
|
+
const anrList = (await nativeModule.getHistoricalAnrReports()) as string[] | null;
|
|
121
|
+
if (!anrList?.length) {
|
|
113
122
|
return;
|
|
114
123
|
}
|
|
115
124
|
|
|
116
|
-
const
|
|
125
|
+
const ackTimestamps: number[] = [];
|
|
126
|
+
let reportedCount = 0;
|
|
117
127
|
|
|
118
|
-
|
|
119
|
-
|
|
128
|
+
for (const anrJson of anrList) {
|
|
129
|
+
try {
|
|
130
|
+
const anr = JSON.parse(anrJson) as ANREvent;
|
|
131
|
+
if (this.reportANRIfNew(anr)) {
|
|
132
|
+
reportedCount += 1;
|
|
133
|
+
}
|
|
134
|
+
ackTimestamps.push(anr.timestamp);
|
|
135
|
+
await this.acknowledgeNearbyPendingAnrs(nativeModule, anr.timestamp);
|
|
136
|
+
} catch (error) {
|
|
137
|
+
this.logError('Failed to parse historical ANR report', error);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
if (reportedCount > 0) {
|
|
120
142
|
this.api.pushMeasurement(
|
|
121
143
|
{
|
|
122
144
|
type: 'anr',
|
|
123
|
-
values: { anr_count:
|
|
145
|
+
values: { anr_count: reportedCount },
|
|
124
146
|
},
|
|
125
147
|
{ skipDedupe: true }
|
|
126
148
|
);
|
|
149
|
+
this.logDebug(`Reported ${reportedCount} historical ANR event(s) from ApplicationExitInfo`);
|
|
150
|
+
}
|
|
151
|
+
} catch (error) {
|
|
152
|
+
this.logError('Failed to drain historical ANRs', error);
|
|
153
|
+
}
|
|
154
|
+
}
|
|
127
155
|
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
156
|
+
private async drainPendingANRs(nativeModule: typeof NativeModules.FaroReactNativeModule): Promise<void> {
|
|
157
|
+
const anrList = await this.fetchPendingANRs(nativeModule);
|
|
158
|
+
if (!anrList?.length) {
|
|
159
|
+
return;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
await this.reportAndAcknowledgePendingANRs(nativeModule, anrList);
|
|
163
|
+
this.logDebug(`Processed ${anrList.length} persisted ANR event(s) from tracker cache`);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
private async handleLiveAnrEvent(
|
|
167
|
+
nativeModule: typeof NativeModules.FaroReactNativeModule,
|
|
168
|
+
anrJson: string
|
|
169
|
+
): Promise<void> {
|
|
170
|
+
try {
|
|
171
|
+
const anr = JSON.parse(anrJson) as ANREvent;
|
|
172
|
+
|
|
173
|
+
if (this.preferAppExitInfoAnr) {
|
|
174
|
+
// Process death + ApplicationExitInfo on next launch delivers the main-thread dump.
|
|
175
|
+
this.logDebug('Deferring live ANR to ApplicationExitInfo on next launch');
|
|
176
|
+
return;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
if (!this.shouldReportTrackerAnr(anr)) {
|
|
180
|
+
await this.acknowledgeANRs(nativeModule, [anr.timestamp]);
|
|
181
|
+
return;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
if (this.reportANRIfNew(anr)) {
|
|
185
|
+
this.api.pushMeasurement(
|
|
186
|
+
{
|
|
187
|
+
type: 'anr',
|
|
188
|
+
values: { anr_count: 1 },
|
|
189
|
+
},
|
|
190
|
+
{ skipDedupe: true }
|
|
191
|
+
);
|
|
192
|
+
}
|
|
193
|
+
await this.acknowledgeANRs(nativeModule, [anr.timestamp]);
|
|
194
|
+
} catch (error) {
|
|
195
|
+
this.logError('Failed to parse native ANR event', error);
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
private async fetchPendingANRs(nativeModule: typeof NativeModules.FaroReactNativeModule): Promise<string[] | null> {
|
|
200
|
+
try {
|
|
201
|
+
if (typeof nativeModule.getPendingANRs === 'function') {
|
|
202
|
+
return (await nativeModule.getPendingANRs()) as string[] | null;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
if (typeof nativeModule.getANRStatus === 'function') {
|
|
206
|
+
return (await nativeModule.getANRStatus()) as string[] | null;
|
|
207
|
+
}
|
|
208
|
+
} catch (error) {
|
|
209
|
+
this.logError('Failed to fetch pending ANRs', error);
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
return null;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
private async acknowledgeANRs(
|
|
216
|
+
nativeModule: typeof NativeModules.FaroReactNativeModule,
|
|
217
|
+
timestamps: number[]
|
|
218
|
+
): Promise<void> {
|
|
219
|
+
if (!timestamps.length || typeof nativeModule.acknowledgeANRs !== 'function') {
|
|
220
|
+
return;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
try {
|
|
224
|
+
await nativeModule.acknowledgeANRs(timestamps);
|
|
225
|
+
} catch (error) {
|
|
226
|
+
this.logError('Failed to acknowledge ANRs', error);
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
private async acknowledgeNearbyPendingAnrs(
|
|
231
|
+
nativeModule: typeof NativeModules.FaroReactNativeModule,
|
|
232
|
+
exitTimestamp: number
|
|
233
|
+
): Promise<void> {
|
|
234
|
+
const pending = await this.fetchPendingANRs(nativeModule);
|
|
235
|
+
if (!pending?.length) {
|
|
236
|
+
return;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
const ackTimestamps: number[] = [];
|
|
240
|
+
for (const anrJson of pending) {
|
|
241
|
+
try {
|
|
242
|
+
const anr = JSON.parse(anrJson) as ANREvent;
|
|
243
|
+
if (this.isNearbyTimestamp(anr.timestamp, exitTimestamp)) {
|
|
244
|
+
ackTimestamps.push(anr.timestamp);
|
|
245
|
+
}
|
|
246
|
+
} catch {
|
|
247
|
+
// ignore malformed cache entries
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
if (ackTimestamps.length > 0) {
|
|
252
|
+
await this.acknowledgeANRs(nativeModule, ackTimestamps);
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
private async reportAndAcknowledgePendingANRs(
|
|
257
|
+
nativeModule: typeof NativeModules.FaroReactNativeModule,
|
|
258
|
+
anrList: string[]
|
|
259
|
+
): Promise<void> {
|
|
260
|
+
const ackTimestamps: number[] = [];
|
|
261
|
+
let reportedCount = 0;
|
|
262
|
+
|
|
263
|
+
for (const anrJson of anrList) {
|
|
264
|
+
try {
|
|
265
|
+
const anr = JSON.parse(anrJson) as ANREvent;
|
|
266
|
+
|
|
267
|
+
if (this.shouldReportTrackerAnr(anr) && this.reportANRIfNew(anr)) {
|
|
268
|
+
reportedCount += 1;
|
|
152
269
|
}
|
|
153
270
|
|
|
154
|
-
|
|
271
|
+
ackTimestamps.push(anr.timestamp);
|
|
272
|
+
} catch {
|
|
273
|
+
const error = new Error('ANR (Application Not Responding)');
|
|
274
|
+
error.stack = undefined;
|
|
275
|
+
this.api.pushError(error, {
|
|
276
|
+
context: {
|
|
277
|
+
mechanism: ErrorMechanism.ANR,
|
|
278
|
+
raw: anrJson,
|
|
279
|
+
},
|
|
280
|
+
fatal: true,
|
|
281
|
+
type: 'ANR',
|
|
282
|
+
});
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
if (reportedCount > 0) {
|
|
287
|
+
this.api.pushMeasurement(
|
|
288
|
+
{
|
|
289
|
+
type: 'anr',
|
|
290
|
+
values: { anr_count: reportedCount },
|
|
291
|
+
},
|
|
292
|
+
{ skipDedupe: true }
|
|
293
|
+
);
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
if (ackTimestamps.length > 0) {
|
|
297
|
+
await this.acknowledgeANRs(nativeModule, ackTimestamps);
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
private shouldReportTrackerAnr(anr: ANREvent): boolean {
|
|
302
|
+
if (this.isNearbyReportedTimestamp(anr.timestamp)) {
|
|
303
|
+
return false;
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
if (isInvalidAnrCaptureStack(anr.stacktrace)) {
|
|
307
|
+
return false;
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
// On API 30+, ApplicationExitInfo is preferred on launch; tracker pending entries
|
|
311
|
+
// are reported only when historical did not already cover the same incident.
|
|
312
|
+
return true;
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
private reportANRIfNew(anr: ANREvent): boolean {
|
|
316
|
+
if (this.isNearbyReportedTimestamp(anr.timestamp)) {
|
|
317
|
+
return false;
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
if (isInvalidAnrCaptureStack(anr.stacktrace)) {
|
|
321
|
+
return false;
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
this.rememberReportedTimestamp(anr.timestamp);
|
|
325
|
+
this.reportANR(anr);
|
|
326
|
+
return true;
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
private rememberReportedTimestamp(timestamp: number): void {
|
|
330
|
+
this.reportedAnrTimestamps.add(timestamp);
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
private isNearbyReportedTimestamp(timestamp: number): boolean {
|
|
334
|
+
for (const reported of Array.from(this.reportedAnrTimestamps)) {
|
|
335
|
+
if (this.isNearbyTimestamp(timestamp, reported)) {
|
|
336
|
+
return true;
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
return false;
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
private isNearbyTimestamp(a: number, b: number): boolean {
|
|
343
|
+
if (a <= 0 || b <= 0) {
|
|
344
|
+
return false;
|
|
345
|
+
}
|
|
346
|
+
return Math.abs(a - b) <= TIMESTAMP_DEDUP_WINDOW_MS;
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
private reportANR(anr: ANREvent): void {
|
|
350
|
+
const rawStacktrace = anr.stacktrace?.trim() ?? '';
|
|
351
|
+
const traceForRetrace = rawStacktrace ? normalizeJavaStackTraceForRetrace(rawStacktrace) : '';
|
|
352
|
+
const parsed = traceForRetrace ? parseAndroidCrashTrace(traceForRetrace) : null;
|
|
353
|
+
|
|
354
|
+
const message = anr.description?.trim() || 'ANR (Application Not Responding)';
|
|
355
|
+
const error = new Error(message);
|
|
356
|
+
error.stack = undefined;
|
|
357
|
+
|
|
358
|
+
const context: Record<string, string> = {
|
|
359
|
+
duration: String(anr.duration),
|
|
360
|
+
mechanism: ErrorMechanism.ANR,
|
|
361
|
+
timestamp: String(anr.timestamp),
|
|
362
|
+
};
|
|
363
|
+
if (anr.source) {
|
|
364
|
+
context['source'] = anr.source;
|
|
365
|
+
}
|
|
366
|
+
if (traceForRetrace) {
|
|
367
|
+
context['stacktrace'] = traceForRetrace;
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
const stackFrames = parsed?.frames ?? [];
|
|
371
|
+
if (!stackFrames.length && rawStacktrace) {
|
|
372
|
+
this.logWarn('ANR main-thread stack present but no Java/Kotlin frames parsed');
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
this.api.pushError(error, {
|
|
376
|
+
type: 'ANR',
|
|
377
|
+
context,
|
|
378
|
+
fatal: true,
|
|
379
|
+
...(stackFrames.length ? { stackFrames } : {}),
|
|
380
|
+
});
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
private async checkANRStatus(nativeModule: typeof NativeModules.FaroReactNativeModule): Promise<void> {
|
|
384
|
+
try {
|
|
385
|
+
const anrList = await this.fetchPendingANRs(nativeModule);
|
|
386
|
+
|
|
387
|
+
if (anrList && anrList.length > 0) {
|
|
388
|
+
await this.reportAndAcknowledgePendingANRs(nativeModule, anrList);
|
|
389
|
+
this.logDebug(`Polled ${anrList.length} ANR event(s) from pending cache`);
|
|
155
390
|
}
|
|
156
391
|
} catch (error) {
|
|
157
392
|
this.logError('Failed to check ANR status', error);
|
|
158
393
|
}
|
|
159
394
|
}
|
|
160
395
|
|
|
161
|
-
/**
|
|
162
|
-
* Clean up resources when instrumentation is disabled.
|
|
163
|
-
*/
|
|
164
396
|
unpatch(): void {
|
|
165
|
-
// Stop polling
|
|
166
397
|
if (this.pollingIntervalId !== null) {
|
|
167
398
|
clearInterval(this.pollingIntervalId);
|
|
168
399
|
this.pollingIntervalId = null;
|
|
169
400
|
}
|
|
170
401
|
|
|
171
|
-
|
|
402
|
+
this.anrEventSubscription?.remove();
|
|
403
|
+
this.anrEventSubscription = null;
|
|
404
|
+
this.reportedAnrTimestamps.clear();
|
|
405
|
+
|
|
172
406
|
if (Platform.OS === 'android') {
|
|
173
407
|
const nativeModule = this.getNativeModule();
|
|
174
408
|
if (nativeModule && typeof nativeModule.stopANRTracking === 'function') {
|
|
@@ -11,7 +11,7 @@ export interface ANRInstrumentationOptions {
|
|
|
11
11
|
|
|
12
12
|
/**
|
|
13
13
|
* Interval in milliseconds for polling ANR status from native.
|
|
14
|
-
* Default:
|
|
14
|
+
* Default: 10000 (10 seconds). Native also emits onANRDetected immediately.
|
|
15
15
|
*/
|
|
16
16
|
pollingInterval?: number;
|
|
17
17
|
}
|
|
@@ -39,4 +39,10 @@ export interface ANREvent {
|
|
|
39
39
|
* Duration of the ANR in milliseconds
|
|
40
40
|
*/
|
|
41
41
|
duration: number;
|
|
42
|
+
|
|
43
|
+
/** Human-readable ANR description from ApplicationExitInfo when available. */
|
|
44
|
+
description?: string;
|
|
45
|
+
|
|
46
|
+
/** `AppExitInfo` (preferred) or `ANRTracker` (fallback). */
|
|
47
|
+
source?: string;
|
|
42
48
|
}
|
|
@@ -68,11 +68,15 @@ export class ConsoleInstrumentation extends BaseInstrumentation {
|
|
|
68
68
|
|
|
69
69
|
if (value && !type && !stackFrames) {
|
|
70
70
|
// Simple error without stack frames
|
|
71
|
-
this.api.pushError(new Error(ConsoleInstrumentation.consoleErrorPrefix + value), {
|
|
71
|
+
this.api.pushError(new Error(ConsoleInstrumentation.consoleErrorPrefix + value), {
|
|
72
|
+
context,
|
|
73
|
+
fatal: false,
|
|
74
|
+
});
|
|
72
75
|
} else {
|
|
73
76
|
// Error with type and/or stack frames (type from error.name, matches Web SDK)
|
|
74
77
|
this.api.pushError(new Error(ConsoleInstrumentation.consoleErrorPrefix + (value ?? '')), {
|
|
75
78
|
context,
|
|
79
|
+
fatal: false,
|
|
76
80
|
type,
|
|
77
81
|
stackFrames,
|
|
78
82
|
});
|
|
@@ -46,11 +46,13 @@ describe('ConsoleInstrumentation', () => {
|
|
|
46
46
|
expect((mockTransport.items[0] as TransportItem<ExceptionEvent>)?.payload.value).toBe(
|
|
47
47
|
'console.error: console.error no 1'
|
|
48
48
|
);
|
|
49
|
+
expect((mockTransport.items[0] as TransportItem<ExceptionEvent>)?.payload.fatal).toBe(false);
|
|
49
50
|
expect((mockTransport.items[0] as TransportItem<ExceptionEvent>)?.payload.context?.mechanism).toBe('console');
|
|
50
51
|
expect((mockTransport.items[1] as TransportItem<ExceptionEvent>)?.payload.type).toBe('Error');
|
|
51
52
|
expect((mockTransport.items[1] as TransportItem<ExceptionEvent>)?.payload.value).toBe(
|
|
52
53
|
'console.error: with object {"foo":"bar","baz":"bam"}'
|
|
53
54
|
);
|
|
55
|
+
expect((mockTransport.items[1] as TransportItem<ExceptionEvent>)?.payload.fatal).toBe(false);
|
|
54
56
|
});
|
|
55
57
|
|
|
56
58
|
it('sends a faro error serialized with error serializer when console.error is called', () => {
|
|
@@ -80,10 +82,12 @@ describe('ConsoleInstrumentation', () => {
|
|
|
80
82
|
expect((mockTransport.items[0] as TransportItem<ExceptionEvent>)?.payload.value).toBe(
|
|
81
83
|
'console.error: console.error no 1'
|
|
82
84
|
);
|
|
85
|
+
expect((mockTransport.items[0] as TransportItem<ExceptionEvent>)?.payload.fatal).toBe(false);
|
|
83
86
|
expect((mockTransport.items[1] as TransportItem<ExceptionEvent>)?.payload.type).toBe('Error');
|
|
84
87
|
expect((mockTransport.items[1] as TransportItem<ExceptionEvent>)?.payload.value).toBe(
|
|
85
88
|
'console.error: with object ' + stringifyExternalJson(context)
|
|
86
89
|
);
|
|
90
|
+
expect((mockTransport.items[1] as TransportItem<ExceptionEvent>)?.payload.fatal).toBe(false);
|
|
87
91
|
});
|
|
88
92
|
|
|
89
93
|
it('Handles objects with circular references', () => {
|
|
@@ -244,6 +248,7 @@ describe('ConsoleInstrumentation', () => {
|
|
|
244
248
|
expect((mockTransport.items[2] as TransportItem<ExceptionEvent>)?.payload.value).toBe(
|
|
245
249
|
'console.error: ' + errorLogMessage
|
|
246
250
|
);
|
|
251
|
+
expect((mockTransport.items[2] as TransportItem<ExceptionEvent>)?.payload.fatal).toBe(false);
|
|
247
252
|
});
|
|
248
253
|
|
|
249
254
|
it('unpatch restores original console methods', () => {
|