diff --git a/.buildconfig.yml b/.buildconfig.yml index cc8f66c784a..75c7c121a34 100644 --- a/.buildconfig.yml +++ b/.buildconfig.yml @@ -343,6 +343,10 @@ projects: path: components/support/android-test description: 'A collection of helpers for testing components from instrumented (on device) tests.' publish: true + support-rusterrors: + path: components/support/rusterrors + description: 'A bridge for reporting Rust errors to Sentry/Glean' + publish: true support-test-appservices: path: components/support/test-appservices description: 'A component for synchronizing Application Services'' unit testing dependencies used in Android Components.' diff --git a/buildSrc/src/main/java/Dependencies.kt b/buildSrc/src/main/java/Dependencies.kt index 2f3a52ab354..c1ac1d12f61 100644 --- a/buildSrc/src/main/java/Dependencies.kt +++ b/buildSrc/src/main/java/Dependencies.kt @@ -166,6 +166,7 @@ object Dependencies { const val mozilla_full_megazord = "org.mozilla.appservices:full-megazord:${Versions.mozilla_appservices}" const val mozilla_full_megazord_forUnitTests = "org.mozilla.appservices:full-megazord-forUnitTests:${Versions.mozilla_appservices}" + const val mozilla_errorsupport = "org.mozilla.appservices:errorsupport:${Versions.mozilla_appservices}" const val mozilla_rustlog = "org.mozilla.appservices:rustlog:${Versions.mozilla_appservices}" const val mozilla_sync15 = "org.mozilla.appservices:sync15:${Versions.mozilla_appservices}" diff --git a/components/concept/base/src/main/java/mozilla/components/concept/base/crash/RustCrashReport.kt b/components/concept/base/src/main/java/mozilla/components/concept/base/crash/RustCrashReport.kt new file mode 100644 index 00000000000..636e3bcb8b3 --- /dev/null +++ b/components/concept/base/src/main/java/mozilla/components/concept/base/crash/RustCrashReport.kt @@ -0,0 +1,19 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +package mozilla.components.concept.base.crash + +/** + * Crash report for rust errors + * + * We implement this on exception classes that correspond to Rust errors to + * customize how the crash reports look. + * + * CrashReporting implementors should test if exceptions implement this + * interface. If so, they should try to customize their crash reports to match. + */ +interface RustCrashReport { + val typeName: String + val message: String +} diff --git a/components/lib/crash-sentry/src/main/java/mozilla/components/lib/crash/sentry/SentryService.kt b/components/lib/crash-sentry/src/main/java/mozilla/components/lib/crash/sentry/SentryService.kt index 91fd759d24e..1850b22382d 100644 --- a/components/lib/crash-sentry/src/main/java/mozilla/components/lib/crash/sentry/SentryService.kt +++ b/components/lib/crash-sentry/src/main/java/mozilla/components/lib/crash/sentry/SentryService.kt @@ -9,10 +9,13 @@ import androidx.annotation.GuardedBy import androidx.annotation.VisibleForTesting import io.sentry.Breadcrumb import io.sentry.Sentry +import io.sentry.SentryEvent import io.sentry.SentryLevel +import io.sentry.SentryOptions.BeforeSendCallback import io.sentry.android.core.SentryAndroid import io.sentry.protocol.SentryId import mozilla.components.Build +import mozilla.components.concept.base.crash.RustCrashReport import mozilla.components.lib.crash.Crash import mozilla.components.lib.crash.service.CrashReporterService import java.util.Locale @@ -131,6 +134,27 @@ class SentryService( options.isEnableNdk = false options.dsn = dsn options.environment = environment + options.beforeSend = BeforeSendCallback { event, _ -> + val throwable = event.throwable + if (throwable is RustCrashReport) { + alterEventForRustCrash(event, throwable) + } + event + } + } + } + + private fun alterEventForRustCrash(event: SentryEvent, crash: RustCrashReport) { + event.fingerprints = listOf(crash.typeName) + // Sentry supports multiple exceptions in an event, modify + // the top-level one controls how the event is displayed + // + // It's technically possible for the event to have a null + // or empty exception list, but that shouldn't happen in + // practice. + event.exceptions?.firstOrNull()?.let { sentryException -> + sentryException.type = crash.typeName + sentryException.value = crash.message } } diff --git a/components/support/rusterrors/README.md b/components/support/rusterrors/README.md new file mode 100644 index 00000000000..d1fa8cafcb0 --- /dev/null +++ b/components/support/rusterrors/README.md @@ -0,0 +1,5 @@ +Handles errors that come from Rust functions. + +This component defines and installs an application-services `ApplicationErrorReporter` class that: + - Forwords error reports and breadcrumbs to `SentryServices` + - Reports error counts to Glean diff --git a/components/support/rusterrors/build.gradle b/components/support/rusterrors/build.gradle new file mode 100644 index 00000000000..e8160d8b08c --- /dev/null +++ b/components/support/rusterrors/build.gradle @@ -0,0 +1,38 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +apply plugin: 'com.android.library' +apply plugin: 'kotlin-android' + +android { + compileSdkVersion config.compileSdkVersion + + defaultConfig { + minSdkVersion config.minSdkVersion + targetSdkVersion config.targetSdkVersion + } + + lintOptions { + warningsAsErrors true + abortOnError true + } + + buildTypes { + release { + minifyEnabled false + proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' + consumerProguardFiles 'proguard-rules-consumer.pro' + } + } +} + +dependencies { + implementation Dependencies.mozilla_errorsupport + implementation Dependencies.kotlin_stdlib + implementation Dependencies.kotlin_coroutines + implementation project(':support-base') +} + +apply from: '../../../publish.gradle' +ext.configurePublish(config.componentsGroupId, archivesBaseName, project.ext.description) diff --git a/components/support/rusterrors/proguard-rules-consumer.pro b/components/support/rusterrors/proguard-rules-consumer.pro new file mode 100644 index 00000000000..d3456cd17ef --- /dev/null +++ b/components/support/rusterrors/proguard-rules-consumer.pro @@ -0,0 +1 @@ +# ProGuard rules for consumers of this library. diff --git a/components/support/rusterrors/proguard-rules.pro b/components/support/rusterrors/proguard-rules.pro new file mode 100644 index 00000000000..50e2b38a975 --- /dev/null +++ b/components/support/rusterrors/proguard-rules.pro @@ -0,0 +1,25 @@ +# Add project specific ProGuard rules here. +# By default, the flags in this file are appended to flags specified +# in /Users/sebastian/Library/Android/sdk/tools/proguard/proguard-android.txt +# You can edit the include path and order by changing the proguardFiles +# directive in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# Add any project specific keep options here: + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile diff --git a/components/support/rusterrors/src/main/AndroidManifest.xml b/components/support/rusterrors/src/main/AndroidManifest.xml new file mode 100644 index 00000000000..ec136da2309 --- /dev/null +++ b/components/support/rusterrors/src/main/AndroidManifest.xml @@ -0,0 +1,5 @@ + + diff --git a/components/support/rusterrors/src/main/java/mozilla/components/support/rusterrors/RustErrors.kt b/components/support/rusterrors/src/main/java/mozilla/components/support/rusterrors/RustErrors.kt new file mode 100644 index 00000000000..de679582cd4 --- /dev/null +++ b/components/support/rusterrors/src/main/java/mozilla/components/support/rusterrors/RustErrors.kt @@ -0,0 +1,39 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +package mozilla.components.support.rusterrors + +import mozilla.appservices.errorsupport.ApplicationErrorReporter +import mozilla.appservices.errorsupport.setApplicationErrorReporter +import mozilla.components.concept.base.crash.Breadcrumb +import mozilla.components.concept.base.crash.CrashReporting +import mozilla.components.concept.base.crash.RustCrashReport + +/** + * Initialize application services error reporting + * + * Errors reports and breadcrumbs from Application Services will be forwarded + * to the CrashReporting instance. Error counting, which is used for expected + * errors like network errors, will be counted with Glean. + */ +public fun initializeRustErrors(crashReporter: CrashReporting) { + setApplicationErrorReporter(AndroidComponentsErrorReportor(crashReporter)) +} + +internal class AppServicesErrorReport( + override val typeName: String, + override val message: String, +) : Exception(typeName), RustCrashReport + +private class AndroidComponentsErrorReportor( + val crashReporter: CrashReporting +) : ApplicationErrorReporter { + override fun reportError(typeName: String, message: String) { + crashReporter.submitCaughtException(AppServicesErrorReport(typeName, message)) + } + + override fun reportBreadcrumb(message: String, module: String, line: UInt, column: UInt) { + crashReporter.recordCrashBreadcrumb(Breadcrumb("$module[$line]: $message")) + } +} diff --git a/docs/changelog.md b/docs/changelog.md index 5f2b2bffa69..ef267639aa5 100644 --- a/docs/changelog.md +++ b/docs/changelog.md @@ -37,6 +37,9 @@ permalink: /changelog/ Use `listOf` instead of `longArrayOf` or call `.toList` * `TimingDistributionMetricType.start` now always returns a valid `TimerId`, `TimingDistributionMetricType.stopAndAccumulate` always requires a `TimerId`. +* **support-rusterrors** + * 🆕 New component to report Rust errors + # 102.0.0 * [Commits](https://github.com/mozilla-mobile/android-components/compare/v101.0.0...v102.0.1) * [Milestone](https://github.com/mozilla-mobile/android-components/milestone/149?closed=1) diff --git a/taskcluster/ci/config.yml b/taskcluster/ci/config.yml index 31554212c7a..69c0a962eb4 100644 --- a/taskcluster/ci/config.yml +++ b/taskcluster/ci/config.yml @@ -104,6 +104,7 @@ treeherder: support-locale: support-locale support-migration: support-migration support-rusthttp: support-rusthttp + support-rusterrors: support-rusterrors support-rustlog: support-rustlog support-sync-telemetry: support-sync-telemetry support-test-appservices: support-test-appservices