@grafana/faro-react-native 1.2.1 → 1.3.1
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/package.json +4 -4
- 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,174 @@
|
|
|
1
|
+
import { parseStackTraceLine, toFaroStackFrames } from '../../errors/stackTraceParser';
|
|
2
|
+
// Mirrors pkg/exporter/androidretrace/mapping.go frameLine / exceptionHeader.
|
|
3
|
+
const FRAME_LINE = /^\s*at\s+([\w$.]+)\.([\w$<>]+)\(([^):]*)(?::(\d+))?\)\s*$/;
|
|
4
|
+
// Thread.getStackTrace() / legacy ANRTracker lines without the "at " prefix.
|
|
5
|
+
const THREAD_STACK_FRAME_LINE = /^\s*([\w$.]+)\.([\w$<>]+)\(([^):]*)(?::(\d+))?\)\s*$/;
|
|
6
|
+
const EXCEPTION_HEADER = /^\s*(?:Caused by:\s*)?([\w$.]+)(?::(.*))?$/;
|
|
7
|
+
// Mirrors pkg/exporter/androidretrace/native_frame.go nativeFrameLine.
|
|
8
|
+
const NATIVE_TOMBSTONE_FRAME_LINE = /^\s*#(\d+)\s+pc\s+(?:0x)?([0-9a-fA-F]+)\s+(\S+)(?:\s+\(([^)]+)\))?/i;
|
|
9
|
+
const TOMBSTONE_SECTION_LABELS = new Set(['backtrace', 'stack', 'build id', 'memory map', 'memory near', 'abi']);
|
|
10
|
+
/** Tombstone section headers and metadata must not become exception_type / nativeExceptionType. */
|
|
11
|
+
export function isTombstoneSectionLabel(value) {
|
|
12
|
+
return TOMBSTONE_SECTION_LABELS.has(value.trim().toLowerCase());
|
|
13
|
+
}
|
|
14
|
+
function isTombstoneMetadataLine(line) {
|
|
15
|
+
const trimmed = line.trim();
|
|
16
|
+
if (!trimmed) {
|
|
17
|
+
return true;
|
|
18
|
+
}
|
|
19
|
+
if (trimmed.startsWith('***')) {
|
|
20
|
+
return true;
|
|
21
|
+
}
|
|
22
|
+
if (/^ABI:/i.test(trimmed)) {
|
|
23
|
+
return true;
|
|
24
|
+
}
|
|
25
|
+
if (/^signal\s+/i.test(trimmed)) {
|
|
26
|
+
return true;
|
|
27
|
+
}
|
|
28
|
+
if (/^pid:/i.test(trimmed)) {
|
|
29
|
+
return true;
|
|
30
|
+
}
|
|
31
|
+
if (isTombstoneSectionLabel(trimmed.replace(/:$/, ''))) {
|
|
32
|
+
return true;
|
|
33
|
+
}
|
|
34
|
+
return false;
|
|
35
|
+
}
|
|
36
|
+
function nativeLibBasename(token) {
|
|
37
|
+
const bang = token.lastIndexOf('!');
|
|
38
|
+
if (bang >= 0) {
|
|
39
|
+
token = token.slice(bang + 1);
|
|
40
|
+
}
|
|
41
|
+
const slash = token.lastIndexOf('/');
|
|
42
|
+
return slash >= 0 ? token.slice(slash + 1) : token;
|
|
43
|
+
}
|
|
44
|
+
function parseNativeTombstoneFrameLine(line) {
|
|
45
|
+
var _a, _b;
|
|
46
|
+
const match = line.match(NATIVE_TOMBSTONE_FRAME_LINE);
|
|
47
|
+
if (!match) {
|
|
48
|
+
return null;
|
|
49
|
+
}
|
|
50
|
+
const library = (_a = match[3]) !== null && _a !== void 0 ? _a : '';
|
|
51
|
+
const suffix = (_b = match[4]) === null || _b === void 0 ? void 0 : _b.trim();
|
|
52
|
+
let fn = '<unknown>';
|
|
53
|
+
if (suffix) {
|
|
54
|
+
const fnMatch = suffix.match(/^([A-Za-z_][A-Za-z0-9_]*)(?:\+(\d+))?$/);
|
|
55
|
+
if ((fnMatch === null || fnMatch === void 0 ? void 0 : fnMatch[1]) && fnMatch[1] !== 'BuildId' && fnMatch[1] !== 'offset') {
|
|
56
|
+
fn = fnMatch[1];
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
return {
|
|
60
|
+
module: nativeLibBasename(library),
|
|
61
|
+
function: fn,
|
|
62
|
+
filename: nativeLibBasename(library),
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
/** Reject version tokens (e.g. "18.2213") and other non-class header lines. */
|
|
66
|
+
export function isPlausibleJavaExceptionIdentifier(value) {
|
|
67
|
+
const trimmed = value.trim();
|
|
68
|
+
if (!trimmed || !/[a-zA-Z]/.test(trimmed)) {
|
|
69
|
+
return false;
|
|
70
|
+
}
|
|
71
|
+
if (/^\d+(\.\d+)+$/.test(trimmed)) {
|
|
72
|
+
return false;
|
|
73
|
+
}
|
|
74
|
+
return true;
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Normalize raw Java thread stacks (e.g. ANRTracker) to Log.getStackTraceString shape
|
|
78
|
+
* so collector R8 retrace (mapping.RetraceText) can rewrite frame lines.
|
|
79
|
+
*/
|
|
80
|
+
export function normalizeJavaStackTraceForRetrace(trace) {
|
|
81
|
+
return trace
|
|
82
|
+
.split('\n')
|
|
83
|
+
.map((line) => {
|
|
84
|
+
const trimmed = line.trim();
|
|
85
|
+
if (!trimmed) {
|
|
86
|
+
return line;
|
|
87
|
+
}
|
|
88
|
+
if (/^\s*at\s+/.test(line)) {
|
|
89
|
+
return line;
|
|
90
|
+
}
|
|
91
|
+
if (THREAD_STACK_FRAME_LINE.test(trimmed)) {
|
|
92
|
+
return ` at ${trimmed}`;
|
|
93
|
+
}
|
|
94
|
+
return line;
|
|
95
|
+
})
|
|
96
|
+
.join('\n');
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* Normalize exception text from ApplicationExitInfo / Log.getStackTraceString headers.
|
|
100
|
+
*
|
|
101
|
+
* React Native fatal JS errors embed the JS message in a fixed header shape
|
|
102
|
+
* (`Error: <message>, stack:`) regardless of the wrapping Java exception class.
|
|
103
|
+
* Match on that shape instead of the exception type name.
|
|
104
|
+
*/
|
|
105
|
+
const RN_FATAL_JS_ERROR_HEADER = /^Error:\s*(.+?),\s*stack:\s*$/i;
|
|
106
|
+
export function normalizeCrashTraceExceptionMessage(rawMessage) {
|
|
107
|
+
var _a;
|
|
108
|
+
if (!(rawMessage === null || rawMessage === void 0 ? void 0 : rawMessage.trim())) {
|
|
109
|
+
return undefined;
|
|
110
|
+
}
|
|
111
|
+
const message = rawMessage.trim();
|
|
112
|
+
const rnFatalMatch = message.match(RN_FATAL_JS_ERROR_HEADER);
|
|
113
|
+
return ((_a = rnFatalMatch === null || rnFatalMatch === void 0 ? void 0 : rnFatalMatch[1]) !== null && _a !== void 0 ? _a : message).trim() || undefined;
|
|
114
|
+
}
|
|
115
|
+
function parseJsFrameLine(line, options) {
|
|
116
|
+
var _a;
|
|
117
|
+
const parsed = parseStackTraceLine(line);
|
|
118
|
+
if (!parsed) {
|
|
119
|
+
return null;
|
|
120
|
+
}
|
|
121
|
+
return (_a = toFaroStackFrames([parsed], options)[0]) !== null && _a !== void 0 ? _a : null;
|
|
122
|
+
}
|
|
123
|
+
/**
|
|
124
|
+
* Parse a raw Android crash trace into structured stack frames for pushError().
|
|
125
|
+
*/
|
|
126
|
+
export function parseAndroidCrashTrace(trace, options) {
|
|
127
|
+
const trimmed = trace.trim();
|
|
128
|
+
if (!trimmed) {
|
|
129
|
+
return null;
|
|
130
|
+
}
|
|
131
|
+
const lines = trimmed.split('\n');
|
|
132
|
+
const jsFrames = [];
|
|
133
|
+
const frames = [];
|
|
134
|
+
const nativeFrames = [];
|
|
135
|
+
let exceptionType;
|
|
136
|
+
let exceptionMessage;
|
|
137
|
+
lines.forEach((line) => {
|
|
138
|
+
var _a;
|
|
139
|
+
const nativeFrame = parseNativeTombstoneFrameLine(line);
|
|
140
|
+
if (nativeFrame) {
|
|
141
|
+
nativeFrames.push(nativeFrame);
|
|
142
|
+
return;
|
|
143
|
+
}
|
|
144
|
+
const frameMatch = (_a = line.match(FRAME_LINE)) !== null && _a !== void 0 ? _a : line.trim().match(THREAD_STACK_FRAME_LINE);
|
|
145
|
+
if (frameMatch) {
|
|
146
|
+
const [, module, method, filename, lineNo] = frameMatch;
|
|
147
|
+
frames.push({
|
|
148
|
+
module: module !== null && module !== void 0 ? module : '',
|
|
149
|
+
function: method !== null && method !== void 0 ? method : '<unknown>',
|
|
150
|
+
filename: (filename === null || filename === void 0 ? void 0 : filename.trim()) || 'SourceFile',
|
|
151
|
+
lineno: lineNo ? parseInt(lineNo, 10) : undefined,
|
|
152
|
+
});
|
|
153
|
+
return;
|
|
154
|
+
}
|
|
155
|
+
const jsFrame = parseJsFrameLine(line, options);
|
|
156
|
+
if (jsFrame) {
|
|
157
|
+
jsFrames.push(jsFrame);
|
|
158
|
+
return;
|
|
159
|
+
}
|
|
160
|
+
const headerMatch = line.match(EXCEPTION_HEADER);
|
|
161
|
+
if (!isTombstoneMetadataLine(line) &&
|
|
162
|
+
(headerMatch === null || headerMatch === void 0 ? void 0 : headerMatch[1]) &&
|
|
163
|
+
isPlausibleJavaExceptionIdentifier(headerMatch[1]) &&
|
|
164
|
+
!isTombstoneSectionLabel(headerMatch[1])) {
|
|
165
|
+
exceptionType = headerMatch[1];
|
|
166
|
+
exceptionMessage = normalizeCrashTraceExceptionMessage(headerMatch[2]);
|
|
167
|
+
}
|
|
168
|
+
});
|
|
169
|
+
if (frames.length === 0 && jsFrames.length === 0 && nativeFrames.length === 0 && !exceptionType) {
|
|
170
|
+
return null;
|
|
171
|
+
}
|
|
172
|
+
return { exceptionType, exceptionMessage, jsFrames, frames, nativeFrames };
|
|
173
|
+
}
|
|
174
|
+
//# sourceMappingURL=parseAndroidCrashTrace.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"parseAndroidCrashTrace.js","sourceRoot":"","sources":["../../../../../src/instrumentations/crashReporting/android/parseAndroidCrashTrace.ts"],"names":[],"mappings":"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","sourcesContent":["import type { ExceptionStackFrame } from '@grafana/faro-core';\n\nimport { parseStackTraceLine, toFaroStackFrames } from '../../errors/stackTraceParser';\n\n/**\n * Android ApplicationExitInfo traces use Java/Kotlin stack-trace formatting.\n * Faro ingest expects structured frames with `module` (obfuscated class) so the\n * collector can R8-retrace them — not the JS call stack from sendCrashReport().\n *\n * Fatal RN JS errors also embed a short Hermes/Metro stack before native frames\n * (e.g. `anonymous@1:1390953`). Those lines are parsed as JS frames (no `module`)\n * so the collector can source-map them to files like DebugScreen.tsx.\n */\nexport interface AndroidCrashStackFrame extends ExceptionStackFrame {\n module: string;\n}\n\nexport interface ParseAndroidCrashTraceOptions {\n releaseBundleFilename?: string;\n}\n\nexport interface NativeTombstoneStackFrame extends ExceptionStackFrame {\n /** Unstripped .so basename (e.g. libappmodules.so) for NDK retrace on the collector. */\n module: string;\n}\n\nexport interface ParsedAndroidCrashTrace {\n exceptionType?: string;\n /** Human-readable message from the trace header (e.g. the JS error text). */\n exceptionMessage?: string;\n /** JS/Hermes frames from the embedded RN stack (source-map targets). */\n jsFrames: ExceptionStackFrame[];\n /** Native Java/Kotlin frames (Android R8 retrace targets). */\n frames: AndroidCrashStackFrame[];\n /** NDK tombstone #NN pc lines (server-side .so retrace targets). */\n nativeFrames: NativeTombstoneStackFrame[];\n}\n\n// Mirrors pkg/exporter/androidretrace/mapping.go frameLine / exceptionHeader.\nconst FRAME_LINE = /^\\s*at\\s+([\\w$.]+)\\.([\\w$<>]+)\\(([^):]*)(?::(\\d+))?\\)\\s*$/;\n// Thread.getStackTrace() / legacy ANRTracker lines without the \"at \" prefix.\nconst THREAD_STACK_FRAME_LINE = /^\\s*([\\w$.]+)\\.([\\w$<>]+)\\(([^):]*)(?::(\\d+))?\\)\\s*$/;\nconst EXCEPTION_HEADER = /^\\s*(?:Caused by:\\s*)?([\\w$.]+)(?::(.*))?$/;\n// Mirrors pkg/exporter/androidretrace/native_frame.go nativeFrameLine.\nconst NATIVE_TOMBSTONE_FRAME_LINE = /^\\s*#(\\d+)\\s+pc\\s+(?:0x)?([0-9a-fA-F]+)\\s+(\\S+)(?:\\s+\\(([^)]+)\\))?/i;\n\nconst TOMBSTONE_SECTION_LABELS = new Set(['backtrace', 'stack', 'build id', 'memory map', 'memory near', 'abi']);\n\n/** Tombstone section headers and metadata must not become exception_type / nativeExceptionType. */\nexport function isTombstoneSectionLabel(value: string): boolean {\n return TOMBSTONE_SECTION_LABELS.has(value.trim().toLowerCase());\n}\n\nfunction isTombstoneMetadataLine(line: string): boolean {\n const trimmed = line.trim();\n if (!trimmed) {\n return true;\n }\n if (trimmed.startsWith('***')) {\n return true;\n }\n if (/^ABI:/i.test(trimmed)) {\n return true;\n }\n if (/^signal\\s+/i.test(trimmed)) {\n return true;\n }\n if (/^pid:/i.test(trimmed)) {\n return true;\n }\n if (isTombstoneSectionLabel(trimmed.replace(/:$/, ''))) {\n return true;\n }\n return false;\n}\n\nfunction nativeLibBasename(token: string): string {\n const bang = token.lastIndexOf('!');\n if (bang >= 0) {\n token = token.slice(bang + 1);\n }\n const slash = token.lastIndexOf('/');\n return slash >= 0 ? token.slice(slash + 1) : token;\n}\n\nfunction parseNativeTombstoneFrameLine(line: string): NativeTombstoneStackFrame | null {\n const match = line.match(NATIVE_TOMBSTONE_FRAME_LINE);\n if (!match) {\n return null;\n }\n\n const library = match[3] ?? '';\n const suffix = match[4]?.trim();\n let fn = '<unknown>';\n if (suffix) {\n const fnMatch = suffix.match(/^([A-Za-z_][A-Za-z0-9_]*)(?:\\+(\\d+))?$/);\n if (fnMatch?.[1] && fnMatch[1] !== 'BuildId' && fnMatch[1] !== 'offset') {\n fn = fnMatch[1];\n }\n }\n\n return {\n module: nativeLibBasename(library),\n function: fn,\n filename: nativeLibBasename(library),\n };\n}\n\n/** Reject version tokens (e.g. \"18.2213\") and other non-class header lines. */\nexport function isPlausibleJavaExceptionIdentifier(value: string): boolean {\n const trimmed = value.trim();\n if (!trimmed || !/[a-zA-Z]/.test(trimmed)) {\n return false;\n }\n if (/^\\d+(\\.\\d+)+$/.test(trimmed)) {\n return false;\n }\n return true;\n}\n\n/**\n * Normalize raw Java thread stacks (e.g. ANRTracker) to Log.getStackTraceString shape\n * so collector R8 retrace (mapping.RetraceText) can rewrite frame lines.\n */\nexport function normalizeJavaStackTraceForRetrace(trace: string): string {\n return trace\n .split('\\n')\n .map((line) => {\n const trimmed = line.trim();\n if (!trimmed) {\n return line;\n }\n if (/^\\s*at\\s+/.test(line)) {\n return line;\n }\n if (THREAD_STACK_FRAME_LINE.test(trimmed)) {\n return ` at ${trimmed}`;\n }\n return line;\n })\n .join('\\n');\n}\n\n/**\n * Normalize exception text from ApplicationExitInfo / Log.getStackTraceString headers.\n *\n * React Native fatal JS errors embed the JS message in a fixed header shape\n * (`Error: <message>, stack:`) regardless of the wrapping Java exception class.\n * Match on that shape instead of the exception type name.\n */\nconst RN_FATAL_JS_ERROR_HEADER = /^Error:\\s*(.+?),\\s*stack:\\s*$/i;\n\nexport function normalizeCrashTraceExceptionMessage(rawMessage: string | undefined): string | undefined {\n if (!rawMessage?.trim()) {\n return undefined;\n }\n\n const message = rawMessage.trim();\n const rnFatalMatch = message.match(RN_FATAL_JS_ERROR_HEADER);\n\n return (rnFatalMatch?.[1] ?? message).trim() || undefined;\n}\n\nfunction parseJsFrameLine(line: string, options?: ParseAndroidCrashTraceOptions): ExceptionStackFrame | null {\n const parsed = parseStackTraceLine(line);\n if (!parsed) {\n return null;\n }\n\n return toFaroStackFrames([parsed], options)[0] ?? null;\n}\n\n/**\n * Parse a raw Android crash trace into structured stack frames for pushError().\n */\nexport function parseAndroidCrashTrace(\n trace: string,\n options?: ParseAndroidCrashTraceOptions\n): ParsedAndroidCrashTrace | null {\n const trimmed = trace.trim();\n if (!trimmed) {\n return null;\n }\n\n const lines = trimmed.split('\\n');\n const jsFrames: ExceptionStackFrame[] = [];\n const frames: AndroidCrashStackFrame[] = [];\n const nativeFrames: NativeTombstoneStackFrame[] = [];\n let exceptionType: string | undefined;\n let exceptionMessage: string | undefined;\n\n lines.forEach((line) => {\n const nativeFrame = parseNativeTombstoneFrameLine(line);\n if (nativeFrame) {\n nativeFrames.push(nativeFrame);\n return;\n }\n\n const frameMatch = line.match(FRAME_LINE) ?? line.trim().match(THREAD_STACK_FRAME_LINE);\n\n if (frameMatch) {\n const [, module, method, filename, lineNo] = frameMatch;\n frames.push({\n module: module ?? '',\n function: method ?? '<unknown>',\n filename: filename?.trim() || 'SourceFile',\n lineno: lineNo ? parseInt(lineNo, 10) : undefined,\n });\n return;\n }\n\n const jsFrame = parseJsFrameLine(line, options);\n if (jsFrame) {\n jsFrames.push(jsFrame);\n return;\n }\n\n const headerMatch = line.match(EXCEPTION_HEADER);\n if (\n !isTombstoneMetadataLine(line) &&\n headerMatch?.[1] &&\n isPlausibleJavaExceptionIdentifier(headerMatch[1]) &&\n !isTombstoneSectionLabel(headerMatch[1])\n ) {\n exceptionType = headerMatch[1];\n exceptionMessage = normalizeCrashTraceExceptionMessage(headerMatch[2]);\n }\n });\n\n if (frames.length === 0 && jsFrames.length === 0 && nativeFrames.length === 0 && !exceptionType) {\n return null;\n }\n\n return { exceptionType, exceptionMessage, jsFrames, frames, nativeFrames };\n}\n"]}
|
|
@@ -1,2 +1,36 @@
|
|
|
1
|
-
|
|
1
|
+
import { Platform } from 'react-native';
|
|
2
|
+
import { AndroidCrashReportingInstrumentation } from './android';
|
|
3
|
+
import { IosCrashReportingInstrumentation } from './ios';
|
|
4
|
+
import { NoOpCrashReportingInstrumentation } from './NoOpCrashReportingInstrumentation';
|
|
5
|
+
function resolveCrashReportingInstrumentation() {
|
|
6
|
+
if (Platform.OS === 'android') {
|
|
7
|
+
return AndroidCrashReportingInstrumentation;
|
|
8
|
+
}
|
|
9
|
+
if (Platform.OS === 'ios') {
|
|
10
|
+
// React Native reports both iPhone and iPadOS as "ios".
|
|
11
|
+
return IosCrashReportingInstrumentation;
|
|
12
|
+
}
|
|
13
|
+
return NoOpCrashReportingInstrumentation;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Platform-aware crash reporting instrumentation export.
|
|
17
|
+
*
|
|
18
|
+
* At module load time, determines the platform and exports the correct implementation:
|
|
19
|
+
* - Android: Uses ApplicationExitInfo API to retrieve crashes
|
|
20
|
+
* - iOS: Uses PLCrashReporter to retrieve crashes
|
|
21
|
+
* - Other: No-op stub (web, macOS, Windows, etc.)
|
|
22
|
+
*
|
|
23
|
+
* Usage:
|
|
24
|
+
* ```ts
|
|
25
|
+
* import { CrashReportingInstrumentation } from '@grafana/faro-react-native/instrumentation-crash';
|
|
26
|
+
*
|
|
27
|
+
* const faro = initializeFaro({
|
|
28
|
+
* // ...
|
|
29
|
+
* instrumentations: [
|
|
30
|
+
* new CrashReportingInstrumentation(),
|
|
31
|
+
* ],
|
|
32
|
+
* });
|
|
33
|
+
* ```
|
|
34
|
+
*/
|
|
35
|
+
export const CrashReportingInstrumentation = resolveCrashReportingInstrumentation();
|
|
2
36
|
//# sourceMappingURL=index.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../src/instrumentations/crashReporting/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../src/instrumentations/crashReporting/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,cAAc,CAAC;AAExC,OAAO,EAAE,oCAAoC,EAAE,MAAM,WAAW,CAAC;AACjE,OAAO,EAAE,gCAAgC,EAAE,MAAM,OAAO,CAAC;AACzD,OAAO,EAAE,iCAAiC,EAAE,MAAM,qCAAqC,CAAC;AAExF,SAAS,oCAAoC;IAC3C,IAAI,QAAQ,CAAC,EAAE,KAAK,SAAS,EAAE,CAAC;QAC9B,OAAO,oCAAoC,CAAC;IAC9C,CAAC;IAED,IAAI,QAAQ,CAAC,EAAE,KAAK,KAAK,EAAE,CAAC;QAC1B,wDAAwD;QACxD,OAAO,gCAAgC,CAAC;IAC1C,CAAC;IAED,OAAO,iCAAiC,CAAC;AAC3C,CAAC;AAED;;;;;;;;;;;;;;;;;;;GAmBG;AACH,MAAM,CAAC,MAAM,6BAA6B,GAAG,oCAAoC,EAAE,CAAC","sourcesContent":["import { Platform } from 'react-native';\n\nimport { AndroidCrashReportingInstrumentation } from './android';\nimport { IosCrashReportingInstrumentation } from './ios';\nimport { NoOpCrashReportingInstrumentation } from './NoOpCrashReportingInstrumentation';\n\nfunction resolveCrashReportingInstrumentation() {\n if (Platform.OS === 'android') {\n return AndroidCrashReportingInstrumentation;\n }\n\n if (Platform.OS === 'ios') {\n // React Native reports both iPhone and iPadOS as \"ios\".\n return IosCrashReportingInstrumentation;\n }\n\n return NoOpCrashReportingInstrumentation;\n}\n\n/**\n * Platform-aware crash reporting instrumentation export.\n *\n * At module load time, determines the platform and exports the correct implementation:\n * - Android: Uses ApplicationExitInfo API to retrieve crashes\n * - iOS: Uses PLCrashReporter to retrieve crashes\n * - Other: No-op stub (web, macOS, Windows, etc.)\n *\n * Usage:\n * ```ts\n * import { CrashReportingInstrumentation } from '@grafana/faro-react-native/instrumentation-crash';\n *\n * const faro = initializeFaro({\n * // ...\n * instrumentations: [\n * new CrashReportingInstrumentation(),\n * ],\n * });\n * ```\n */\nexport const CrashReportingInstrumentation = resolveCrashReportingInstrumentation();\n\n// Export public API types\nexport type { CrashReportingOptions } from './types';\n"]}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { VERSION } from '@grafana/faro-core';
|
|
2
|
+
import { BaseCrashReportingInstrumentation } from '../BaseCrashReportingInstrumentation';
|
|
3
|
+
import { resolveCrashErrorMessage, shouldSkipCrashReport } from './crashErrorMessage';
|
|
4
|
+
import { parseIosCrashTrace } from './parseIosCrashTrace';
|
|
5
|
+
/**
|
|
6
|
+
* iOS Crash Reporting Instrumentation.
|
|
7
|
+
*
|
|
8
|
+
* Uses PLCrashReporter to capture native crashes from previous sessions:
|
|
9
|
+
* - SIGSEGV: Segmentation fault / invalid memory access
|
|
10
|
+
* - SIGABRT: Abnormal termination (e.g., assertion failure, uncaught exception)
|
|
11
|
+
* - SIGBUS: Bus error / alignment/hardware error
|
|
12
|
+
* - SIGILL: Illegal instruction
|
|
13
|
+
* - SIGFPE: Floating point exception
|
|
14
|
+
* - SIGTRAP: Breakpoint/debugger trap
|
|
15
|
+
* - Mach exceptions (EXC_BAD_ACCESS, EXC_CRASH, etc.)
|
|
16
|
+
*
|
|
17
|
+
* Stack frames are symbolicated on the native side before being surfaced.
|
|
18
|
+
*/
|
|
19
|
+
export class IosCrashReportingInstrumentation extends BaseCrashReportingInstrumentation {
|
|
20
|
+
constructor() {
|
|
21
|
+
super(...arguments);
|
|
22
|
+
this.name = '@grafana/faro-react-native:instrumentation-crash-ios';
|
|
23
|
+
this.version = VERSION;
|
|
24
|
+
}
|
|
25
|
+
parseCrashTrace(trace) {
|
|
26
|
+
return parseIosCrashTrace(trace);
|
|
27
|
+
}
|
|
28
|
+
resolveCrashErrorMessage(crash, parsedTrace) {
|
|
29
|
+
return resolveCrashErrorMessage(crash, parsedTrace);
|
|
30
|
+
}
|
|
31
|
+
shouldSkipCrashReport(crash) {
|
|
32
|
+
return shouldSkipCrashReport(crash);
|
|
33
|
+
}
|
|
34
|
+
getStackFrames(parsedTrace) {
|
|
35
|
+
var _a;
|
|
36
|
+
return (_a = parsedTrace === null || parsedTrace === void 0 ? void 0 : parsedTrace.frames) !== null && _a !== void 0 ? _a : [];
|
|
37
|
+
}
|
|
38
|
+
buildPlatformContext(parsedTrace, context) {
|
|
39
|
+
if (parsedTrace === null || parsedTrace === void 0 ? void 0 : parsedTrace.exceptionName) {
|
|
40
|
+
context['nativeExceptionName'] = parsedTrace.exceptionName;
|
|
41
|
+
}
|
|
42
|
+
if (parsedTrace === null || parsedTrace === void 0 ? void 0 : parsedTrace.signalName) {
|
|
43
|
+
context['signalName'] = parsedTrace.signalName;
|
|
44
|
+
}
|
|
45
|
+
if (parsedTrace === null || parsedTrace === void 0 ? void 0 : parsedTrace.signalDescription) {
|
|
46
|
+
context['signalDescription'] = parsedTrace.signalDescription;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
getParseFailureWarning() {
|
|
50
|
+
return 'Native crash trace present but no frames parsed; check iOS PLCrashReporter format';
|
|
51
|
+
}
|
|
52
|
+
getTraceUnavailableWarning() {
|
|
53
|
+
return 'Native crash trace unavailable (PLCrashReporter trace was null at crash time)';
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
//# sourceMappingURL=IosCrashReportingInstrumentation.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"IosCrashReportingInstrumentation.js","sourceRoot":"","sources":["../../../../../src/instrumentations/crashReporting/ios/IosCrashReportingInstrumentation.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,oBAAoB,CAAC;AAE7C,OAAO,EAAE,iCAAiC,EAAE,MAAM,sCAAsC,CAAC;AAGzF,OAAO,EAAE,wBAAwB,EAAE,qBAAqB,EAAE,MAAM,qBAAqB,CAAC;AACtF,OAAO,EAA4B,kBAAkB,EAAE,MAAM,sBAAsB,CAAC;AAEpF;;;;;;;;;;;;;GAaG;AACH,MAAM,OAAO,gCAAiC,SAAQ,iCAAiC;IAAvF;;QACW,SAAI,GAAG,sDAAsD,CAAC;QAC9D,YAAO,GAAG,OAAO,CAAC;IAqC7B,CAAC;IAnCW,eAAe,CAAC,KAAa;QACrC,OAAO,kBAAkB,CAAC,KAAK,CAAC,CAAC;IACnC,CAAC;IAES,wBAAwB,CAAC,KAAkB,EAAE,WAAuC;QAC5F,OAAO,wBAAwB,CAAC,KAAK,EAAE,WAAW,CAAC,CAAC;IACtD,CAAC;IAES,qBAAqB,CAAC,KAAkB;QAChD,OAAO,qBAAqB,CAAC,KAAK,CAAC,CAAC;IACtC,CAAC;IAES,cAAc,CAAC,WAAuC;;QAC9D,OAAO,MAAA,WAAW,aAAX,WAAW,uBAAX,WAAW,CAAE,MAAM,mCAAI,EAAE,CAAC;IACnC,CAAC;IAES,oBAAoB,CAAC,WAAuC,EAAE,OAA+B;QACrG,IAAI,WAAW,aAAX,WAAW,uBAAX,WAAW,CAAE,aAAa,EAAE,CAAC;YAC/B,OAAO,CAAC,qBAAqB,CAAC,GAAG,WAAW,CAAC,aAAa,CAAC;QAC7D,CAAC;QACD,IAAI,WAAW,aAAX,WAAW,uBAAX,WAAW,CAAE,UAAU,EAAE,CAAC;YAC5B,OAAO,CAAC,YAAY,CAAC,GAAG,WAAW,CAAC,UAAU,CAAC;QACjD,CAAC;QACD,IAAI,WAAW,aAAX,WAAW,uBAAX,WAAW,CAAE,iBAAiB,EAAE,CAAC;YACnC,OAAO,CAAC,mBAAmB,CAAC,GAAG,WAAW,CAAC,iBAAiB,CAAC;QAC/D,CAAC;IACH,CAAC;IAES,sBAAsB;QAC9B,OAAO,mFAAmF,CAAC;IAC7F,CAAC;IAES,0BAA0B;QAClC,OAAO,+EAA+E,CAAC;IACzF,CAAC;CACF","sourcesContent":["import { VERSION } from '@grafana/faro-core';\n\nimport { BaseCrashReportingInstrumentation } from '../BaseCrashReportingInstrumentation';\nimport type { CrashReport } from '../types';\n\nimport { resolveCrashErrorMessage, shouldSkipCrashReport } from './crashErrorMessage';\nimport { type ParsedIosCrashTrace, parseIosCrashTrace } from './parseIosCrashTrace';\n\n/**\n * iOS Crash Reporting Instrumentation.\n *\n * Uses PLCrashReporter to capture native crashes from previous sessions:\n * - SIGSEGV: Segmentation fault / invalid memory access\n * - SIGABRT: Abnormal termination (e.g., assertion failure, uncaught exception)\n * - SIGBUS: Bus error / alignment/hardware error\n * - SIGILL: Illegal instruction\n * - SIGFPE: Floating point exception\n * - SIGTRAP: Breakpoint/debugger trap\n * - Mach exceptions (EXC_BAD_ACCESS, EXC_CRASH, etc.)\n *\n * Stack frames are symbolicated on the native side before being surfaced.\n */\nexport class IosCrashReportingInstrumentation extends BaseCrashReportingInstrumentation {\n readonly name = '@grafana/faro-react-native:instrumentation-crash-ios';\n readonly version = VERSION;\n\n protected parseCrashTrace(trace: string): ParsedIosCrashTrace | null {\n return parseIosCrashTrace(trace);\n }\n\n protected resolveCrashErrorMessage(crash: CrashReport, parsedTrace: ParsedIosCrashTrace | null): string {\n return resolveCrashErrorMessage(crash, parsedTrace);\n }\n\n protected shouldSkipCrashReport(crash: CrashReport): boolean {\n return shouldSkipCrashReport(crash);\n }\n\n protected getStackFrames(parsedTrace: ParsedIosCrashTrace | null): any[] {\n return parsedTrace?.frames ?? [];\n }\n\n protected buildPlatformContext(parsedTrace: ParsedIosCrashTrace | null, context: Record<string, string>): void {\n if (parsedTrace?.exceptionName) {\n context['nativeExceptionName'] = parsedTrace.exceptionName;\n }\n if (parsedTrace?.signalName) {\n context['signalName'] = parsedTrace.signalName;\n }\n if (parsedTrace?.signalDescription) {\n context['signalDescription'] = parsedTrace.signalDescription;\n }\n }\n\n protected getParseFailureWarning(): string {\n return 'Native crash trace present but no frames parsed; check iOS PLCrashReporter format';\n }\n\n protected getTraceUnavailableWarning(): string {\n return 'Native crash trace unavailable (PLCrashReporter trace was null at crash time)';\n }\n}\n"]}
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Human-readable exception value for iOS PLCrashReporter crash reports.
|
|
3
|
+
* Priority: exception reason → signal description → signal name → fallback.
|
|
4
|
+
*/
|
|
5
|
+
export function resolveCrashErrorMessage(crash, parsedTrace) {
|
|
6
|
+
var _a, _b, _c, _d, _e;
|
|
7
|
+
// iOS uncaught exceptions (NSException) have name and reason
|
|
8
|
+
if ((parsedTrace === null || parsedTrace === void 0 ? void 0 : parsedTrace.exceptionName) || (parsedTrace === null || parsedTrace === void 0 ? void 0 : parsedTrace.exceptionReason)) {
|
|
9
|
+
const name = parsedTrace.exceptionName || 'Exception';
|
|
10
|
+
const reason = (_a = parsedTrace.exceptionReason) === null || _a === void 0 ? void 0 : _a.trim();
|
|
11
|
+
if (reason) {
|
|
12
|
+
return `${name}: ${reason}`;
|
|
13
|
+
}
|
|
14
|
+
return name;
|
|
15
|
+
}
|
|
16
|
+
// Use signal description if available from parsed trace
|
|
17
|
+
if ((_b = parsedTrace === null || parsedTrace === void 0 ? void 0 : parsedTrace.signalDescription) === null || _b === void 0 ? void 0 : _b.trim()) {
|
|
18
|
+
return parsedTrace.signalDescription.trim();
|
|
19
|
+
}
|
|
20
|
+
// Use signal name from parsed trace
|
|
21
|
+
if ((_c = parsedTrace === null || parsedTrace === void 0 ? void 0 : parsedTrace.signalName) === null || _c === void 0 ? void 0 : _c.trim()) {
|
|
22
|
+
const signalName = parsedTrace.signalName.trim();
|
|
23
|
+
const description = SIGNAL_DESCRIPTIONS[signalName];
|
|
24
|
+
return description || signalName;
|
|
25
|
+
}
|
|
26
|
+
// Check crash description - use if meaningful
|
|
27
|
+
const description = (_d = crash.description) === null || _d === void 0 ? void 0 : _d.trim();
|
|
28
|
+
if (description && !isGenericCrashDescription(description)) {
|
|
29
|
+
return description;
|
|
30
|
+
}
|
|
31
|
+
// Check if we have a known signal reason
|
|
32
|
+
const reason = (_e = crash.reason) === null || _e === void 0 ? void 0 : _e.trim();
|
|
33
|
+
if (reason && SIGNAL_DESCRIPTIONS[reason]) {
|
|
34
|
+
// If description is explicitly generic, use full format
|
|
35
|
+
if (description && isGenericCrashDescription(description)) {
|
|
36
|
+
return buildFallbackCrashMessage(crash);
|
|
37
|
+
}
|
|
38
|
+
// No description - use simple signal description
|
|
39
|
+
return SIGNAL_DESCRIPTIONS[reason];
|
|
40
|
+
}
|
|
41
|
+
// Final fallback: format with signal name, description, and status
|
|
42
|
+
return buildFallbackCrashMessage(crash);
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Skip crash reports that have no useful information.
|
|
46
|
+
* iOS crashes should always have either a trace or a signal.
|
|
47
|
+
*/
|
|
48
|
+
export function shouldSkipCrashReport(crash) {
|
|
49
|
+
var _a, _b, _c;
|
|
50
|
+
if ((_a = crash.trace) === null || _a === void 0 ? void 0 : _a.trim()) {
|
|
51
|
+
return false;
|
|
52
|
+
}
|
|
53
|
+
if ((_b = crash.signal) === null || _b === void 0 ? void 0 : _b.trim()) {
|
|
54
|
+
return false;
|
|
55
|
+
}
|
|
56
|
+
const description = (_c = crash.description) === null || _c === void 0 ? void 0 : _c.trim();
|
|
57
|
+
if (description && !isGenericCrashDescription(description)) {
|
|
58
|
+
return false;
|
|
59
|
+
}
|
|
60
|
+
return true;
|
|
61
|
+
}
|
|
62
|
+
/** BSD signal names to human-readable descriptions */
|
|
63
|
+
const SIGNAL_DESCRIPTIONS = {
|
|
64
|
+
SIGHUP: 'Hangup',
|
|
65
|
+
SIGINT: 'Interrupt',
|
|
66
|
+
SIGQUIT: 'Quit',
|
|
67
|
+
SIGILL: 'Illegal instruction',
|
|
68
|
+
SIGTRAP: 'Trace/BPT trap',
|
|
69
|
+
SIGABRT: 'Abort trap',
|
|
70
|
+
SIGEMT: 'EMT trap',
|
|
71
|
+
SIGFPE: 'Floating point exception',
|
|
72
|
+
SIGKILL: 'Killed',
|
|
73
|
+
SIGBUS: 'Bus error',
|
|
74
|
+
SIGSEGV: 'Segmentation fault',
|
|
75
|
+
SIGSYS: 'Bad system call',
|
|
76
|
+
SIGPIPE: 'Broken pipe',
|
|
77
|
+
SIGALRM: 'Alarm clock',
|
|
78
|
+
SIGTERM: 'Terminated',
|
|
79
|
+
};
|
|
80
|
+
function isGenericCrashDescription(description) {
|
|
81
|
+
const normalized = description.trim().toLowerCase();
|
|
82
|
+
return normalized === 'crash' || normalized === 'application crash' || normalized === 'signal crash';
|
|
83
|
+
}
|
|
84
|
+
/** Generic fallback when the trace carries no parseable signal or description. */
|
|
85
|
+
export function buildFallbackCrashMessage(crash) {
|
|
86
|
+
var _a;
|
|
87
|
+
const reason = crash.reason || 'UNKNOWN';
|
|
88
|
+
const status = (_a = crash.status) !== null && _a !== void 0 ? _a : 0;
|
|
89
|
+
let description;
|
|
90
|
+
if (SIGNAL_DESCRIPTIONS[reason]) {
|
|
91
|
+
description = SIGNAL_DESCRIPTIONS[reason];
|
|
92
|
+
}
|
|
93
|
+
else {
|
|
94
|
+
description = crash.description || 'Application crash';
|
|
95
|
+
}
|
|
96
|
+
return `${reason}: ${description}, status: ${status}`;
|
|
97
|
+
}
|
|
98
|
+
//# sourceMappingURL=crashErrorMessage.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"crashErrorMessage.js","sourceRoot":"","sources":["../../../../../src/instrumentations/crashReporting/ios/crashErrorMessage.ts"],"names":[],"mappings":"AAIA;;;GAGG;AACH,MAAM,UAAU,wBAAwB,CAAC,KAAkB,EAAE,WAAuC;;IAClG,6DAA6D;IAC7D,IAAI,CAAA,WAAW,aAAX,WAAW,uBAAX,WAAW,CAAE,aAAa,MAAI,WAAW,aAAX,WAAW,uBAAX,WAAW,CAAE,eAAe,CAAA,EAAE,CAAC;QAC/D,MAAM,IAAI,GAAG,WAAW,CAAC,aAAa,IAAI,WAAW,CAAC;QACtD,MAAM,MAAM,GAAG,MAAA,WAAW,CAAC,eAAe,0CAAE,IAAI,EAAE,CAAC;QACnD,IAAI,MAAM,EAAE,CAAC;YACX,OAAO,GAAG,IAAI,KAAK,MAAM,EAAE,CAAC;QAC9B,CAAC;QACD,OAAO,IAAI,CAAC;IACd,CAAC;IAED,wDAAwD;IACxD,IAAI,MAAA,WAAW,aAAX,WAAW,uBAAX,WAAW,CAAE,iBAAiB,0CAAE,IAAI,EAAE,EAAE,CAAC;QAC3C,OAAO,WAAW,CAAC,iBAAiB,CAAC,IAAI,EAAE,CAAC;IAC9C,CAAC;IAED,oCAAoC;IACpC,IAAI,MAAA,WAAW,aAAX,WAAW,uBAAX,WAAW,CAAE,UAAU,0CAAE,IAAI,EAAE,EAAE,CAAC;QACpC,MAAM,UAAU,GAAG,WAAW,CAAC,UAAU,CAAC,IAAI,EAAE,CAAC;QACjD,MAAM,WAAW,GAAG,mBAAmB,CAAC,UAAU,CAAC,CAAC;QACpD,OAAO,WAAW,IAAI,UAAU,CAAC;IACnC,CAAC;IAED,8CAA8C;IAC9C,MAAM,WAAW,GAAG,MAAA,KAAK,CAAC,WAAW,0CAAE,IAAI,EAAE,CAAC;IAC9C,IAAI,WAAW,IAAI,CAAC,yBAAyB,CAAC,WAAW,CAAC,EAAE,CAAC;QAC3D,OAAO,WAAW,CAAC;IACrB,CAAC;IAED,yCAAyC;IACzC,MAAM,MAAM,GAAG,MAAA,KAAK,CAAC,MAAM,0CAAE,IAAI,EAAE,CAAC;IACpC,IAAI,MAAM,IAAI,mBAAmB,CAAC,MAAM,CAAC,EAAE,CAAC;QAC1C,wDAAwD;QACxD,IAAI,WAAW,IAAI,yBAAyB,CAAC,WAAW,CAAC,EAAE,CAAC;YAC1D,OAAO,yBAAyB,CAAC,KAAK,CAAC,CAAC;QAC1C,CAAC;QACD,iDAAiD;QACjD,OAAO,mBAAmB,CAAC,MAAM,CAAC,CAAC;IACrC,CAAC;IAED,mEAAmE;IACnE,OAAO,yBAAyB,CAAC,KAAK,CAAC,CAAC;AAC1C,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,qBAAqB,CAAC,KAAkB;;IACtD,IAAI,MAAA,KAAK,CAAC,KAAK,0CAAE,IAAI,EAAE,EAAE,CAAC;QACxB,OAAO,KAAK,CAAC;IACf,CAAC;IAED,IAAI,MAAA,KAAK,CAAC,MAAM,0CAAE,IAAI,EAAE,EAAE,CAAC;QACzB,OAAO,KAAK,CAAC;IACf,CAAC;IAED,MAAM,WAAW,GAAG,MAAA,KAAK,CAAC,WAAW,0CAAE,IAAI,EAAE,CAAC;IAC9C,IAAI,WAAW,IAAI,CAAC,yBAAyB,CAAC,WAAW,CAAC,EAAE,CAAC;QAC3D,OAAO,KAAK,CAAC;IACf,CAAC;IAED,OAAO,IAAI,CAAC;AACd,CAAC;AAED,sDAAsD;AACtD,MAAM,mBAAmB,GAA2B;IAClD,MAAM,EAAE,QAAQ;IAChB,MAAM,EAAE,WAAW;IACnB,OAAO,EAAE,MAAM;IACf,MAAM,EAAE,qBAAqB;IAC7B,OAAO,EAAE,gBAAgB;IACzB,OAAO,EAAE,YAAY;IACrB,MAAM,EAAE,UAAU;IAClB,MAAM,EAAE,0BAA0B;IAClC,OAAO,EAAE,QAAQ;IACjB,MAAM,EAAE,WAAW;IACnB,OAAO,EAAE,oBAAoB;IAC7B,MAAM,EAAE,iBAAiB;IACzB,OAAO,EAAE,aAAa;IACtB,OAAO,EAAE,aAAa;IACtB,OAAO,EAAE,YAAY;CACtB,CAAC;AAEF,SAAS,yBAAyB,CAAC,WAAmB;IACpD,MAAM,UAAU,GAAG,WAAW,CAAC,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC;IACpD,OAAO,UAAU,KAAK,OAAO,IAAI,UAAU,KAAK,mBAAmB,IAAI,UAAU,KAAK,cAAc,CAAC;AACvG,CAAC;AAED,kFAAkF;AAClF,MAAM,UAAU,yBAAyB,CAAC,KAAkB;;IAC1D,MAAM,MAAM,GAAG,KAAK,CAAC,MAAM,IAAI,SAAS,CAAC;IACzC,MAAM,MAAM,GAAG,MAAA,KAAK,CAAC,MAAM,mCAAI,CAAC,CAAC;IAEjC,IAAI,WAAmB,CAAC;IACxB,IAAI,mBAAmB,CAAC,MAAM,CAAC,EAAE,CAAC;QAChC,WAAW,GAAG,mBAAmB,CAAC,MAAM,CAAC,CAAC;IAC5C,CAAC;SAAM,CAAC;QACN,WAAW,GAAG,KAAK,CAAC,WAAW,IAAI,mBAAmB,CAAC;IACzD,CAAC;IAED,OAAO,GAAG,MAAM,KAAK,WAAW,aAAa,MAAM,EAAE,CAAC;AACxD,CAAC","sourcesContent":["import type { CrashReport } from '../types';\n\nimport type { ParsedIosCrashTrace } from './parseIosCrashTrace';\n\n/**\n * Human-readable exception value for iOS PLCrashReporter crash reports.\n * Priority: exception reason → signal description → signal name → fallback.\n */\nexport function resolveCrashErrorMessage(crash: CrashReport, parsedTrace: ParsedIosCrashTrace | null): string {\n // iOS uncaught exceptions (NSException) have name and reason\n if (parsedTrace?.exceptionName || parsedTrace?.exceptionReason) {\n const name = parsedTrace.exceptionName || 'Exception';\n const reason = parsedTrace.exceptionReason?.trim();\n if (reason) {\n return `${name}: ${reason}`;\n }\n return name;\n }\n\n // Use signal description if available from parsed trace\n if (parsedTrace?.signalDescription?.trim()) {\n return parsedTrace.signalDescription.trim();\n }\n\n // Use signal name from parsed trace\n if (parsedTrace?.signalName?.trim()) {\n const signalName = parsedTrace.signalName.trim();\n const description = SIGNAL_DESCRIPTIONS[signalName];\n return description || signalName;\n }\n\n // Check crash description - use if meaningful\n const description = crash.description?.trim();\n if (description && !isGenericCrashDescription(description)) {\n return description;\n }\n\n // Check if we have a known signal reason\n const reason = crash.reason?.trim();\n if (reason && SIGNAL_DESCRIPTIONS[reason]) {\n // If description is explicitly generic, use full format\n if (description && isGenericCrashDescription(description)) {\n return buildFallbackCrashMessage(crash);\n }\n // No description - use simple signal description\n return SIGNAL_DESCRIPTIONS[reason];\n }\n\n // Final fallback: format with signal name, description, and status\n return buildFallbackCrashMessage(crash);\n}\n\n/**\n * Skip crash reports that have no useful information.\n * iOS crashes should always have either a trace or a signal.\n */\nexport function shouldSkipCrashReport(crash: CrashReport): boolean {\n if (crash.trace?.trim()) {\n return false;\n }\n\n if (crash.signal?.trim()) {\n return false;\n }\n\n const description = crash.description?.trim();\n if (description && !isGenericCrashDescription(description)) {\n return false;\n }\n\n return true;\n}\n\n/** BSD signal names to human-readable descriptions */\nconst SIGNAL_DESCRIPTIONS: Record<string, string> = {\n SIGHUP: 'Hangup',\n SIGINT: 'Interrupt',\n SIGQUIT: 'Quit',\n SIGILL: 'Illegal instruction',\n SIGTRAP: 'Trace/BPT trap',\n SIGABRT: 'Abort trap',\n SIGEMT: 'EMT trap',\n SIGFPE: 'Floating point exception',\n SIGKILL: 'Killed',\n SIGBUS: 'Bus error',\n SIGSEGV: 'Segmentation fault',\n SIGSYS: 'Bad system call',\n SIGPIPE: 'Broken pipe',\n SIGALRM: 'Alarm clock',\n SIGTERM: 'Terminated',\n};\n\nfunction isGenericCrashDescription(description: string): boolean {\n const normalized = description.trim().toLowerCase();\n return normalized === 'crash' || normalized === 'application crash' || normalized === 'signal crash';\n}\n\n/** Generic fallback when the trace carries no parseable signal or description. */\nexport function buildFallbackCrashMessage(crash: CrashReport): string {\n const reason = crash.reason || 'UNKNOWN';\n const status = crash.status ?? 0;\n\n let description: string;\n if (SIGNAL_DESCRIPTIONS[reason]) {\n description = SIGNAL_DESCRIPTIONS[reason];\n } else {\n description = crash.description || 'Application crash';\n }\n\n return `${reason}: ${description}, status: ${status}`;\n}\n"]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../../src/instrumentations/crashReporting/ios/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gCAAgC,EAAE,MAAM,oCAAoC,CAAC;AACtF,OAAO,EAAE,kBAAkB,EAAE,MAAM,sBAAsB,CAAC;AAE1D,OAAO,EAAE,wBAAwB,EAAE,qBAAqB,EAAE,MAAM,qBAAqB,CAAC","sourcesContent":["export { IosCrashReportingInstrumentation } from './IosCrashReportingInstrumentation';\nexport { parseIosCrashTrace } from './parseIosCrashTrace';\nexport type { ParsedIosCrashTrace, IosCrashStackFrame } from './parseIosCrashTrace';\nexport { resolveCrashErrorMessage, shouldSkipCrashReport } from './crashErrorMessage';\n"]}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* iOS PLCrashReporter stack trace line format:
|
|
3
|
+
* "0 libsystem_kernel.dylib 0x00000001a2b3c000"
|
|
4
|
+
* frame_number library_name instruction_pointer
|
|
5
|
+
*/
|
|
6
|
+
const IOS_STACK_FRAME_LINE = /^\s*(\d+)\s+(.+?)\s+(0x[0-9a-fA-F]+)\s*$/;
|
|
7
|
+
/**
|
|
8
|
+
* Parse a raw iOS crash trace from PLCrashReporter into structured stack frames.
|
|
9
|
+
*
|
|
10
|
+
* iOS crashes from PLCrashReporter come with formatted stack traces where each line
|
|
11
|
+
* contains: frame number, library name, and instruction pointer hex address.
|
|
12
|
+
*
|
|
13
|
+
* The collector will use the library name and instruction pointer for symbolication
|
|
14
|
+
* with dSYM files or Breakpad symbols.
|
|
15
|
+
*/
|
|
16
|
+
export function parseIosCrashTrace(trace, _options) {
|
|
17
|
+
const trimmed = trace.trim();
|
|
18
|
+
if (!trimmed) {
|
|
19
|
+
return null;
|
|
20
|
+
}
|
|
21
|
+
const lines = trimmed.split('\n');
|
|
22
|
+
const frames = [];
|
|
23
|
+
for (const line of lines) {
|
|
24
|
+
const frameMatch = line.match(IOS_STACK_FRAME_LINE);
|
|
25
|
+
if (frameMatch) {
|
|
26
|
+
const [, frameNumber, libraryName, instructionPointer] = frameMatch;
|
|
27
|
+
if (!libraryName || !instructionPointer || !frameNumber) {
|
|
28
|
+
continue;
|
|
29
|
+
}
|
|
30
|
+
frames.push({
|
|
31
|
+
module: libraryName.trim(),
|
|
32
|
+
function: instructionPointer,
|
|
33
|
+
filename: libraryName.trim(),
|
|
34
|
+
lineno: parseInt(frameNumber, 10),
|
|
35
|
+
instructionPointer: instructionPointer,
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
if (frames.length === 0) {
|
|
40
|
+
return null;
|
|
41
|
+
}
|
|
42
|
+
return { frames };
|
|
43
|
+
}
|
|
44
|
+
//# sourceMappingURL=parseIosCrashTrace.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"parseIosCrashTrace.js","sourceRoot":"","sources":["../../../../../src/instrumentations/crashReporting/ios/parseIosCrashTrace.ts"],"names":[],"mappings":"AAgCA;;;;GAIG;AACH,MAAM,oBAAoB,GAAG,0CAA0C,CAAC;AAExE;;;;;;;;GAQG;AACH,MAAM,UAAU,kBAAkB,CAAC,KAAa,EAAE,QAAoC;IACpF,MAAM,OAAO,GAAG,KAAK,CAAC,IAAI,EAAE,CAAC;IAC7B,IAAI,CAAC,OAAO,EAAE,CAAC;QACb,OAAO,IAAI,CAAC;IACd,CAAC;IAED,MAAM,KAAK,GAAG,OAAO,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;IAClC,MAAM,MAAM,GAAyB,EAAE,CAAC;IAExC,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QACzB,MAAM,UAAU,GAAG,IAAI,CAAC,KAAK,CAAC,oBAAoB,CAAC,CAAC;QAEpD,IAAI,UAAU,EAAE,CAAC;YACf,MAAM,CAAC,EAAE,WAAW,EAAE,WAAW,EAAE,kBAAkB,CAAC,GAAG,UAAU,CAAC;YAEpE,IAAI,CAAC,WAAW,IAAI,CAAC,kBAAkB,IAAI,CAAC,WAAW,EAAE,CAAC;gBACxD,SAAS;YACX,CAAC;YAED,MAAM,CAAC,IAAI,CAAC;gBACV,MAAM,EAAE,WAAW,CAAC,IAAI,EAAE;gBAC1B,QAAQ,EAAE,kBAAkB;gBAC5B,QAAQ,EAAE,WAAW,CAAC,IAAI,EAAE;gBAC5B,MAAM,EAAE,QAAQ,CAAC,WAAW,EAAE,EAAE,CAAC;gBACjC,kBAAkB,EAAE,kBAAkB;aACvC,CAAC,CAAC;QACL,CAAC;IACH,CAAC;IAED,IAAI,MAAM,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACxB,OAAO,IAAI,CAAC;IACd,CAAC;IAED,OAAO,EAAE,MAAM,EAAE,CAAC;AACpB,CAAC","sourcesContent":["import type { ExceptionStackFrame } from '@grafana/faro-core';\n\n/**\n * iOS PLCrashReporter stack frames include library name and instruction pointers.\n * Format: \"0 libsystem_kernel.dylib 0x00000001a2b3c000\"\n */\nexport interface IosCrashStackFrame extends ExceptionStackFrame {\n /** Library/module name (e.g., \"MyApp\", \"UIKitCore\") */\n module: string;\n /** Instruction pointer address (hex string) */\n instructionPointer?: string;\n /** Library base address (hex string) */\n libraryBaseAddress?: string;\n}\n\nexport interface ParseIosCrashTraceOptions {\n releaseBundleFilename?: string;\n}\n\nexport interface ParsedIosCrashTrace {\n /** Signal name (e.g., \"SIGSEGV\", \"SIGABRT\") */\n signalName?: string;\n /** Human-readable signal description */\n signalDescription?: string;\n /** Exception name for uncaught exceptions (e.g., \"NSInternalInconsistencyException\") */\n exceptionName?: string;\n /** Exception reason/message for uncaught exceptions */\n exceptionReason?: string;\n /** iOS native frames (library + instruction pointer for symbolication) */\n frames: IosCrashStackFrame[];\n}\n\n/**\n * iOS PLCrashReporter stack trace line format:\n * \"0 libsystem_kernel.dylib 0x00000001a2b3c000\"\n * frame_number library_name instruction_pointer\n */\nconst IOS_STACK_FRAME_LINE = /^\\s*(\\d+)\\s+(.+?)\\s+(0x[0-9a-fA-F]+)\\s*$/;\n\n/**\n * Parse a raw iOS crash trace from PLCrashReporter into structured stack frames.\n *\n * iOS crashes from PLCrashReporter come with formatted stack traces where each line\n * contains: frame number, library name, and instruction pointer hex address.\n *\n * The collector will use the library name and instruction pointer for symbolication\n * with dSYM files or Breakpad symbols.\n */\nexport function parseIosCrashTrace(trace: string, _options?: ParseIosCrashTraceOptions): ParsedIosCrashTrace | null {\n const trimmed = trace.trim();\n if (!trimmed) {\n return null;\n }\n\n const lines = trimmed.split('\\n');\n const frames: IosCrashStackFrame[] = [];\n\n for (const line of lines) {\n const frameMatch = line.match(IOS_STACK_FRAME_LINE);\n\n if (frameMatch) {\n const [, frameNumber, libraryName, instructionPointer] = frameMatch;\n\n if (!libraryName || !instructionPointer || !frameNumber) {\n continue;\n }\n\n frames.push({\n module: libraryName.trim(),\n function: instructionPointer,\n filename: libraryName.trim(),\n lineno: parseInt(frameNumber, 10),\n instructionPointer: instructionPointer,\n });\n }\n }\n\n if (frames.length === 0) {\n return null;\n }\n\n return { frames };\n}\n"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../../../src/instrumentations/crashReporting/types.ts"],"names":[],"mappings":"","sourcesContent":["/**\n *
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../../../src/instrumentations/crashReporting/types.ts"],"names":[],"mappings":"","sourcesContent":["/**\n * Crash report data structure returned from native module.\n */\nexport interface CrashReport {\n trace?: string;\n signal?: string;\n timestamp?: number;\n description?: string;\n processName?: string;\n pid?: number;\n importance?: number;\n // iOS-specific fields\n reason?: string; // Signal name (e.g., 'SIGSEGV')\n status?: number; // Exit status code\n}\n\n/**\n * Configuration options for crash reporting instrumentation.\n */\nexport interface CrashReportingOptions {\n /**\n * Enable or disable crash reporting. Defaults to true.\n */\n enabled?: boolean;\n\n /**\n * Optional release bundle filename hint for R8 retracing.\n * e.g., \"index.android.bundle\"\n */\n releaseBundleFilename?: string;\n}\n"]}
|
|
@@ -66,12 +66,12 @@ export class ErrorsInstrumentation extends BaseInstrumentation {
|
|
|
66
66
|
// Enhance error with React Native context and stack frames
|
|
67
67
|
const { error: enhancedError, stackFrames, context, } = enhanceErrorWithContext(error, {
|
|
68
68
|
mechanism: ErrorMechanism.UNCAUGHT,
|
|
69
|
-
isFatal: String(isFatal !== null && isFatal !== void 0 ? isFatal : false),
|
|
70
69
|
}, { releaseBundleFilename: this.options.releaseBundleFilename });
|
|
71
70
|
// Push error to Faro with enhanced data (type from error.name, matches Web SDK)
|
|
72
71
|
this.api.pushError(enhancedError, {
|
|
73
72
|
type: enhancedError.name || 'Error',
|
|
74
73
|
context,
|
|
74
|
+
fatal: isFatal !== null && isFatal !== void 0 ? isFatal : false,
|
|
75
75
|
stackFrames,
|
|
76
76
|
});
|
|
77
77
|
// Track error fingerprint for deduplication
|
|
@@ -122,6 +122,7 @@ export class ErrorsInstrumentation extends BaseInstrumentation {
|
|
|
122
122
|
this.api.pushError(enhancedError, {
|
|
123
123
|
type: errorType,
|
|
124
124
|
context,
|
|
125
|
+
fatal: false,
|
|
125
126
|
stackFrames,
|
|
126
127
|
});
|
|
127
128
|
// Track error fingerprint for deduplication
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../src/instrumentations/errors/index.ts"],"names":[],"mappings":"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","sourcesContent":["import type { ErrorUtils } from 'react-native';\n\nimport { BaseInstrumentation } from '@grafana/faro-core';\n\nimport { ErrorMechanism, primitiveUnhandledType } from './const';\nexport { ErrorMechanism } from './const';\nexport type { ErrorMechanismType } from './const';\nimport { enhanceErrorWithContext } from './stackTraceParser';\n\n// Access the global ErrorUtils\ndeclare const global: {\n ErrorUtils: ErrorUtils;\n addEventListener?: (event: string, handler: (event: PromiseRejectionEvent) => void) => void;\n removeEventListener?: (event: string, handler: (event: PromiseRejectionEvent) => void) => void;\n};\n\ntype ErrorHandlerCallback = (error: Error | unknown, isFatal?: boolean) => void;\n\nexport interface ErrorsInstrumentationOptions {\n /**\n * Patterns to ignore errors by message\n * @example [/network timeout/i, /cancelled/i]\n */\n ignoreErrors?: RegExp[];\n\n /**\n * Enable error deduplication (default: true)\n * Prevents sending the same error multiple times within a time window\n */\n enableDeduplication?: boolean;\n\n /**\n * Deduplication time window in milliseconds (default: 5000)\n * Errors with same message/stack within this window are considered duplicates\n */\n deduplicationWindow?: number;\n\n /**\n * Maximum number of errors to track for deduplication (default: 50)\n * Older errors are removed when this limit is reached\n */\n maxDeduplicationEntries?: number;\n\n /**\n * Bundle filename for Hermes/minified release lines (`func@line:col`).\n * Must match the Metro plugin source map `file` field (default `bundle.js`).\n */\n releaseBundleFilename?: string;\n}\n\ninterface ErrorFingerprint {\n message: string;\n stack: string;\n timestamp: number;\n}\n\n/**\n * Errors instrumentation for React Native\n *\n * Features:\n * - Captures unhandled errors and promise rejections using ErrorUtils\n * - Parses React Native stack traces (dev, release, Metro bundler formats)\n * - Adds platform context (OS, version, Hermes engine)\n * - Error deduplication to prevent duplicate reports\n * - Configurable error filtering\n *\n * @example\n * ```tsx\n * import { ErrorsInstrumentation, initializeFaro } from '@grafana/faro-react-native';\n *\n * await initializeFaro({\n * // ...config\n * instrumentations: [\n * new ErrorsInstrumentation({\n * ignoreErrors: [/network timeout/i, /cancelled/i],\n * enableDeduplication: true,\n * deduplicationWindow: 5000,\n * }),\n * ],\n * });\n * ```\n */\nexport class ErrorsInstrumentation extends BaseInstrumentation {\n readonly name = '@grafana/faro-react-native-errors';\n readonly version = '1.0.0';\n\n private originalErrorHandler?: ErrorHandlerCallback;\n private unhandledRejectionListener?: (event: PromiseRejectionEvent) => void;\n private options: Required<Omit<ErrorsInstrumentationOptions, 'releaseBundleFilename'>> & {\n releaseBundleFilename?: string;\n };\n private errorFingerprints: ErrorFingerprint[] = [];\n\n constructor(options: ErrorsInstrumentationOptions = {}) {\n super();\n this.options = {\n ignoreErrors: options.ignoreErrors ?? [],\n enableDeduplication: options.enableDeduplication ?? true,\n deduplicationWindow: options.deduplicationWindow ?? 5000,\n maxDeduplicationEntries: options.maxDeduplicationEntries ?? 50,\n releaseBundleFilename: options.releaseBundleFilename,\n };\n }\n\n initialize(): void {\n // Capture unhandled JavaScript errors\n this.setupGlobalErrorHandler();\n\n // Capture unhandled promise rejections\n this.setupUnhandledRejectionHandler();\n }\n\n private setupGlobalErrorHandler(): void {\n // Store the original error handler\n this.originalErrorHandler = global.ErrorUtils.getGlobalHandler();\n\n // Set our custom handler\n global.ErrorUtils.setGlobalHandler((error: Error, isFatal?: boolean) => {\n try {\n // Check if error should be ignored\n if (this.shouldIgnoreError(error)) {\n return;\n }\n\n // Check for duplicate errors\n if (this.options.enableDeduplication && this.isDuplicateError(error)) {\n return;\n }\n\n // Enhance error with React Native context and stack frames\n const {\n error: enhancedError,\n stackFrames,\n context,\n } = enhanceErrorWithContext(\n error,\n {\n mechanism: ErrorMechanism.UNCAUGHT,\n isFatal: String(isFatal ?? false),\n },\n { releaseBundleFilename: this.options.releaseBundleFilename }\n );\n\n // Push error to Faro with enhanced data (type from error.name, matches Web SDK)\n this.api.pushError(enhancedError, {\n type: enhancedError.name || 'Error',\n context,\n stackFrames,\n });\n\n // Track error fingerprint for deduplication\n if (this.options.enableDeduplication) {\n this.addErrorFingerprint(error);\n }\n } catch (_e) {\n // Don't let error reporting cause more errors\n } finally {\n // Always call the original handler to maintain normal error behavior\n if (this.originalErrorHandler) {\n this.originalErrorHandler(error, isFatal);\n }\n }\n });\n }\n\n private setupUnhandledRejectionHandler(): void {\n // React Native supports the standard unhandledrejection event\n this.unhandledRejectionListener = (event: PromiseRejectionEvent) => {\n try {\n const reason = event.reason;\n\n // Convert reason to an Error if it isn't one (matches Web SDK approach)\n const isPrimitiveRejection = !(reason instanceof Error);\n let error: Error;\n if (reason instanceof Error) {\n error = reason;\n } else {\n error = new Error(\n `Unhandled Promise Rejection: ${typeof reason === 'object' ? JSON.stringify(reason) : String(reason)}`\n );\n }\n\n // Check if error should be ignored\n if (this.shouldIgnoreError(error)) {\n return;\n }\n\n // Check for duplicate errors\n if (this.options.enableDeduplication && this.isDuplicateError(error)) {\n return;\n }\n\n // Enhance error with React Native context and stack frames\n const {\n error: enhancedError,\n stackFrames,\n context,\n } = enhanceErrorWithContext(\n error,\n {\n mechanism: ErrorMechanism.UNHANDLED_REJECTION,\n },\n { releaseBundleFilename: this.options.releaseBundleFilename }\n );\n\n // Type: use actual error type for Error objects; 'UnhandledRejection' for primitives (Web SDK)\n const errorType = isPrimitiveRejection ? primitiveUnhandledType : enhancedError.name || 'Error';\n\n this.api.pushError(enhancedError, {\n type: errorType,\n context,\n stackFrames,\n });\n\n // Track error fingerprint for deduplication\n if (this.options.enableDeduplication) {\n this.addErrorFingerprint(error);\n }\n } catch (_e) {\n // Don't let error reporting cause more errors\n }\n };\n\n // Add the listener\n global.addEventListener?.('unhandledrejection', this.unhandledRejectionListener);\n }\n\n /**\n * Check if error should be ignored based on ignoreErrors patterns\n */\n private shouldIgnoreError(error: Error): boolean {\n if (!error || !error.message) {\n return false;\n }\n\n return this.options.ignoreErrors.some((pattern) => pattern.test(error.message));\n }\n\n /**\n * Check if error is a duplicate based on message and stack\n */\n private isDuplicateError(error: Error): boolean {\n const message = error.message || '';\n const stack = error.stack || '';\n const now = Date.now();\n\n // Clean up old entries first\n this.cleanupOldFingerprints(now);\n\n // Check if we've seen this error recently\n return this.errorFingerprints.some((fingerprint) => {\n return (\n fingerprint.message === message &&\n fingerprint.stack === stack &&\n now - fingerprint.timestamp < this.options.deduplicationWindow\n );\n });\n }\n\n /**\n * Add error fingerprint for deduplication tracking\n */\n private addErrorFingerprint(error: Error): void {\n const message = error.message || '';\n const stack = error.stack || '';\n const timestamp = Date.now();\n\n this.errorFingerprints.push({ message, stack, timestamp });\n\n // Limit the number of tracked fingerprints\n if (this.errorFingerprints.length > this.options.maxDeduplicationEntries) {\n this.errorFingerprints.shift();\n }\n }\n\n /**\n * Remove error fingerprints older than the deduplication window\n */\n private cleanupOldFingerprints(now: number): void {\n this.errorFingerprints = this.errorFingerprints.filter(\n (fingerprint) => now - fingerprint.timestamp < this.options.deduplicationWindow\n );\n }\n\n unpatch(): void {\n // Restore original error handler\n if (this.originalErrorHandler) {\n global.ErrorUtils.setGlobalHandler(this.originalErrorHandler);\n }\n\n // Remove unhandled rejection listener\n if (this.unhandledRejectionListener) {\n global.removeEventListener?.('unhandledrejection', this.unhandledRejectionListener);\n }\n\n // Clear deduplication tracking\n this.errorFingerprints = [];\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../src/instrumentations/errors/index.ts"],"names":[],"mappings":"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","sourcesContent":["import type { ErrorUtils } from 'react-native';\n\nimport { BaseInstrumentation } from '@grafana/faro-core';\n\nimport { ErrorMechanism, primitiveUnhandledType } from './const';\nexport { ErrorMechanism } from './const';\nexport type { ErrorMechanismType } from './const';\nimport { enhanceErrorWithContext } from './stackTraceParser';\n\n// Access the global ErrorUtils\ndeclare const global: {\n ErrorUtils: ErrorUtils;\n addEventListener?: (event: string, handler: (event: PromiseRejectionEvent) => void) => void;\n removeEventListener?: (event: string, handler: (event: PromiseRejectionEvent) => void) => void;\n};\n\ntype ErrorHandlerCallback = (error: Error | unknown, isFatal?: boolean) => void;\n\nexport interface ErrorsInstrumentationOptions {\n /**\n * Patterns to ignore errors by message\n * @example [/network timeout/i, /cancelled/i]\n */\n ignoreErrors?: RegExp[];\n\n /**\n * Enable error deduplication (default: true)\n * Prevents sending the same error multiple times within a time window\n */\n enableDeduplication?: boolean;\n\n /**\n * Deduplication time window in milliseconds (default: 5000)\n * Errors with same message/stack within this window are considered duplicates\n */\n deduplicationWindow?: number;\n\n /**\n * Maximum number of errors to track for deduplication (default: 50)\n * Older errors are removed when this limit is reached\n */\n maxDeduplicationEntries?: number;\n\n /**\n * Bundle filename for Hermes/minified release lines (`func@line:col`).\n * Must match the Metro plugin source map `file` field (default `bundle.js`).\n */\n releaseBundleFilename?: string;\n}\n\ninterface ErrorFingerprint {\n message: string;\n stack: string;\n timestamp: number;\n}\n\n/**\n * Errors instrumentation for React Native\n *\n * Features:\n * - Captures unhandled errors and promise rejections using ErrorUtils\n * - Parses React Native stack traces (dev, release, Metro bundler formats)\n * - Adds platform context (OS, version, Hermes engine)\n * - Error deduplication to prevent duplicate reports\n * - Configurable error filtering\n *\n * @example\n * ```tsx\n * import { ErrorsInstrumentation, initializeFaro } from '@grafana/faro-react-native';\n *\n * await initializeFaro({\n * // ...config\n * instrumentations: [\n * new ErrorsInstrumentation({\n * ignoreErrors: [/network timeout/i, /cancelled/i],\n * enableDeduplication: true,\n * deduplicationWindow: 5000,\n * }),\n * ],\n * });\n * ```\n */\nexport class ErrorsInstrumentation extends BaseInstrumentation {\n readonly name = '@grafana/faro-react-native-errors';\n readonly version = '1.0.0';\n\n private originalErrorHandler?: ErrorHandlerCallback;\n private unhandledRejectionListener?: (event: PromiseRejectionEvent) => void;\n private options: Required<Omit<ErrorsInstrumentationOptions, 'releaseBundleFilename'>> & {\n releaseBundleFilename?: string;\n };\n private errorFingerprints: ErrorFingerprint[] = [];\n\n constructor(options: ErrorsInstrumentationOptions = {}) {\n super();\n this.options = {\n ignoreErrors: options.ignoreErrors ?? [],\n enableDeduplication: options.enableDeduplication ?? true,\n deduplicationWindow: options.deduplicationWindow ?? 5000,\n maxDeduplicationEntries: options.maxDeduplicationEntries ?? 50,\n releaseBundleFilename: options.releaseBundleFilename,\n };\n }\n\n initialize(): void {\n // Capture unhandled JavaScript errors\n this.setupGlobalErrorHandler();\n\n // Capture unhandled promise rejections\n this.setupUnhandledRejectionHandler();\n }\n\n private setupGlobalErrorHandler(): void {\n // Store the original error handler\n this.originalErrorHandler = global.ErrorUtils.getGlobalHandler();\n\n // Set our custom handler\n global.ErrorUtils.setGlobalHandler((error: Error, isFatal?: boolean) => {\n try {\n // Check if error should be ignored\n if (this.shouldIgnoreError(error)) {\n return;\n }\n\n // Check for duplicate errors\n if (this.options.enableDeduplication && this.isDuplicateError(error)) {\n return;\n }\n\n // Enhance error with React Native context and stack frames\n const {\n error: enhancedError,\n stackFrames,\n context,\n } = enhanceErrorWithContext(\n error,\n {\n mechanism: ErrorMechanism.UNCAUGHT,\n },\n { releaseBundleFilename: this.options.releaseBundleFilename }\n );\n\n // Push error to Faro with enhanced data (type from error.name, matches Web SDK)\n this.api.pushError(enhancedError, {\n type: enhancedError.name || 'Error',\n context,\n fatal: isFatal ?? false,\n stackFrames,\n });\n\n // Track error fingerprint for deduplication\n if (this.options.enableDeduplication) {\n this.addErrorFingerprint(error);\n }\n } catch (_e) {\n // Don't let error reporting cause more errors\n } finally {\n // Always call the original handler to maintain normal error behavior\n if (this.originalErrorHandler) {\n this.originalErrorHandler(error, isFatal);\n }\n }\n });\n }\n\n private setupUnhandledRejectionHandler(): void {\n // React Native supports the standard unhandledrejection event\n this.unhandledRejectionListener = (event: PromiseRejectionEvent) => {\n try {\n const reason = event.reason;\n\n // Convert reason to an Error if it isn't one (matches Web SDK approach)\n const isPrimitiveRejection = !(reason instanceof Error);\n let error: Error;\n if (reason instanceof Error) {\n error = reason;\n } else {\n error = new Error(\n `Unhandled Promise Rejection: ${typeof reason === 'object' ? JSON.stringify(reason) : String(reason)}`\n );\n }\n\n // Check if error should be ignored\n if (this.shouldIgnoreError(error)) {\n return;\n }\n\n // Check for duplicate errors\n if (this.options.enableDeduplication && this.isDuplicateError(error)) {\n return;\n }\n\n // Enhance error with React Native context and stack frames\n const {\n error: enhancedError,\n stackFrames,\n context,\n } = enhanceErrorWithContext(\n error,\n {\n mechanism: ErrorMechanism.UNHANDLED_REJECTION,\n },\n { releaseBundleFilename: this.options.releaseBundleFilename }\n );\n\n // Type: use actual error type for Error objects; 'UnhandledRejection' for primitives (Web SDK)\n const errorType = isPrimitiveRejection ? primitiveUnhandledType : enhancedError.name || 'Error';\n\n this.api.pushError(enhancedError, {\n type: errorType,\n context,\n fatal: false,\n stackFrames,\n });\n\n // Track error fingerprint for deduplication\n if (this.options.enableDeduplication) {\n this.addErrorFingerprint(error);\n }\n } catch (_e) {\n // Don't let error reporting cause more errors\n }\n };\n\n // Add the listener\n global.addEventListener?.('unhandledrejection', this.unhandledRejectionListener);\n }\n\n /**\n * Check if error should be ignored based on ignoreErrors patterns\n */\n private shouldIgnoreError(error: Error): boolean {\n if (!error || !error.message) {\n return false;\n }\n\n return this.options.ignoreErrors.some((pattern) => pattern.test(error.message));\n }\n\n /**\n * Check if error is a duplicate based on message and stack\n */\n private isDuplicateError(error: Error): boolean {\n const message = error.message || '';\n const stack = error.stack || '';\n const now = Date.now();\n\n // Clean up old entries first\n this.cleanupOldFingerprints(now);\n\n // Check if we've seen this error recently\n return this.errorFingerprints.some((fingerprint) => {\n return (\n fingerprint.message === message &&\n fingerprint.stack === stack &&\n now - fingerprint.timestamp < this.options.deduplicationWindow\n );\n });\n }\n\n /**\n * Add error fingerprint for deduplication tracking\n */\n private addErrorFingerprint(error: Error): void {\n const message = error.message || '';\n const stack = error.stack || '';\n const timestamp = Date.now();\n\n this.errorFingerprints.push({ message, stack, timestamp });\n\n // Limit the number of tracked fingerprints\n if (this.errorFingerprints.length > this.options.maxDeduplicationEntries) {\n this.errorFingerprints.shift();\n }\n }\n\n /**\n * Remove error fingerprints older than the deduplication window\n */\n private cleanupOldFingerprints(now: number): void {\n this.errorFingerprints = this.errorFingerprints.filter(\n (fingerprint) => now - fingerprint.timestamp < this.options.deduplicationWindow\n );\n }\n\n unpatch(): void {\n // Restore original error handler\n if (this.originalErrorHandler) {\n global.ErrorUtils.setGlobalHandler(this.originalErrorHandler);\n }\n\n // Remove unhandled rejection listener\n if (this.unhandledRejectionListener) {\n global.removeEventListener?.('unhandledrejection', this.unhandledRejectionListener);\n }\n\n // Clear deduplication tracking\n this.errorFingerprints = [];\n }\n}\n"]}
|