diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 19:33:14 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 19:33:14 +0000 |
commit | 36d22d82aa202bb199967e9512281e9a53db42c9 (patch) | |
tree | 105e8c98ddea1c1e4784a60a5a6410fa416be2de /third_party/rust/error-support | |
parent | Initial commit. (diff) | |
download | firefox-esr-upstream.tar.xz firefox-esr-upstream.zip |
Adding upstream version 115.7.0esr.upstream/115.7.0esrupstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'third_party/rust/error-support')
-rw-r--r-- | third_party/rust/error-support/.cargo-checksum.json | 1 | ||||
-rw-r--r-- | third_party/rust/error-support/Cargo.toml | 40 | ||||
-rw-r--r-- | third_party/rust/error-support/README.md | 89 | ||||
-rw-r--r-- | third_party/rust/error-support/android/build.gradle | 7 | ||||
-rw-r--r-- | third_party/rust/error-support/android/src/main/AndroidManifest.xml | 2 | ||||
-rw-r--r-- | third_party/rust/error-support/build.rs | 8 | ||||
-rw-r--r-- | third_party/rust/error-support/src/errorsupport.udl | 16 | ||||
-rw-r--r-- | third_party/rust/error-support/src/handling.rs | 112 | ||||
-rw-r--r-- | third_party/rust/error-support/src/lib.rs | 162 | ||||
-rw-r--r-- | third_party/rust/error-support/src/macros.rs | 62 | ||||
-rw-r--r-- | third_party/rust/error-support/src/redact.rs | 75 | ||||
-rw-r--r-- | third_party/rust/error-support/src/reporting.rs | 71 | ||||
-rw-r--r-- | third_party/rust/error-support/uniffi.toml | 8 |
13 files changed, 653 insertions, 0 deletions
diff --git a/third_party/rust/error-support/.cargo-checksum.json b/third_party/rust/error-support/.cargo-checksum.json new file mode 100644 index 0000000000..ff145c497b --- /dev/null +++ b/third_party/rust/error-support/.cargo-checksum.json @@ -0,0 +1 @@ +{"files":{"Cargo.toml":"54be1df819228db901e155bdf99a313c43f24df61f423093f03f2a42da394cfb","README.md":"8030b4a314b1be31ba018ac12c3b586bb736db5307c3c395f2857fffe0130322","android/build.gradle":"200fe9fcf26477ae4e941dd1e702c43deae9fb0a7252569bd7352eac1771efbe","android/src/main/AndroidManifest.xml":"4f8b16fa6a03120ac810c6438a3a60294075414d92e06caa7e85388e389e5d17","build.rs":"c8d3c38c1208eea36224662b284d8daf3e7ad1b07d22d750524f3da1cc66ccca","src/errorsupport.udl":"e793034d01a2608298528051757f38405e006ee1abc4cf65dc6f18c53590ace8","src/handling.rs":"545c969d71907d81cb5af93f435ba443508adda2ec57ac2a975fed7d9828ccea","src/lib.rs":"96ae3cc2c1077ae45442ace6b5b5311b86267d0b9067f3ff58396af30ccbbc07","src/macros.rs":"0d03f82fab20c96a182f941baf3fcf2a286b00fea871ee7fd8e339abc14f9522","src/redact.rs":"c9a4df1a87be68b15d583587bda941d4c60a1d0449e2d43ff99f3611a290a863","src/reporting.rs":"38efd24d86ba8facfb181cb27e8b698d2831db0afab85691ffda034a4dc68dfa","uniffi.toml":"644fe81c12fe3c01ee81e017ca3c00d0e611f014b7eade51aadaf208179a3450"},"package":null}
\ No newline at end of file diff --git a/third_party/rust/error-support/Cargo.toml b/third_party/rust/error-support/Cargo.toml new file mode 100644 index 0000000000..35d911dd75 --- /dev/null +++ b/third_party/rust/error-support/Cargo.toml @@ -0,0 +1,40 @@ +# THIS FILE IS AUTOMATICALLY GENERATED BY CARGO +# +# When uploading crates to the registry Cargo will automatically +# "normalize" Cargo.toml files for maximal compatibility +# with all versions of Cargo and also rewrite `path` dependencies +# to registry (e.g., crates.io) dependencies. +# +# If you are reading this file be aware that the original Cargo.toml +# will likely look very different (and much more reasonable). +# See Cargo.toml.orig for the original contents. + +[package] +edition = "2021" +name = "error-support" +version = "0.1.0" +authors = ["Thom Chiovoloni <tchiovoloni@mozilla.com>"] +autotests = false +readme = "README.md" +license = "MPL-2.0" + +[dependencies] +log = "0.4" +uniffi = "0.23" + +[dependencies.backtrace] +version = "0.3" +optional = true + +[dependencies.error-support-macros] +path = "macros" + +[dependencies.lazy_static] +version = "1.4" + +[dependencies.parking_lot] +version = ">=0.11,<=0.12" + +[build-dependencies.uniffi] +version = "0.23" +features = ["build"] diff --git a/third_party/rust/error-support/README.md b/third_party/rust/error-support/README.md new file mode 100644 index 0000000000..aed789ef7b --- /dev/null +++ b/third_party/rust/error-support/README.md @@ -0,0 +1,89 @@ +# Application error handling support + +This crate provides support for other crates to effectively report errors. +Because app-services components get embedded in various apps, written in +multiple languages, we face several challenges: + + - Rust stacktraces are generally not available so we often need to provide + extra context to help debug where an error is occuring. + - Without stack traces, Sentry and other error reporting systems don't do a + great job at auto-grouping errors together, so we need to manually group them. + - We can't hook directly into the error reporting system or even depend on a + particular error reporting system to be in use. This means the system + needs to be simple and flexible enough to plug in to multiple systems. + +## Breadcrumbs as context + +We use a breadcrumb system as the basis for adding context to errors. +Breadcrumbs are individual messages that form a log-style stream, and the most +recent breadcrumbs get attached to each error reported. There are a lot of +other ways to provide context to an error, but we just use breadcrumbs for +everything because it's a relatively simple system that's easy to hook up to +error reporting platforms. + +## Basic error reporting tools + +Basic error reporting is handled using several macros: + + - `report_error!()` creates an error report. It inputs a `type_name` as the + first parameter, and `format!` style arguments afterwards. `type_name` is + used to group errors together and show up as error titles/headers. Use the + format-style args to create is a long-form description for the issue. Most + of the time you won't need to call this directly, since can automatically + do it when converting internal results to public ones. However, it can be + useful in the case where you see an error that you want + to report, but want to try to recover rather than returning an error. + - `breadcrumb!()` creates a new breadcrumb that will show up on future errors. + - `trace_error!()` inputs a `Result<>` and creates a breadcrumb if it's an + `Err`. This is useful if when you're trying to track down where an error + originated from, since you can wrap each possible source of the error with + `trace_error!()`. `trace_error!()` returns the result passed in to it, + which makes wrapping function calls easy. + + +## Public/Internal errors and converting between them + +Our components generally create 2 error enums: one for internal use and one for +the public API. They are typically named `Error` and +`[ComponentName]ApiError`. The internal error typically carries a lot of +low-level details and lots of variants which is useful to us app-services +developers. The public error typically has less variants with the variants +often just storing a reason string rather than low-level error codes. There +are also two `Result<>` types that correspond to these two errors, typically +named `Result` and `ApiResult`. + +This means we need to convert from internal errors to public errors, which has +the nice side benefit of giving us a centralized spot to make error reports for +selected public errors. This is done with the `ErrorHandling` type and +`GetErrorHandling` trait in `src/handling.rs`. The basic system is that you +convert between one error to another and choose if you want to report the error +and/or log a warning. When reporting an error you can choose a type name to +group the error with. This system is extremely flexible, since you can inspect +the internal error and use error codes or other data to determine if it should +be reported or not, which type name to report it with, etc. Eventually we also +hope to allow expected errors to be counted in telemetry (think things like +network errors, shutdown errors, etc.). + +To assist this conversion, the `handle_error` procedural macro can be used to +automatically convert between `Result` and `ApiResult` using +`GetErrorHandling`. Note that this depends on having the `Result` type +imported in your module with a `use` statement. + +See the `logins::errors` and `logins::store` modules for an example of how this +all fits together. + +## ⚠️ Personally Identifiable Information ⚠️ + +When converting internal errors to public errors, we should ensure that there +is no personally identifying information (PII) in any error reports. We should +also ensure that no PII is contained in the public error enum, since consumers +may end up uses those for their own error reports. + +We operate on a best-effort basis to ensure this. Our error details often come +from an error from one of our dependencies, which makes it very diffucult to be +completely sure though. For example, `rusqlite::Error` could include data from +a user's database in their errors, which would then appear in our error +variants. However, we've never seen that in practice so we are comfortable +including the `rusqlite` error message in our error reports, without attempting +to sanitize them. + diff --git a/third_party/rust/error-support/android/build.gradle b/third_party/rust/error-support/android/build.gradle new file mode 100644 index 0000000000..4ef6d7a11a --- /dev/null +++ b/third_party/rust/error-support/android/build.gradle @@ -0,0 +1,7 @@ + +apply from: "$rootDir/build-scripts/component-common.gradle" +apply from: "$rootDir/publish.gradle" + +ext.configureUniFFIBindgen("../src/errorsupport.udl") +ext.dependsOnTheMegazord() +ext.configurePublish() diff --git a/third_party/rust/error-support/android/src/main/AndroidManifest.xml b/third_party/rust/error-support/android/src/main/AndroidManifest.xml new file mode 100644 index 0000000000..51c272b64e --- /dev/null +++ b/third_party/rust/error-support/android/src/main/AndroidManifest.xml @@ -0,0 +1,2 @@ +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="org.mozilla.appservices.errorsupport" /> diff --git a/third_party/rust/error-support/build.rs b/third_party/rust/error-support/build.rs new file mode 100644 index 0000000000..afa61ff66a --- /dev/null +++ b/third_party/rust/error-support/build.rs @@ -0,0 +1,8 @@ +/* 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/. + */ + +fn main() { + uniffi::generate_scaffolding("./src/errorsupport.udl").unwrap(); +} diff --git a/third_party/rust/error-support/src/errorsupport.udl b/third_party/rust/error-support/src/errorsupport.udl new file mode 100644 index 0000000000..40482bc4e9 --- /dev/null +++ b/third_party/rust/error-support/src/errorsupport.udl @@ -0,0 +1,16 @@ +/* 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/. */ + +namespace errorsupport { + // Set the global error reporter. This is typically done early in startup. + void set_application_error_reporter(ApplicationErrorReporter error_reporter); + // Unset the global error reporter. This is typically done at shutdown for + // platforms that want to cleanup references like Desktop. + void unset_application_error_reporter(); +}; + +callback interface ApplicationErrorReporter { + void report_error(string type_name, string message); + void report_breadcrumb(string message, string module, u32 line, u32 column); +}; diff --git a/third_party/rust/error-support/src/handling.rs b/third_party/rust/error-support/src/handling.rs new file mode 100644 index 0000000000..f01fbc2b00 --- /dev/null +++ b/third_party/rust/error-support/src/handling.rs @@ -0,0 +1,112 @@ +/* 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/. */ + +//! Helpers for components to "handle" errors. + +/// Describes what error reporting action should be taken. +#[derive(Debug, Default)] +pub struct ErrorReporting { + /// If Some(level), will write a log message at that level. + log_level: Option<log::Level>, + /// If Some(report_class) will call the error reporter with details. + report_class: Option<String>, +} + +/// Specifies how an "internal" error is converted to an "external" public error and +/// any logging or reporting that should happen. +pub struct ErrorHandling<E> { + /// The external error that should be returned. + pub err: E, + /// How the error should be reported. + pub reporting: ErrorReporting, +} + +impl<E> ErrorHandling<E> { + /// Create an ErrorHandling instance with an error conversion. + /// + /// ErrorHandling instance are created using a builder-style API. This is always the first + /// function in the chain, optionally followed by `log()`, `report()`, etc. + pub fn convert(err: E) -> Self { + Self { + err, + reporting: ErrorReporting::default(), + } + } + + /// Add logging to an ErrorHandling instance + pub fn log(self, level: log::Level) -> Self { + Self { + err: self.err, + reporting: ErrorReporting { + log_level: Some(level), + ..self.reporting + }, + } + } + + /// Add reporting to an ErrorHandling instance + pub fn report(self, report_class: impl Into<String>) -> Self { + Self { + err: self.err, + reporting: ErrorReporting { + report_class: Some(report_class.into()), + ..self.reporting + }, + } + } + + // Convenience functions for the most common error reports + + /// log a warning + pub fn log_warning(self) -> Self { + self.log(log::Level::Warn) + } + + /// log an info + pub fn log_info(self) -> Self { + self.log(log::Level::Info) + } + + /// Add reporting to an ErrorHandling instance and also log an Error + pub fn report_error(self, report_class: impl Into<String>) -> Self { + Self { + err: self.err, + reporting: ErrorReporting { + log_level: Some(log::Level::Error), + report_class: Some(report_class.into()), + }, + } + } +} + +/// A trait to define how errors are converted and reported. +pub trait GetErrorHandling { + type ExternalError; + + /// Return how to handle our internal errors + fn get_error_handling(&self) -> ErrorHandling<Self::ExternalError>; +} + +/// Handle the specified "internal" error, taking any logging or error +/// reporting actions and converting the error to the public error. +/// Called by our `handle_error` macro so needs to be public. +pub fn convert_log_report_error<IE, EE>(e: IE) -> EE +where + IE: GetErrorHandling<ExternalError = EE> + std::error::Error, + EE: std::error::Error, +{ + let handling = e.get_error_handling(); + let reporting = handling.reporting; + if let Some(level) = reporting.log_level { + log::log!(level, "{}", e.to_string()); + } + if let Some(report_class) = reporting.report_class { + // notify the error reporter if the feature is enabled. + // XXX - should we arrange for the `report_class` to have the + // original crate calling this as a prefix, or will we still be + // able to identify that? + crate::report_error_to_app(report_class, e.to_string()); + } + handling.err +} diff --git a/third_party/rust/error-support/src/lib.rs b/third_party/rust/error-support/src/lib.rs new file mode 100644 index 0000000000..075e833f10 --- /dev/null +++ b/third_party/rust/error-support/src/lib.rs @@ -0,0 +1,162 @@ +/* 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/. */ + +mod macros; + +#[cfg(feature = "backtrace")] +/// Re-export of the `backtrace` crate for use in macros and +/// to ensure the needed version is kept in sync in dependents. +pub use backtrace; + +#[cfg(not(feature = "backtrace"))] +/// A compatibility shim for `backtrace`. +pub mod backtrace { + use std::fmt; + + pub struct Backtrace; + + impl fmt::Debug for Backtrace { + #[cold] + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "Not available") + } + } +} + +mod redact; +pub use redact::*; + +mod reporting; +pub use reporting::{ + report_breadcrumb, report_error_to_app, set_application_error_reporter, + unset_application_error_reporter, ApplicationErrorReporter, +}; + +pub use error_support_macros::handle_error; + +mod handling; +pub use handling::{convert_log_report_error, ErrorHandling, ErrorReporting, GetErrorHandling}; + +/// XXX - Most of this is now considered deprecated - only FxA uses it, and +/// should be replaced with the facilities in the `handling` module. + +/// Define a wrapper around the the provided ErrorKind type. +/// See also `define_error` which is more likely to be what you want. +#[macro_export] +macro_rules! define_error_wrapper { + ($Kind:ty) => { + pub type Result<T, E = Error> = std::result::Result<T, E>; + struct ErrorData { + kind: $Kind, + backtrace: Option<std::sync::Mutex<$crate::backtrace::Backtrace>>, + } + + impl ErrorData { + #[cold] + fn new(kind: $Kind) -> Self { + ErrorData { + kind, + #[cfg(feature = "backtrace")] + backtrace: Some(std::sync::Mutex::new( + $crate::backtrace::Backtrace::new_unresolved(), + )), + #[cfg(not(feature = "backtrace"))] + backtrace: None, + } + } + + #[cfg(feature = "backtrace")] + #[cold] + fn get_backtrace(&self) -> Option<&std::sync::Mutex<$crate::backtrace::Backtrace>> { + self.backtrace.as_ref().map(|mutex| { + mutex.lock().unwrap().resolve(); + mutex + }) + } + + #[cfg(not(feature = "backtrace"))] + #[cold] + fn get_backtrace(&self) -> Option<&std::sync::Mutex<$crate::backtrace::Backtrace>> { + None + } + } + + impl std::fmt::Debug for ErrorData { + #[cfg(feature = "backtrace")] + #[cold] + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + let mut bt = self.backtrace.unwrap().lock().unwrap(); + bt.resolve(); + write!(f, "{:?}\n\n{}", bt, self.kind) + } + + #[cfg(not(feature = "backtrace"))] + #[cold] + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + write!(f, "{}", self.kind) + } + } + + #[derive(Debug, thiserror::Error)] + pub struct Error(Box<ErrorData>); + impl Error { + #[cold] + pub fn kind(&self) -> &$Kind { + &self.0.kind + } + + #[cold] + pub fn backtrace(&self) -> Option<&std::sync::Mutex<$crate::backtrace::Backtrace>> { + self.0.get_backtrace() + } + } + + impl std::fmt::Display for Error { + #[cold] + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + std::fmt::Display::fmt(self.kind(), f) + } + } + + impl From<$Kind> for Error { + // Cold to optimize in favor of non-error cases. + #[cold] + fn from(ctx: $Kind) -> Error { + Error(Box::new(ErrorData::new(ctx))) + } + } + }; +} + +/// Define a set of conversions from external error types into the provided +/// error kind. Use `define_error` to do this at the same time as +/// `define_error_wrapper`. +#[macro_export] +macro_rules! define_error_conversions { + ($Kind:ident { $(($variant:ident, $type:ty)),* $(,)? }) => ($( + impl From<$type> for Error { + // Cold to optimize in favor of non-error cases. + #[cold] + fn from(e: $type) -> Self { + Error::from($Kind::$variant(e)) + } + } + )*); +} + +/// All the error boilerplate (okay, with a couple exceptions in some cases) in +/// one place. +#[macro_export] +macro_rules! define_error { + ($Kind:ident { $(($variant:ident, $type:ty)),* $(,)? }) => { + $crate::define_error_wrapper!($Kind); + $crate::define_error_conversions! { + $Kind { + $(($variant, $type)),* + } + } + }; +} + +uniffi::include_scaffolding!("errorsupport"); diff --git a/third_party/rust/error-support/src/macros.rs b/third_party/rust/error-support/src/macros.rs new file mode 100644 index 0000000000..11bf6dbca5 --- /dev/null +++ b/third_party/rust/error-support/src/macros.rs @@ -0,0 +1,62 @@ +/* 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/. */ + +/// Tell the application to report an error +/// +/// If configured by the application, this sent to the application, which should report it to a +/// Sentry-like system. This should only be used for errors that we don't expect to see and will +/// work on fixing if we see a non-trivial volume of them. +/// +/// type_name identifies the error. It should be the main text that gets shown to the +/// user and also how the error system groups errors together. It should be in UpperCamelCase +/// form. +/// +/// Good type_names require some trial and error, for example: +/// - Start with the error kind variant name +/// - Add more text to distinguish errors more. For example an error code, or an extra word +/// based on inspecting the error details +#[macro_export] +macro_rules! report_error { + ($type_name:expr, $($arg:tt)*) => { + let message = std::format!($($arg)*); + ::log::warn!("report {}: {}", $type_name, message); + $crate::report_error_to_app($type_name.to_string(), message.to_string()); + }; +} + +/// Log a breadcrumb if we see an `Result::Err` value +/// +/// Use this macro to wrap a function call that returns a `Result<>`. If that call returns an +/// error, then we will log a breadcrumb for it. This can be used to track down the codepath where +/// an error happened. +#[macro_export] +macro_rules! trace_error { + ($result:expr) => {{ + let result = $result; + if let Err(e) = &result { + $crate::breadcrumb!("Saw error: {}", e); + }; + result + }}; +} + +/// Tell the application to log a breadcrumb +/// +/// Breadcrumbs are log-like entries that get tracked by the error reporting system. When we +/// report an error, recent breadcrumbs will be associated with it. +#[macro_export] +macro_rules! breadcrumb { + ($($arg:tt)*) => { + { + let message = std::format!($($arg)*); + ::log::info!("breadcrumb: {}", message); + $crate::report_breadcrumb( + message, + std::module_path!().to_string(), + std::line!(), + std::column!(), + ); + } + }; +} diff --git a/third_party/rust/error-support/src/redact.rs b/third_party/rust/error-support/src/redact.rs new file mode 100644 index 0000000000..01a333ee35 --- /dev/null +++ b/third_party/rust/error-support/src/redact.rs @@ -0,0 +1,75 @@ +/* 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/. */ + +//! Functions to redact strings to remove PII before logging them + +/// Redact a URL. +/// +/// It's tricky to redact an URL without revealing PII. We check for various known bad URL forms +/// and report them, otherwise we just log "<URL>". +pub fn redact_url(url: &str) -> String { + if url.is_empty() { + return "<URL (empty)>".to_string(); + } + match url.find(':') { + None => "<URL (no scheme)>".to_string(), + Some(n) => { + let mut chars = url[0..n].chars(); + match chars.next() { + // No characters in the scheme + None => return "<URL (empty scheme)>".to_string(), + Some(c) => { + // First character must be alphabetic + if !c.is_ascii_alphabetic() { + return "<URL (invalid scheme)>".to_string(); + } + } + } + for c in chars { + // Subsequent characters must be in the set ( alpha | digit | "+" | "-" | "." ) + if !(c.is_ascii_alphanumeric() || c == '+' || c == '-' || c == '.') { + return "<URL (invalid scheme)>".to_string(); + } + } + "<URL>".to_string() + } + } +} + +/// Redact compact jwe string (Five base64 segments, separated by `.` chars) +pub fn redact_compact_jwe(url: &str) -> String { + url.replace(|ch| ch != '.', "x") +} + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn test_redact_url() { + assert_eq!(redact_url("http://some.website.com/index.html"), "<URL>"); + assert_eq!(redact_url("about:config"), "<URL>"); + assert_eq!(redact_url(""), "<URL (empty)>"); + assert_eq!(redact_url("://some.website.com/"), "<URL (empty scheme)>"); + assert_eq!(redact_url("some.website.com/"), "<URL (no scheme)>"); + assert_eq!(redact_url("some.website.com/"), "<URL (no scheme)>"); + assert_eq!( + redact_url("abc%@=://some.website.com/"), + "<URL (invalid scheme)>" + ); + assert_eq!( + redact_url("0https://some.website.com/"), + "<URL (invalid scheme)>" + ); + assert_eq!( + redact_url("a+weird-but.lega1-SCHEME://some.website.com/"), + "<URL>" + ); + } + + #[test] + fn test_redact_compact_jwe() { + assert_eq!(redact_compact_jwe("abc.1234.x3243"), "xxx.xxxx.xxxxx") + } +} diff --git a/third_party/rust/error-support/src/reporting.rs b/third_party/rust/error-support/src/reporting.rs new file mode 100644 index 0000000000..cf0f1ebd0b --- /dev/null +++ b/third_party/rust/error-support/src/reporting.rs @@ -0,0 +1,71 @@ +/* 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/. */ + +use parking_lot::RwLock; +use std::sync::atomic::{AtomicU32, Ordering}; + +/// Counter for breadcrumb messages +/// +/// We are currently seeing breadcrumbs that may indicate that the reporting is unreliable. In +/// some reports, the breadcrumbs seem like they may be duplicated and/or out of order. This +/// counter is a temporary measure to check out that theory. +static BREADCRUMB_COUNTER: AtomicU32 = AtomicU32::new(0); + +fn get_breadcrumb_counter_value() -> u32 { + // Notes: + // - fetch_add is specified to wrap around in case of overflow, which seems okay. + // - By itself, this does not guarentee that breadcrumb logs will be ordered the same way as + // the counter values. If two threads are running at the same time, it's very possible + // that thread A gets the lower breadcrumb value, but thread B wins the race to report its + // breadcrumb. However, if we expect operations to be synchronized, like with places DB, + // then the breadcrumb counter values should always increase by 1. + BREADCRUMB_COUNTER.fetch_add(1, Ordering::Relaxed) +} + +/// Application error reporting trait +/// +/// The application that's consuming application-services implements this via a UniFFI callback +/// interface, then calls `set_application_error_reporter()` to setup a global +/// ApplicationErrorReporter. +pub trait ApplicationErrorReporter: Sync + Send { + /// Send an error report to a Sentry-like error reporting system + /// + /// type_name should be used to group errors together + fn report_error(&self, type_name: String, message: String); + /// Send a breadcrumb to a Sentry-like error reporting system + fn report_breadcrumb(&self, message: String, module: String, line: u32, column: u32); +} + +// ApplicationErrorReporter to use if the app doesn't set one +struct DefaultApplicationErrorReporter; +impl ApplicationErrorReporter for DefaultApplicationErrorReporter { + fn report_error(&self, _type_name: String, _message: String) {} + fn report_breadcrumb(&self, _message: String, _module: String, _line: u32, _column: u32) {} +} + +lazy_static::lazy_static! { + // RwLock rather than a Mutex, since we only expect to set this once. + pub(crate) static ref APPLICATION_ERROR_REPORTER: RwLock<Box<dyn ApplicationErrorReporter>> = RwLock::new(Box::new(DefaultApplicationErrorReporter)); +} + +pub fn set_application_error_reporter(reporter: Box<dyn ApplicationErrorReporter>) { + *APPLICATION_ERROR_REPORTER.write() = reporter; +} + +pub fn unset_application_error_reporter() { + *APPLICATION_ERROR_REPORTER.write() = Box::new(DefaultApplicationErrorReporter) +} + +pub fn report_error_to_app(type_name: String, message: String) { + APPLICATION_ERROR_REPORTER + .read() + .report_error(type_name, message); +} + +pub fn report_breadcrumb(message: String, module: String, line: u32, column: u32) { + let message = format!("{} ({})", message, get_breadcrumb_counter_value()); + APPLICATION_ERROR_REPORTER + .read() + .report_breadcrumb(message, module, line, column); +} diff --git a/third_party/rust/error-support/uniffi.toml b/third_party/rust/error-support/uniffi.toml new file mode 100644 index 0000000000..356cf9fd07 --- /dev/null +++ b/third_party/rust/error-support/uniffi.toml @@ -0,0 +1,8 @@ +[bindings.kotlin] +package_name = "mozilla.appservices.errorsupport" +cdylib_name = "megazord" + +[bindings.swift] +ffi_module_name = "MozillaRustComponents" +ffi_module_filename = "errorFFI" +generate_module_map = false
\ No newline at end of file |