@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,47 +1,28 @@
|
|
|
1
1
|
import { NativeModules } from 'react-native';
|
|
2
2
|
|
|
3
|
-
import { BaseInstrumentation
|
|
4
|
-
|
|
5
|
-
import { ErrorMechanism } from '../errors/const';
|
|
3
|
+
import { BaseInstrumentation } from '@grafana/faro-core';
|
|
6
4
|
|
|
5
|
+
import { looksLikeNativeTombstoneTrace } from './android/nativeTombstoneTrace';
|
|
7
6
|
import type { CrashReport, CrashReportingOptions } from './types';
|
|
8
7
|
|
|
9
8
|
/**
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
* Retrieves crash reports from previous app sessions and sends them via Faro API.
|
|
13
|
-
*
|
|
14
|
-
* **Platform Support**:
|
|
15
|
-
* - **Android**: Uses ApplicationExitInfo API (Android 11+ / API 30+)
|
|
16
|
-
* Captures: CRASH, CRASH_NATIVE, ANR, LOW_MEMORY, EXCESSIVE_RESOURCE_USAGE
|
|
17
|
-
* - **iOS**: Uses PLCrashReporter (requires adding dependency to podspec)
|
|
18
|
-
* Captures: Signal crashes (SIGSEGV, SIGABRT, etc.) and Mach exceptions
|
|
9
|
+
* Abstract base class for platform-specific crash reporting implementations.
|
|
19
10
|
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
* @example
|
|
24
|
-
* ```typescript
|
|
25
|
-
* import { initializeFaro, CrashReportingInstrumentation } from '@grafana/faro-react-native';
|
|
26
|
-
*
|
|
27
|
-
* initializeFaro({
|
|
28
|
-
* url: 'https://collector.example.com',
|
|
29
|
-
* instrumentations: [
|
|
30
|
-
* new CrashReportingInstrumentation(),
|
|
31
|
-
* ],
|
|
32
|
-
* });
|
|
33
|
-
* ```
|
|
11
|
+
* Defines the common contract that both Android and iOS implementations must follow.
|
|
12
|
+
* Handles the shared logic for retrieving crash reports from native modules and
|
|
13
|
+
* processing them, while delegating platform-specific parsing and validation to subclasses.
|
|
34
14
|
*/
|
|
35
|
-
export class
|
|
36
|
-
readonly name
|
|
37
|
-
readonly version
|
|
15
|
+
export abstract class BaseCrashReportingInstrumentation extends BaseInstrumentation {
|
|
16
|
+
abstract override readonly name: string;
|
|
17
|
+
abstract override readonly version: string;
|
|
38
18
|
|
|
39
|
-
|
|
19
|
+
protected readonly options: { enabled: boolean; releaseBundleFilename?: string };
|
|
40
20
|
|
|
41
21
|
constructor(options: CrashReportingOptions = {}) {
|
|
42
22
|
super();
|
|
43
23
|
this.options = {
|
|
44
24
|
enabled: options.enabled ?? true,
|
|
25
|
+
releaseBundleFilename: options.releaseBundleFilename,
|
|
45
26
|
};
|
|
46
27
|
}
|
|
47
28
|
|
|
@@ -59,37 +40,56 @@ export class CrashReportingInstrumentation extends BaseInstrumentation {
|
|
|
59
40
|
|
|
60
41
|
this.logDebug('Initializing crash reporting instrumentation');
|
|
61
42
|
|
|
62
|
-
// Wait for session attributes
|
|
63
|
-
// This ensures crash reports include full session metadata
|
|
43
|
+
// Wait for session attributes before sending crash reports
|
|
64
44
|
await this.waitForSessionAttributes();
|
|
65
45
|
|
|
66
|
-
// Get crash reports from previous session
|
|
46
|
+
// Get and process crash reports from previous session
|
|
67
47
|
await this.processCrashReports(nativeModule);
|
|
68
48
|
}
|
|
69
49
|
|
|
70
50
|
/**
|
|
71
|
-
*
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
*
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
*
|
|
82
|
-
|
|
51
|
+
* Platform-specific: Parse a crash trace string into structured stack frames.
|
|
52
|
+
*/
|
|
53
|
+
protected abstract parseCrashTrace(trace: string): any | null;
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Platform-specific: Resolve human-readable error message from crash data.
|
|
57
|
+
*/
|
|
58
|
+
protected abstract resolveCrashErrorMessage(crash: CrashReport, parsedTrace: any | null): string;
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Platform-specific: Determine if a crash report should be skipped.
|
|
62
|
+
*/
|
|
63
|
+
protected abstract shouldSkipCrashReport(crash: CrashReport): boolean;
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Platform-specific: Extract stack frames from parsed trace.
|
|
67
|
+
*/
|
|
68
|
+
protected abstract getStackFrames(parsedTrace: any | null): any[];
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Platform-specific: Build platform-specific context from parsed trace.
|
|
72
|
+
*/
|
|
73
|
+
protected abstract buildPlatformContext(parsedTrace: any | null, context: Record<string, string>): void;
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Platform-specific: Get warning message when trace parsing fails.
|
|
77
|
+
*/
|
|
78
|
+
protected abstract getParseFailureWarning(): string;
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Platform-specific: Get warning message when trace is unavailable.
|
|
83
82
|
*/
|
|
83
|
+
protected abstract getTraceUnavailableWarning(): string;
|
|
84
|
+
|
|
84
85
|
private async waitForSessionAttributes(maxWaitMs = 10000): Promise<void> {
|
|
85
86
|
const startTime = Date.now();
|
|
86
|
-
const pollInterval = 200;
|
|
87
|
+
const pollInterval = 200;
|
|
87
88
|
let checkCount = 0;
|
|
88
89
|
|
|
89
90
|
while (Date.now() - startTime < maxWaitMs) {
|
|
90
91
|
checkCount++;
|
|
91
92
|
try {
|
|
92
|
-
// Check if session attributes are populated
|
|
93
93
|
const sessionAttrs = this.metas?.value?.session?.attributes;
|
|
94
94
|
|
|
95
95
|
if (sessionAttrs) {
|
|
@@ -98,7 +98,7 @@ export class CrashReportingInstrumentation extends BaseInstrumentation {
|
|
|
98
98
|
|
|
99
99
|
// Look for multiple device-specific attributes that indicate full async collection is done.
|
|
100
100
|
// getSessionAttributes() collects these asynchronously:
|
|
101
|
-
// - device_id (
|
|
101
|
+
// - device_id (SDK installation id, kept as a flat attr during migration)
|
|
102
102
|
// - device_os_detail (async getDeviceOsDetail)
|
|
103
103
|
// - device_model_name (DeviceInfo.getDeviceNameSync)
|
|
104
104
|
// All three should be present when collection is complete.
|
|
@@ -120,15 +120,12 @@ export class CrashReportingInstrumentation extends BaseInstrumentation {
|
|
|
120
120
|
this.logDebug(`Check #${checkCount}: No session attributes available yet`);
|
|
121
121
|
}
|
|
122
122
|
} catch (error) {
|
|
123
|
-
// Continue waiting if we can't access metas yet
|
|
124
123
|
this.logDebug(`Check #${checkCount}: Error accessing metas: ${error}`);
|
|
125
124
|
}
|
|
126
125
|
|
|
127
|
-
// Wait before next check
|
|
128
126
|
await new Promise((resolve) => setTimeout(resolve, pollInterval));
|
|
129
127
|
}
|
|
130
128
|
|
|
131
|
-
// Timeout reached - log warning but continue
|
|
132
129
|
const finalAttrs = this.metas?.value?.session?.attributes;
|
|
133
130
|
const finalAttrCount = finalAttrs ? Object.keys(finalAttrs).length : 0;
|
|
134
131
|
const finalAttrKeys = finalAttrs ? JSON.stringify(Object.keys(finalAttrs)) : 'none';
|
|
@@ -164,20 +161,37 @@ export class CrashReportingInstrumentation extends BaseInstrumentation {
|
|
|
164
161
|
|
|
165
162
|
this.logDebug(`Processing ${crashReports.length} crash report(s) from previous session`);
|
|
166
163
|
|
|
164
|
+
const reportedTimestamps = new Set<number>();
|
|
165
|
+
|
|
167
166
|
for (const crashJson of crashReports) {
|
|
168
167
|
try {
|
|
169
168
|
this.logDebug(`Parsing crash report JSON: ${crashJson.substring(0, 200)}...`);
|
|
170
169
|
const crash = JSON.parse(crashJson) as CrashReport;
|
|
170
|
+
|
|
171
|
+
if (crash.timestamp && reportedTimestamps.has(crash.timestamp)) {
|
|
172
|
+
this.logDebug(`Skipping duplicate crash report at ${crash.timestamp}`);
|
|
173
|
+
continue;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
if (this.shouldSkipCrashReport(crash)) {
|
|
177
|
+
this.logDebug(`Skipping crash report at ${crash.timestamp} (platform-specific skip logic)`);
|
|
178
|
+
continue;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
if (crash.timestamp) {
|
|
182
|
+
reportedTimestamps.add(crash.timestamp);
|
|
183
|
+
}
|
|
184
|
+
|
|
171
185
|
this.sendCrashReport(crash);
|
|
172
186
|
} catch (parseError) {
|
|
173
|
-
// If parsing fails, still try to report something
|
|
174
187
|
this.logError('Failed to parse crash report JSON', parseError);
|
|
175
188
|
this.api.pushError(new Error('Application crash (parse error)'), {
|
|
176
189
|
context: {
|
|
177
|
-
mechanism:
|
|
190
|
+
mechanism: 'crash',
|
|
178
191
|
parseError: String(parseError),
|
|
179
|
-
raw: crashJson.substring(0, 500),
|
|
192
|
+
raw: crashJson.substring(0, 500),
|
|
180
193
|
},
|
|
194
|
+
fatal: true,
|
|
181
195
|
type: 'crash',
|
|
182
196
|
});
|
|
183
197
|
}
|
|
@@ -188,12 +202,17 @@ export class CrashReportingInstrumentation extends BaseInstrumentation {
|
|
|
188
202
|
}
|
|
189
203
|
|
|
190
204
|
private sendCrashReport(crash: CrashReport): void {
|
|
191
|
-
|
|
205
|
+
// Platform-specific parsing
|
|
206
|
+
const parsedTrace = crash.trace ? this.parseCrashTrace(crash.trace) : null;
|
|
207
|
+
const errorMessage = this.resolveCrashErrorMessage(crash, parsedTrace).trim() || 'Application crash';
|
|
208
|
+
|
|
209
|
+
// Use a message-only Error so pushError does not capture the JS reporter stack
|
|
192
210
|
const error = new Error(errorMessage);
|
|
211
|
+
error.stack = undefined;
|
|
193
212
|
|
|
194
|
-
// Build context from crash data
|
|
213
|
+
// Build context from crash data
|
|
195
214
|
const context: Record<string, string> = {
|
|
196
|
-
mechanism:
|
|
215
|
+
mechanism: 'crash',
|
|
197
216
|
};
|
|
198
217
|
|
|
199
218
|
if (crash.trace) {
|
|
@@ -218,48 +237,55 @@ export class CrashReportingInstrumentation extends BaseInstrumentation {
|
|
|
218
237
|
context['importance'] = String(crash.importance);
|
|
219
238
|
}
|
|
220
239
|
|
|
221
|
-
//
|
|
240
|
+
// Platform-specific context
|
|
241
|
+
this.buildPlatformContext(parsedTrace, context);
|
|
242
|
+
|
|
243
|
+
// Platform-specific stack frames
|
|
244
|
+
const stackFrames = this.getStackFrames(parsedTrace);
|
|
245
|
+
|
|
246
|
+
// Warnings for missing/unparseable traces
|
|
247
|
+
if (!stackFrames.length && crash.trace) {
|
|
248
|
+
this.logWarn(this.getParseFailureWarning());
|
|
249
|
+
} else if (!crash.trace) {
|
|
250
|
+
this.logWarn(this.getTraceUnavailableWarning());
|
|
251
|
+
} else if (looksLikeNativeTombstoneTrace(crash.trace)) {
|
|
252
|
+
const framePreview = crash.trace
|
|
253
|
+
.split('\n')
|
|
254
|
+
.map((line) => line.trim())
|
|
255
|
+
.find((line) => /#\d+\s+pc\s+/i.test(line));
|
|
256
|
+
this.logInfo(
|
|
257
|
+
`[Faro crash native] Sending tombstone trace (${crash.trace.split('\n').length} lines) preview=${framePreview ?? 'n/a'}`
|
|
258
|
+
);
|
|
259
|
+
}
|
|
260
|
+
|
|
222
261
|
this.api.pushError(error, {
|
|
223
|
-
|
|
262
|
+
stackFrames,
|
|
224
263
|
context,
|
|
264
|
+
type: 'crash',
|
|
265
|
+
fatal: true,
|
|
225
266
|
});
|
|
226
267
|
|
|
227
|
-
this.logDebug(`
|
|
268
|
+
this.logDebug(`Sent crash report: ${errorMessage}`);
|
|
228
269
|
}
|
|
229
270
|
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
case 'CRASH_NATIVE':
|
|
247
|
-
description = 'Application crash (Native)';
|
|
248
|
-
break;
|
|
249
|
-
case 'LOW_MEMORY':
|
|
250
|
-
description = 'Application terminated due to low memory';
|
|
251
|
-
break;
|
|
252
|
-
case 'EXCESSIVE_RESOURCE_USAGE':
|
|
253
|
-
description = 'Application terminated due to excessive resource usage';
|
|
254
|
-
break;
|
|
255
|
-
case 'INITIALIZATION_FAILURE':
|
|
256
|
-
description = 'Application failed to initialize';
|
|
257
|
-
break;
|
|
258
|
-
default:
|
|
259
|
-
description = crash.description || 'Application crash';
|
|
260
|
-
break;
|
|
261
|
-
}
|
|
271
|
+
// Logging helpers
|
|
272
|
+
override logDebug(message: string, ...args: any[]): void {
|
|
273
|
+
this.internalLogger?.debug?.(message, ...args);
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
override logInfo(message: string, ...args: any[]): void {
|
|
277
|
+
this.internalLogger?.info?.(message, ...args);
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
override logWarn(message: string, ...args: any[]): void {
|
|
281
|
+
this.internalLogger?.warn?.(message, ...args);
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
override logError(message: string, ...args: any[]): void {
|
|
285
|
+
this.internalLogger?.error?.(message, ...args);
|
|
286
|
+
}
|
|
262
287
|
|
|
263
|
-
|
|
288
|
+
unpatch(): void {
|
|
289
|
+
// No patching to undo
|
|
264
290
|
}
|
|
265
291
|
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { BaseInstrumentation, VERSION } from '@grafana/faro-core';
|
|
2
|
+
|
|
3
|
+
import type { CrashReportingOptions } from './types';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Crash reporting stub for platforms other than Android and iOS (e.g. web, macOS, Windows).
|
|
7
|
+
*/
|
|
8
|
+
export class NoOpCrashReportingInstrumentation extends BaseInstrumentation {
|
|
9
|
+
readonly name = '@grafana/faro-react-native:instrumentation-crash-noop';
|
|
10
|
+
readonly version = VERSION;
|
|
11
|
+
|
|
12
|
+
constructor(_options: CrashReportingOptions = {}) {
|
|
13
|
+
super();
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
initialize(): void {
|
|
17
|
+
this.logDebug('Crash reporting is not supported on this platform');
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
unpatch(): void {
|
|
21
|
+
// No patching to undo
|
|
22
|
+
}
|
|
23
|
+
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { VERSION } from '@grafana/faro-core';
|
|
2
|
+
|
|
3
|
+
import { BaseCrashReportingInstrumentation } from '../BaseCrashReportingInstrumentation';
|
|
4
|
+
import type { CrashReport } from '../types';
|
|
5
|
+
|
|
6
|
+
import { resolveCrashErrorMessage, shouldSkipCrashReport } from './crashErrorMessage';
|
|
7
|
+
import { parseAndroidCrashTrace, type ParsedAndroidCrashTrace } from './parseAndroidCrashTrace';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Android Crash Reporting Instrumentation.
|
|
11
|
+
*
|
|
12
|
+
* Uses ApplicationExitInfo API (Android 11+ / API 30+) to retrieve crash reports
|
|
13
|
+
* from previous sessions. Captures:
|
|
14
|
+
* - CRASH: Java/Kotlin exception crashes
|
|
15
|
+
* - CRASH_NATIVE: Native (NDK) crashes
|
|
16
|
+
* - LOW_MEMORY: App killed due to low memory
|
|
17
|
+
* - EXCESSIVE_RESOURCE_USAGE: App killed due to excessive resource usage
|
|
18
|
+
*
|
|
19
|
+
* ANRs are reported separately by ANRInstrumentation, not via ApplicationExitInfo replay.
|
|
20
|
+
*/
|
|
21
|
+
export class AndroidCrashReportingInstrumentation extends BaseCrashReportingInstrumentation {
|
|
22
|
+
readonly name = '@grafana/faro-react-native:instrumentation-crash-android';
|
|
23
|
+
readonly version = VERSION;
|
|
24
|
+
|
|
25
|
+
protected parseCrashTrace(trace: string): ParsedAndroidCrashTrace | null {
|
|
26
|
+
return parseAndroidCrashTrace(trace, {
|
|
27
|
+
releaseBundleFilename: this.options.releaseBundleFilename,
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
protected resolveCrashErrorMessage(crash: CrashReport, parsedTrace: ParsedAndroidCrashTrace | null): string {
|
|
32
|
+
return resolveCrashErrorMessage(crash, parsedTrace);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
protected shouldSkipCrashReport(crash: CrashReport): boolean {
|
|
36
|
+
return shouldSkipCrashReport(crash);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
protected getStackFrames(parsedTrace: ParsedAndroidCrashTrace | null): any[] {
|
|
40
|
+
if (!parsedTrace) {
|
|
41
|
+
return [];
|
|
42
|
+
}
|
|
43
|
+
// Android has JS frames, Java/Kotlin frames (R8 retrace), and NDK tombstone rows (.so retrace).
|
|
44
|
+
return [...(parsedTrace.jsFrames ?? []), ...(parsedTrace.frames ?? []), ...(parsedTrace.nativeFrames ?? [])];
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
protected buildPlatformContext(parsedTrace: ParsedAndroidCrashTrace | null, context: Record<string, string>): void {
|
|
48
|
+
// Store the native Java/Kotlin exception class separately for drill-down
|
|
49
|
+
// Plugin overview metrics key off exception_type = 'crash', so keep that stable
|
|
50
|
+
if (parsedTrace?.exceptionType) {
|
|
51
|
+
context['nativeExceptionType'] = parsedTrace.exceptionType;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
protected getParseFailureWarning(): string {
|
|
56
|
+
return 'Native crash trace present but no frames parsed; check Android trace format or UncaughtExceptionHandler cache';
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
protected getTraceUnavailableWarning(): string {
|
|
60
|
+
return 'Native crash trace unavailable (ApplicationExitInfo traceInputStream was null at crash time)';
|
|
61
|
+
}
|
|
62
|
+
}
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
import type { CrashReport } from '../types';
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
buildFallbackCrashMessage,
|
|
5
|
+
isAnrTimeoutDescription,
|
|
6
|
+
resolveCrashErrorMessage,
|
|
7
|
+
shouldSkipCrashReport,
|
|
8
|
+
shouldSkipLowSignalCrashReport,
|
|
9
|
+
} from './crashErrorMessage';
|
|
10
|
+
import type { ParsedAndroidCrashTrace } from './parseAndroidCrashTrace';
|
|
11
|
+
|
|
12
|
+
const baseCrash: CrashReport = {
|
|
13
|
+
reason: 'CRASH',
|
|
14
|
+
timestamp: 1_700_000_000_000,
|
|
15
|
+
status: 0,
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
describe('resolveCrashErrorMessage', () => {
|
|
19
|
+
it('prefers the parsed trace message', () => {
|
|
20
|
+
const parsed: ParsedAndroidCrashTrace = {
|
|
21
|
+
exceptionMessage: 'Intentional native crash from demo module',
|
|
22
|
+
exceptionType: 'java.lang.RuntimeException',
|
|
23
|
+
jsFrames: [],
|
|
24
|
+
frames: [],
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
expect(resolveCrashErrorMessage(baseCrash, parsed)).toBe('Intentional native crash from demo module');
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
it('uses ApplicationExitInfo description when the trace has no message', () => {
|
|
31
|
+
const crash: CrashReport = {
|
|
32
|
+
...baseCrash,
|
|
33
|
+
description:
|
|
34
|
+
'Input dispatching timed out (com.example.app/.MainActivity is not responding. Waited 5002ms for FocusEvent)',
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
expect(resolveCrashErrorMessage(crash, null)).toBe(
|
|
38
|
+
'Input dispatching timed out (com.example.app/.MainActivity is not responding. Waited 5002ms for FocusEvent)'
|
|
39
|
+
);
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
it('extracts java exception types from non-generic ApplicationExitInfo descriptions', () => {
|
|
43
|
+
const crash: CrashReport = {
|
|
44
|
+
...baseCrash,
|
|
45
|
+
description: 'java.lang.NullPointerException: null reference',
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
expect(resolveCrashErrorMessage(crash, null)).toBe('java.lang.NullPointerException');
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
it('prefers parsed exception type over generic ApplicationExitInfo descriptions such as "crash"', () => {
|
|
52
|
+
const parsed: ParsedAndroidCrashTrace = {
|
|
53
|
+
exceptionType: 'java.lang.NullPointerException',
|
|
54
|
+
jsFrames: [],
|
|
55
|
+
frames: [],
|
|
56
|
+
};
|
|
57
|
+
const crash: CrashReport = {
|
|
58
|
+
...baseCrash,
|
|
59
|
+
description: 'crash',
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
expect(resolveCrashErrorMessage(crash, parsed)).toBe('java.lang.NullPointerException');
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
it('falls back to exception type for message-less traces such as NullPointerException', () => {
|
|
66
|
+
const parsed: ParsedAndroidCrashTrace = {
|
|
67
|
+
exceptionType: 'java.lang.NullPointerException',
|
|
68
|
+
jsFrames: [],
|
|
69
|
+
frames: [],
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
expect(resolveCrashErrorMessage(baseCrash, parsed)).toBe('java.lang.NullPointerException');
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
it('uses generic fallback when nothing else is available', () => {
|
|
76
|
+
expect(resolveCrashErrorMessage(baseCrash, null)).toBe('CRASH: Application crash (Java/Kotlin), status: 0');
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
it('does not treat version-like tokens as exception types', () => {
|
|
80
|
+
const parsed: ParsedAndroidCrashTrace = {
|
|
81
|
+
exceptionType: '18.2213',
|
|
82
|
+
jsFrames: [],
|
|
83
|
+
frames: [
|
|
84
|
+
{
|
|
85
|
+
module: 'com.example.CrashModule',
|
|
86
|
+
function: 'blockMainThread',
|
|
87
|
+
filename: 'CrashModule.kt',
|
|
88
|
+
lineno: 56,
|
|
89
|
+
},
|
|
90
|
+
],
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
expect(resolveCrashErrorMessage(baseCrash, parsed)).toBe('CRASH: Application crash (Java/Kotlin), status: 0');
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
it('falls back when the trace has frames but no parseable message or exception type', () => {
|
|
97
|
+
const parsed: ParsedAndroidCrashTrace = {
|
|
98
|
+
jsFrames: [],
|
|
99
|
+
frames: [
|
|
100
|
+
{
|
|
101
|
+
module: 'com.example.CrashModule',
|
|
102
|
+
function: 'triggerNativeCrash',
|
|
103
|
+
filename: 'CrashModule.kt',
|
|
104
|
+
lineno: 60,
|
|
105
|
+
},
|
|
106
|
+
],
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
expect(resolveCrashErrorMessage(baseCrash, parsed)).toBe('CRASH: Application crash (Java/Kotlin), status: 0');
|
|
110
|
+
});
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
describe('isAnrTimeoutDescription', () => {
|
|
114
|
+
it('detects Android ANR watchdog descriptions', () => {
|
|
115
|
+
expect(
|
|
116
|
+
isAnrTimeoutDescription(
|
|
117
|
+
'Input dispatching timed out (com.example.app/com.example.app.MainActivity is not responding. Waited 5002ms for FocusEvent)'
|
|
118
|
+
)
|
|
119
|
+
).toBe(true);
|
|
120
|
+
expect(isAnrTimeoutDescription('Application Not Responding')).toBe(true);
|
|
121
|
+
expect(isAnrTimeoutDescription('java.lang.RuntimeException: something broke')).toBe(false);
|
|
122
|
+
});
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
describe('shouldSkipCrashReport', () => {
|
|
126
|
+
it('skips ApplicationExitInfo rows with ANR timeout descriptions', () => {
|
|
127
|
+
expect(
|
|
128
|
+
shouldSkipCrashReport({
|
|
129
|
+
...baseCrash,
|
|
130
|
+
description:
|
|
131
|
+
'Input dispatching timed out (com.example.app/com.example.app.MainActivity is not responding. Waited 5002ms for FocusEvent)',
|
|
132
|
+
})
|
|
133
|
+
).toBe(true);
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
it('does not skip real crashes with Java exception descriptions', () => {
|
|
137
|
+
expect(
|
|
138
|
+
shouldSkipCrashReport({
|
|
139
|
+
...baseCrash,
|
|
140
|
+
description: 'java.lang.RuntimeException: Network failure',
|
|
141
|
+
trace: 'java.lang.RuntimeException: Network failure\n at com.example.Foo.bar(Foo.kt:1)',
|
|
142
|
+
})
|
|
143
|
+
).toBe(false);
|
|
144
|
+
});
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
describe('shouldSkipLowSignalCrashReport', () => {
|
|
148
|
+
it('skips trace-less rows with only generic descriptions', () => {
|
|
149
|
+
expect(
|
|
150
|
+
shouldSkipLowSignalCrashReport({
|
|
151
|
+
...baseCrash,
|
|
152
|
+
description: 'Application crash (Java/Kotlin)',
|
|
153
|
+
})
|
|
154
|
+
).toBe(true);
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
it('keeps rows that carry a native trace', () => {
|
|
158
|
+
expect(
|
|
159
|
+
shouldSkipLowSignalCrashReport({
|
|
160
|
+
...baseCrash,
|
|
161
|
+
trace: 'java.lang.NullPointerException\n at com.example.Foo.bar(Foo.kt:1)',
|
|
162
|
+
})
|
|
163
|
+
).toBe(false);
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
it('keeps rows with a specific ApplicationExitInfo description', () => {
|
|
167
|
+
expect(
|
|
168
|
+
shouldSkipLowSignalCrashReport({
|
|
169
|
+
...baseCrash,
|
|
170
|
+
description: 'java.lang.RuntimeException: intentional native crash',
|
|
171
|
+
})
|
|
172
|
+
).toBe(false);
|
|
173
|
+
});
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
describe('buildFallbackCrashMessage', () => {
|
|
177
|
+
it('formats native crash reasons', () => {
|
|
178
|
+
expect(buildFallbackCrashMessage({ ...baseCrash, reason: 'CRASH_NATIVE' })).toBe(
|
|
179
|
+
'CRASH_NATIVE: Application crash (Native), status: 0'
|
|
180
|
+
);
|
|
181
|
+
});
|
|
182
|
+
});
|