summaryrefslogtreecommitdiffstats
path: root/third_party/rust/error-support/src
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 19:33:14 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 19:33:14 +0000
commit36d22d82aa202bb199967e9512281e9a53db42c9 (patch)
tree105e8c98ddea1c1e4784a60a5a6410fa416be2de /third_party/rust/error-support/src
parentInitial commit. (diff)
downloadfirefox-esr-36d22d82aa202bb199967e9512281e9a53db42c9.tar.xz
firefox-esr-36d22d82aa202bb199967e9512281e9a53db42c9.zip
Adding upstream version 115.7.0esr.upstream/115.7.0esr
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'third_party/rust/error-support/src')
-rw-r--r--third_party/rust/error-support/src/errorsupport.udl16
-rw-r--r--third_party/rust/error-support/src/handling.rs112
-rw-r--r--third_party/rust/error-support/src/lib.rs162
-rw-r--r--third_party/rust/error-support/src/macros.rs62
-rw-r--r--third_party/rust/error-support/src/redact.rs75
-rw-r--r--third_party/rust/error-support/src/reporting.rs71
6 files changed, 498 insertions, 0 deletions
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);
+}