@grafana/faro-react-native 1.2.1 → 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/package.json +3 -3
- 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
package/android/build.gradle
CHANGED
|
@@ -7,11 +7,13 @@ buildscript {
|
|
|
7
7
|
dependencies {
|
|
8
8
|
classpath 'com.android.tools.build:gradle:7.4.2'
|
|
9
9
|
classpath 'org.jetbrains.kotlin:kotlin-gradle-plugin:1.8.0'
|
|
10
|
+
classpath 'com.google.protobuf:protobuf-gradle-plugin:0.9.4'
|
|
10
11
|
}
|
|
11
12
|
}
|
|
12
13
|
|
|
13
14
|
apply plugin: 'com.android.library'
|
|
14
15
|
apply plugin: 'kotlin-android'
|
|
16
|
+
apply plugin: 'com.google.protobuf'
|
|
15
17
|
|
|
16
18
|
android {
|
|
17
19
|
namespace 'com.grafana.faro.reactnative'
|
|
@@ -20,17 +22,40 @@ android {
|
|
|
20
22
|
defaultConfig {
|
|
21
23
|
minSdkVersion 21
|
|
22
24
|
targetSdkVersion 33
|
|
25
|
+
consumerProguardFiles 'consumer-proguard-rules.pro'
|
|
23
26
|
}
|
|
24
27
|
|
|
25
28
|
sourceSets {
|
|
26
29
|
main {
|
|
27
30
|
java.srcDirs = ['src/main/java']
|
|
31
|
+
proto {
|
|
32
|
+
srcDir 'src/main/proto'
|
|
33
|
+
}
|
|
28
34
|
}
|
|
29
35
|
}
|
|
30
36
|
|
|
31
37
|
lintOptions {
|
|
32
38
|
abortOnError false
|
|
33
39
|
}
|
|
40
|
+
|
|
41
|
+
testOptions {
|
|
42
|
+
unitTests.returnDefaultValues = true
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
protobuf {
|
|
47
|
+
protoc {
|
|
48
|
+
artifact = 'com.google.protobuf:protoc:3.25.1'
|
|
49
|
+
}
|
|
50
|
+
generateProtoTasks {
|
|
51
|
+
all().each { task ->
|
|
52
|
+
task.builtins {
|
|
53
|
+
java {
|
|
54
|
+
option 'lite'
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
}
|
|
34
59
|
}
|
|
35
60
|
|
|
36
61
|
repositories {
|
|
@@ -42,6 +67,11 @@ dependencies {
|
|
|
42
67
|
//noinspection GradleDynamicVersion
|
|
43
68
|
implementation 'com.facebook.react:react-native:+'
|
|
44
69
|
implementation 'org.jetbrains.kotlin:kotlin-stdlib:1.8.0'
|
|
70
|
+
implementation 'com.google.protobuf:protobuf-javalite:3.25.1'
|
|
71
|
+
|
|
72
|
+
testImplementation 'junit:junit:4.13.2'
|
|
73
|
+
testImplementation 'org.robolectric:robolectric:4.11.1'
|
|
74
|
+
testImplementation 'androidx.test:core:1.5.0'
|
|
45
75
|
}
|
|
46
76
|
|
|
47
77
|
/* ---------------------------------------------------------------------------
|
|
@@ -53,8 +83,9 @@ dependencies {
|
|
|
53
83
|
*
|
|
54
84
|
* 1. npm install @grafana/faro-react-native @grafana/faro-metro-plugin
|
|
55
85
|
* 2. Wrap metro.config.js with `withFaroConfig(...)`
|
|
56
|
-
* 3. Export
|
|
57
|
-
* `
|
|
86
|
+
* 3. Export FARO_SOURCEMAP_* before running `yarn android --mode=release`.
|
|
87
|
+
* Bundle id is read from `app/build/faro/bundle-id-release.txt` when the
|
|
88
|
+
* `com.grafana.faro` Gradle plugin is applied (same id Metro uses).
|
|
58
89
|
*
|
|
59
90
|
* On any release-style command (assembleRelease, bundleRelease, installRelease,
|
|
60
91
|
* `yarn android --mode=release`), release entry tasks depend on
|
|
@@ -62,21 +93,20 @@ dependencies {
|
|
|
62
93
|
* the JS bundle task (`bundleReleaseJsAndAssets` or `createBundleReleaseJsAndAssets`)
|
|
63
94
|
* so upload runs only after a successful bundle — never via a finalizer (which
|
|
64
95
|
* can run even when bundling fails and could upload a stale map). The upload
|
|
65
|
-
* task runs `faro-cli metro upload` via a small Node shim.
|
|
66
|
-
*
|
|
67
|
-
*
|
|
68
|
-
*
|
|
69
|
-
*
|
|
96
|
+
* task runs `faro-cli metro upload` via a small Node shim. Bundle id comes from
|
|
97
|
+
* the Gradle plugin file; upload credentials from env or sourcemaps.config.json.
|
|
98
|
+
* Credentials are forwarded as **process environment variables** (not CLI flags)
|
|
99
|
+
* so secrets do not appear in `ps` or Gradle `--info` command lines.
|
|
100
|
+
*
|
|
101
|
+
* Required at build time:
|
|
102
|
+
* Bundle id — `app/build/faro/bundle-id-release.txt` from `com.grafana.faro`
|
|
103
|
+
* (`faroWriteBundleIdRelease`). Same id Metro uses. Forwarded as --bundle-id.
|
|
70
104
|
*
|
|
71
|
-
* Required env
|
|
72
|
-
*
|
|
73
|
-
*
|
|
74
|
-
*
|
|
75
|
-
*
|
|
76
|
-
* FARO_SOURCEMAP_ENDPOINT — Forwarded as --endpoint.
|
|
77
|
-
* FARO_SOURCEMAP_APP_ID — Forwarded as --app-id.
|
|
78
|
-
* FARO_SOURCEMAP_STACK_ID — Forwarded as --stack-id.
|
|
79
|
-
* FARO_SOURCEMAP_API_KEY — Forwarded as --api-key.
|
|
105
|
+
* Required env (Gradle forwards to the Node child process):
|
|
106
|
+
* FARO_SOURCEMAP_ENDPOINT
|
|
107
|
+
* FARO_SOURCEMAP_APP_ID
|
|
108
|
+
* FARO_SOURCEMAP_STACK_ID
|
|
109
|
+
* FARO_SOURCEMAP_API_KEY
|
|
80
110
|
*
|
|
81
111
|
* Optional env at build time:
|
|
82
112
|
* FARO_SKIP_SOURCEMAP_UPLOAD=1 — skip the upload (e.g. offline iteration).
|
|
@@ -109,6 +139,45 @@ gradle.projectsEvaluated {
|
|
|
109
139
|
def jsRoot = rootProject.projectDir.parentFile
|
|
110
140
|
def cliPath = new File(jsRoot, "node_modules/@grafana/faro-metro-plugin/bin/faro-upload-source-map.js")
|
|
111
141
|
def composedMap = new File(appProject.buildDir, "generated/sourcemaps/react/release/index.android.bundle.map")
|
|
142
|
+
def bundleIdFile = new File(appProject.buildDir, "faro/bundle-id-release.txt")
|
|
143
|
+
|
|
144
|
+
// Security: sourcemaps.config.json is a fallback for local dev only.
|
|
145
|
+
// In CI/production, prefer FARO_SOURCEMAP_* env vars to avoid committing credentials.
|
|
146
|
+
// Add sourcemaps.config.json to .gitignore if it contains apiKey.
|
|
147
|
+
def readSourcemapsConfigValue = { String key ->
|
|
148
|
+
def configFile = new File(jsRoot, "sourcemaps.config.json")
|
|
149
|
+
if (!configFile.exists()) {
|
|
150
|
+
return ""
|
|
151
|
+
}
|
|
152
|
+
try {
|
|
153
|
+
def parsed = new groovy.json.JsonSlurper().parse(configFile)
|
|
154
|
+
def val = parsed[key]
|
|
155
|
+
return val == null ? "" : val.toString().trim()
|
|
156
|
+
} catch (Exception e) {
|
|
157
|
+
logger.error("[Faro] Failed to parse ${configFile}: ${e.message}")
|
|
158
|
+
return ""
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
def envOrSourcemapsConfig = { String envName, String configKey ->
|
|
163
|
+
def fromEnv = (System.getenv(envName) ?: "").trim()
|
|
164
|
+
if (!fromEnv.isEmpty()) {
|
|
165
|
+
return fromEnv
|
|
166
|
+
}
|
|
167
|
+
return readSourcemapsConfigValue(configKey)
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
def resolveFaroBundleId = {
|
|
171
|
+
if (!bundleIdFile.exists()) {
|
|
172
|
+
return ""
|
|
173
|
+
}
|
|
174
|
+
def fromFile = bundleIdFile.getText("UTF-8").trim()
|
|
175
|
+
return fromFile.isEmpty() ? "" : fromFile
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
def isValidAndroidBundleId = { String id ->
|
|
179
|
+
id ==~ /^[^@]+@\d+@[^@]+$/
|
|
180
|
+
}
|
|
112
181
|
|
|
113
182
|
def uploadTask = appProject.tasks.register("faroUploadComposedSourceMapAndroidRelease", Exec) {
|
|
114
183
|
group = "react"
|
|
@@ -117,17 +186,16 @@ gradle.projectsEvaluated {
|
|
|
117
186
|
workingDir = jsRoot
|
|
118
187
|
|
|
119
188
|
def faroSkip = (System.getenv("FARO_SKIP_SOURCEMAP_UPLOAD") ?: "").trim().toLowerCase()
|
|
120
|
-
def
|
|
121
|
-
def
|
|
122
|
-
def
|
|
123
|
-
def
|
|
124
|
-
def faroApiKey = (System.getenv("FARO_SOURCEMAP_API_KEY") ?: "").trim()
|
|
189
|
+
def faroEndpoint = envOrSourcemapsConfig("FARO_SOURCEMAP_ENDPOINT", "endpoint")
|
|
190
|
+
def faroAppId = envOrSourcemapsConfig("FARO_SOURCEMAP_APP_ID", "appId")
|
|
191
|
+
def faroStackId = envOrSourcemapsConfig("FARO_SOURCEMAP_STACK_ID", "stackId")
|
|
192
|
+
def faroApiKey = envOrSourcemapsConfig("FARO_SOURCEMAP_API_KEY", "apiKey")
|
|
125
193
|
|
|
126
194
|
// Capture the inputs we use for caching / up-to-date checks. We
|
|
127
195
|
// deliberately *don't* track FARO_SOURCEMAP_API_KEY in inputs to
|
|
128
196
|
// avoid Gradle leaking it into a cache-key string.
|
|
129
197
|
inputs.file(composedMap).withPropertyName("composedMap").optional()
|
|
130
|
-
inputs.
|
|
198
|
+
inputs.file(bundleIdFile).withPropertyName("bundleIdFile").optional()
|
|
131
199
|
inputs.property("faroEndpoint", faroEndpoint)
|
|
132
200
|
inputs.property("faroAppId", faroAppId)
|
|
133
201
|
inputs.property("faroStackId", faroStackId)
|
|
@@ -139,40 +207,54 @@ gradle.projectsEvaluated {
|
|
|
139
207
|
return false
|
|
140
208
|
}
|
|
141
209
|
if (!cliPath.exists()) {
|
|
142
|
-
logger.
|
|
210
|
+
logger.error("[Faro] Skipping composed source map upload — CLI shim not found at ${cliPath}. Install @grafana/faro-metro-plugin alongside @grafana/faro-react-native.")
|
|
143
211
|
return false
|
|
144
212
|
}
|
|
145
213
|
if (!composedMap.exists()) {
|
|
146
|
-
logger.
|
|
214
|
+
logger.error("[Faro] Skipping composed source map upload — ${composedMap} does not exist. Did Hermes precompile run?")
|
|
147
215
|
return false
|
|
148
216
|
}
|
|
217
|
+
def resolvedBundleId = resolveFaroBundleId()
|
|
149
218
|
def missing = []
|
|
150
|
-
if (
|
|
219
|
+
if (resolvedBundleId.isEmpty()) {
|
|
220
|
+
missing << "bundle id file ${bundleIdFile} (apply com.grafana.faro and run faroWriteBundleIdRelease)"
|
|
221
|
+
} else if (!isValidAndroidBundleId(resolvedBundleId)) {
|
|
222
|
+
logger.error("[Faro] Skipping composed source map upload — invalid bundle id \"${resolvedBundleId}\" in ${bundleIdFile} (expected applicationId@versionCode@versionName).")
|
|
223
|
+
return false
|
|
224
|
+
}
|
|
151
225
|
if (faroEndpoint.isEmpty()) missing << "FARO_SOURCEMAP_ENDPOINT"
|
|
152
226
|
if (faroAppId.isEmpty()) missing << "FARO_SOURCEMAP_APP_ID"
|
|
153
227
|
if (faroStackId.isEmpty()) missing << "FARO_SOURCEMAP_STACK_ID"
|
|
154
228
|
if (faroApiKey.isEmpty()) missing << "FARO_SOURCEMAP_API_KEY"
|
|
155
229
|
if (!missing.isEmpty()) {
|
|
156
|
-
logger.
|
|
230
|
+
logger.error("[Faro] Skipping composed source map upload — missing: ${missing.join(", ")}. Set FARO_SOURCEMAP_* or fill sourcemaps.config.json (endpoint, appId, stackId, apiKey). Bundle id is read from ${bundleIdFile}.")
|
|
157
231
|
return false
|
|
158
232
|
}
|
|
233
|
+
ext.resolvedBundleId = resolvedBundleId
|
|
159
234
|
return true
|
|
160
235
|
}
|
|
161
236
|
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
237
|
+
doFirst {
|
|
238
|
+
def bundleId = ext.resolvedBundleId ?: resolveFaroBundleId()
|
|
239
|
+
environment 'FARO_SOURCEMAP_ENDPOINT', faroEndpoint
|
|
240
|
+
environment 'FARO_SOURCEMAP_APP_ID', faroAppId
|
|
241
|
+
environment 'FARO_SOURCEMAP_STACK_ID', faroStackId
|
|
242
|
+
environment 'FARO_SOURCEMAP_API_KEY', faroApiKey
|
|
243
|
+
commandLine(
|
|
244
|
+
"node", cliPath.absolutePath,
|
|
245
|
+
"--verbose",
|
|
246
|
+
"--map", composedMap.absolutePath,
|
|
247
|
+
"--bundle-id", bundleId,
|
|
248
|
+
)
|
|
249
|
+
}
|
|
172
250
|
}
|
|
173
251
|
|
|
174
252
|
def bundleTaskNames = ["bundleReleaseJsAndAssets", "createBundleReleaseJsAndAssets"]
|
|
175
253
|
uploadTask.configure { task ->
|
|
254
|
+
def writeBundleIdTask = appProject.tasks.findByName("faroWriteBundleIdRelease")
|
|
255
|
+
if (writeBundleIdTask != null) {
|
|
256
|
+
task.dependsOn(writeBundleIdTask)
|
|
257
|
+
}
|
|
176
258
|
bundleTaskNames.each { name ->
|
|
177
259
|
def bundleTask = appProject.tasks.findByName(name)
|
|
178
260
|
if (bundleTask != null) {
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
package com.grafana.faro.reactnative
|
|
2
2
|
|
|
3
|
+
import android.content.Context
|
|
3
4
|
import android.os.Handler
|
|
4
5
|
import android.os.Looper
|
|
5
6
|
import android.util.Log
|
|
@@ -20,7 +21,9 @@ class ANRTracker : Thread("ANRTracker") {
|
|
|
20
21
|
private const val TAG = "ANRTracker"
|
|
21
22
|
|
|
22
23
|
/**
|
|
23
|
-
*
|
|
24
|
+
* Watchdog check interval in milliseconds (default 5 seconds).
|
|
25
|
+
* Updated from JS via [FaroReactNativeModule.startANRTracking].
|
|
26
|
+
* Exported in ANR payloads as `duration` — the detection threshold, not measured block time.
|
|
24
27
|
*/
|
|
25
28
|
var timeout: Long = 5000L
|
|
26
29
|
|
|
@@ -49,13 +52,36 @@ class ANRTracker : Thread("ANRTracker") {
|
|
|
49
52
|
}
|
|
50
53
|
|
|
51
54
|
/**
|
|
52
|
-
*
|
|
55
|
+
* Application context used to persist ANRs before JS delivery.
|
|
53
56
|
*/
|
|
54
|
-
|
|
57
|
+
@Volatile
|
|
58
|
+
var applicationContext: Context? = null
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Remove in-memory ANRs that were acknowledged on disk.
|
|
62
|
+
*/
|
|
63
|
+
fun acknowledgeInMemory(timestamps: Collection<Long>) {
|
|
64
|
+
if (timestamps.isEmpty()) {
|
|
65
|
+
return
|
|
66
|
+
}
|
|
67
|
+
val timestampSet = timestamps.toSet()
|
|
55
68
|
synchronized(anrListLock) {
|
|
56
|
-
anrList.
|
|
69
|
+
anrList.removeAll { json ->
|
|
70
|
+
try {
|
|
71
|
+
JSONObject(json).optLong("timestamp", 0L) in timestampSet
|
|
72
|
+
} catch (_: Exception) {
|
|
73
|
+
false
|
|
74
|
+
}
|
|
75
|
+
}
|
|
57
76
|
}
|
|
58
77
|
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Optional callback invoked on the ANR tracker thread when an ANR is recorded.
|
|
81
|
+
* Used to push telemetry immediately instead of waiting for the JS poll interval.
|
|
82
|
+
*/
|
|
83
|
+
@Volatile
|
|
84
|
+
var onAnrDetected: ((String) -> Unit)? = null
|
|
59
85
|
}
|
|
60
86
|
|
|
61
87
|
private val mainHandler = Handler(Looper.getMainLooper())
|
|
@@ -63,6 +89,9 @@ class ANRTracker : Thread("ANRTracker") {
|
|
|
63
89
|
private val isRunning = AtomicBoolean(true)
|
|
64
90
|
private val taskExecuted = AtomicBoolean(false)
|
|
65
91
|
|
|
92
|
+
/** True while the main thread is blocked and we already recorded one ANR for this period. */
|
|
93
|
+
private val anrInProgress = AtomicBoolean(false)
|
|
94
|
+
|
|
66
95
|
private val checkTask = Runnable {
|
|
67
96
|
// This task runs on the main thread
|
|
68
97
|
taskExecuted.set(true)
|
|
@@ -91,6 +120,11 @@ class ANRTracker : Thread("ANRTracker") {
|
|
|
91
120
|
break
|
|
92
121
|
}
|
|
93
122
|
|
|
123
|
+
if (taskExecuted.get()) {
|
|
124
|
+
// Main thread recovered — allow a future blocked period to be reported again.
|
|
125
|
+
anrInProgress.set(false)
|
|
126
|
+
}
|
|
127
|
+
|
|
94
128
|
// If the task hasn't executed after the check interval, start monitoring for ANR
|
|
95
129
|
if (!taskExecuted.get()) {
|
|
96
130
|
Log.d(TAG, "Task not executed after initial check")
|
|
@@ -114,8 +148,14 @@ class ANRTracker : Thread("ANRTracker") {
|
|
|
114
148
|
// Check again if the task has executed
|
|
115
149
|
if (!taskExecuted.get()) {
|
|
116
150
|
Log.d(TAG, "Task is still not executed after ${timeout}ms")
|
|
117
|
-
// The main thread is blocked -
|
|
118
|
-
|
|
151
|
+
// The main thread is blocked - record once per blocked period.
|
|
152
|
+
if (!anrInProgress.get()) {
|
|
153
|
+
anrInProgress.set(true)
|
|
154
|
+
handleAnrDetected()
|
|
155
|
+
}
|
|
156
|
+
} else if (anrInProgress.get()) {
|
|
157
|
+
// Main thread recovered during the extended wait — same cycle as line 123.
|
|
158
|
+
anrInProgress.set(false)
|
|
119
159
|
}
|
|
120
160
|
}
|
|
121
161
|
|
|
@@ -175,9 +215,12 @@ class ANRTracker : Thread("ANRTracker") {
|
|
|
175
215
|
val stackTrace = mainThread.stackTrace
|
|
176
216
|
|
|
177
217
|
// Build a readable stack trace
|
|
218
|
+
// Match Android Log.getStackTraceString / ApplicationExitInfo format so
|
|
219
|
+
// ingest R8 retrace and parseAndroidCrashTrace can consume the text.
|
|
178
220
|
val stackTraceStr = StringBuilder()
|
|
179
221
|
for (element in stackTrace) {
|
|
180
|
-
stackTraceStr.append(
|
|
222
|
+
stackTraceStr.append(" at ")
|
|
223
|
+
.append(element.className)
|
|
181
224
|
.append(".")
|
|
182
225
|
.append(element.methodName)
|
|
183
226
|
.append("(")
|
|
@@ -193,12 +236,21 @@ class ANRTracker : Thread("ANRTracker") {
|
|
|
193
236
|
put("timestamp", System.currentTimeMillis())
|
|
194
237
|
put("stacktrace", stackTraceStr.toString())
|
|
195
238
|
put("duration", timeout)
|
|
239
|
+
put("source", "ANRTracker")
|
|
196
240
|
}
|
|
197
241
|
|
|
198
|
-
|
|
242
|
+
val payload = anrInfo.toString()
|
|
243
|
+
|
|
244
|
+
// Persist first: survives process kill before JS/network can run.
|
|
245
|
+
applicationContext?.let { context ->
|
|
246
|
+
FaroAnrCache.savePendingAnr(context, payload)
|
|
247
|
+
}
|
|
248
|
+
|
|
199
249
|
synchronized(anrListLock) {
|
|
200
|
-
anrList.add(
|
|
250
|
+
anrList.add(payload)
|
|
201
251
|
}
|
|
252
|
+
|
|
253
|
+
onAnrDetected?.invoke(payload)
|
|
202
254
|
|
|
203
255
|
Log.w(TAG, "ANR detected: $stackTraceStr")
|
|
204
256
|
} catch (e: Exception) {
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
package com.grafana.faro.reactnative
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Extracts the blocked main-thread stack from an ApplicationExitInfo ANR trace dump.
|
|
5
|
+
*
|
|
6
|
+
* Android emits a multi-thread text blob (see `ApplicationExitInfo.traceInputStream`).
|
|
7
|
+
* Sentry-style ANR reporting uses the `"main"` thread section, not a live
|
|
8
|
+
* [Thread.getStackTrace] snapshot from a watchdog thread.
|
|
9
|
+
*/
|
|
10
|
+
internal object AnrThreadDumpParser {
|
|
11
|
+
|
|
12
|
+
private val threadHeader = Regex("^\"[^\"]+\".*prio=")
|
|
13
|
+
private val frameWithAt = Regex("^at\\s+.+")
|
|
14
|
+
private val frameWithoutAt = Regex("^[\\w$.]+\\.[\\w$<>]+\\(.+\\)\\s*$")
|
|
15
|
+
|
|
16
|
+
fun extractMainThreadStack(fullDump: String): String {
|
|
17
|
+
if (fullDump.isBlank()) {
|
|
18
|
+
return ""
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
val lines = fullDump.lines()
|
|
22
|
+
var inMain = false
|
|
23
|
+
val stack = StringBuilder()
|
|
24
|
+
|
|
25
|
+
for (line in lines) {
|
|
26
|
+
val trimmed = line.trim()
|
|
27
|
+
if (trimmed.isEmpty()) {
|
|
28
|
+
continue
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
if (!inMain) {
|
|
32
|
+
if (trimmed.startsWith("\"main\"") || trimmed == "main") {
|
|
33
|
+
inMain = true
|
|
34
|
+
}
|
|
35
|
+
continue
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
if (threadHeader.matches(trimmed) && !trimmed.startsWith("\"main\"")) {
|
|
39
|
+
break
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
if (frameWithAt.matches(trimmed)) {
|
|
43
|
+
stack.appendLine(if (line.startsWith(" ")) line else " $trimmed")
|
|
44
|
+
continue
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
if (frameWithoutAt.matches(trimmed)) {
|
|
48
|
+
stack.appendLine(" at $trimmed")
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
if (stack.isNotEmpty()) {
|
|
53
|
+
return stack.toString().trim()
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// Some devices return only flat "at …" lines without a DALVIK THREADS header.
|
|
57
|
+
return extractFlatAtLines(fullDump)
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
private fun extractFlatAtLines(fullDump: String): String {
|
|
61
|
+
val stack = StringBuilder()
|
|
62
|
+
for (line in fullDump.lines()) {
|
|
63
|
+
val trimmed = line.trim()
|
|
64
|
+
if (frameWithAt.matches(trimmed)) {
|
|
65
|
+
stack.appendLine(if (line.startsWith(" ")) line else " $trimmed")
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
return stack.toString().trim()
|
|
69
|
+
}
|
|
70
|
+
}
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
package com.grafana.faro.reactnative
|
|
2
|
+
|
|
3
|
+
import android.app.ApplicationExitInfo
|
|
4
|
+
import android.os.Build
|
|
5
|
+
import android.util.Log
|
|
6
|
+
import java.io.ByteArrayOutputStream
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Reads and normalizes ApplicationExitInfo trace streams into text tombstones.
|
|
10
|
+
*/
|
|
11
|
+
internal object ApplicationExitTraceReader {
|
|
12
|
+
|
|
13
|
+
private const val TAG = "FaroCrashReporter"
|
|
14
|
+
private const val MAX_TRACE_BYTES = 512 * 1024
|
|
15
|
+
private const val MAX_TRACE_LINES = 200
|
|
16
|
+
|
|
17
|
+
data class ParsedExitTrace(
|
|
18
|
+
val text: String,
|
|
19
|
+
val signal: String = "",
|
|
20
|
+
val source: TraceSource = TraceSource.NONE,
|
|
21
|
+
)
|
|
22
|
+
|
|
23
|
+
enum class TraceSource {
|
|
24
|
+
NONE,
|
|
25
|
+
TEXT_STREAM,
|
|
26
|
+
PROTOBUF_TOMBSTONE,
|
|
27
|
+
CACHED_NATIVE,
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
fun read(exitInfo: ApplicationExitInfo): ParsedExitTrace {
|
|
31
|
+
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.R) {
|
|
32
|
+
return ParsedExitTrace("")
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
return try {
|
|
36
|
+
val traceInputStream = exitInfo.traceInputStream ?: return ParsedExitTrace("")
|
|
37
|
+
val bytes = readAllBytes(traceInputStream)
|
|
38
|
+
if (bytes.isEmpty()) {
|
|
39
|
+
return ParsedExitTrace("")
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
parseTraceBytes(exitInfo, bytes)
|
|
43
|
+
} catch (error: Exception) {
|
|
44
|
+
Log.w(TAG, "Failed to read ApplicationExitInfo trace: ${error.message}")
|
|
45
|
+
ParsedExitTrace("")
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
fun parseTraceBytes(exitInfo: ApplicationExitInfo, bytes: ByteArray): ParsedExitTrace {
|
|
50
|
+
val asText = decodeUtf8(bytes)
|
|
51
|
+
if (TombstoneBacktraceFormatter.looksLikeTextTombstone(asText)) {
|
|
52
|
+
Log.i(TAG, "[Faro crash native] Using text tombstone from ApplicationExitInfo (${asText.lineCount()} lines)")
|
|
53
|
+
return ParsedExitTrace(
|
|
54
|
+
text = trimTraceLines(asText),
|
|
55
|
+
source = TraceSource.TEXT_STREAM,
|
|
56
|
+
)
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S &&
|
|
60
|
+
exitInfo.reason == ApplicationExitInfo.REASON_CRASH_NATIVE
|
|
61
|
+
) {
|
|
62
|
+
return parseProtobufTombstone(bytes)
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
if (asText.isNotBlank()) {
|
|
66
|
+
return ParsedExitTrace(
|
|
67
|
+
text = trimTraceLines(asText),
|
|
68
|
+
source = TraceSource.TEXT_STREAM,
|
|
69
|
+
)
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
return ParsedExitTrace("")
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
private fun parseProtobufTombstone(bytes: ByteArray): ParsedExitTrace {
|
|
76
|
+
val parsed = TombstoneWireParser.parse(bytes)
|
|
77
|
+
if (parsed == null) {
|
|
78
|
+
Log.w(TAG, "[Faro crash native] Failed to parse tombstone protobuf (${bytes.size} bytes)")
|
|
79
|
+
return ParsedExitTrace("")
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
val formatted = TombstoneBacktraceFormatter.format(parsed)
|
|
83
|
+
if (formatted.text.isBlank()) {
|
|
84
|
+
Log.w(TAG, "[Faro crash native] Tombstone protobuf parsed but backtrace was empty")
|
|
85
|
+
return ParsedExitTrace("")
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
Log.i(
|
|
89
|
+
TAG,
|
|
90
|
+
"[Faro crash native] Decoded tombstone protobuf (${formatted.text.lineCount()} lines, signal=${formatted.signal})",
|
|
91
|
+
)
|
|
92
|
+
return ParsedExitTrace(
|
|
93
|
+
text = trimTraceLines(formatted.text),
|
|
94
|
+
signal = formatted.signal,
|
|
95
|
+
source = TraceSource.PROTOBUF_TOMBSTONE,
|
|
96
|
+
)
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
private fun readAllBytes(input: java.io.InputStream): ByteArray {
|
|
100
|
+
val buffer = ByteArrayOutputStream()
|
|
101
|
+
val chunk = ByteArray(8192)
|
|
102
|
+
var total = 0
|
|
103
|
+
while (true) {
|
|
104
|
+
val read = input.read(chunk)
|
|
105
|
+
if (read <= 0) {
|
|
106
|
+
break
|
|
107
|
+
}
|
|
108
|
+
total += read
|
|
109
|
+
if (total > MAX_TRACE_BYTES) {
|
|
110
|
+
Log.w(TAG, "ApplicationExitInfo trace exceeded ${MAX_TRACE_BYTES} bytes; truncating")
|
|
111
|
+
buffer.write(chunk, 0, read.coerceAtMost(MAX_TRACE_BYTES - (total - read)))
|
|
112
|
+
break
|
|
113
|
+
}
|
|
114
|
+
buffer.write(chunk, 0, read)
|
|
115
|
+
}
|
|
116
|
+
return buffer.toByteArray()
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
private fun decodeUtf8(bytes: ByteArray): String {
|
|
120
|
+
if (bytes.isEmpty()) {
|
|
121
|
+
return ""
|
|
122
|
+
}
|
|
123
|
+
if (bytes.any { it == 0.toByte() }) {
|
|
124
|
+
return ""
|
|
125
|
+
}
|
|
126
|
+
return bytes.toString(Charsets.UTF_8).trim()
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
private fun trimTraceLines(text: String): String {
|
|
130
|
+
return text.lineSequence()
|
|
131
|
+
.take(MAX_TRACE_LINES)
|
|
132
|
+
.joinToString("\n")
|
|
133
|
+
.trim()
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
private fun String.lineCount(): Int {
|
|
137
|
+
if (isEmpty()) {
|
|
138
|
+
return 0
|
|
139
|
+
}
|
|
140
|
+
return lines().size
|
|
141
|
+
}
|
|
142
|
+
}
|