taggederror: introduce bail macro replacement which allows tagging

Summary: This change introduces a bail macro that allows tagging errors using the syntax `bail!(fault=Fault::Request, "my normal {}", bail_args)` or `bail!(Fault::Request, "my normal {}", bail_args)`.

Reviewed By: DurhamG

Differential Revision: D22646428

fbshipit-source-id: a6ec2940001b26db8ddc3a6d3620a1e17406c867
This commit is contained in:
Meyer Jacobs 2020-07-22 15:34:04 -07:00 committed by Facebook GitHub Bot
parent be0df9024d
commit 586ada8de6
7 changed files with 133 additions and 6 deletions

View File

@ -3624,6 +3624,12 @@ def debugthrowrustexception(ui, _repo):
bindings.error.throwrustexception()
@command("debugthrowrustbail", [], "")
def debugthrowrustbail(ui, _repo):
"""cause an error to be returned from rust and propagated to python using bail"""
bindings.error.throwrustbail()
@command("debugthrowexception", [], "")
def debugthrowexception(ui, _repo):
"""cause an intentional exception to be raised in the command"""

View File

@ -8,7 +8,7 @@
use cpython::*;
use cpython_ext::{error, ResultPyErrExt};
use taggederror::{intentional_error, CommonMetadata, Fault, FilteredAnyhow};
use taggederror::{intentional_bail, intentional_error, CommonMetadata, Fault, FilteredAnyhow};
py_exception!(error, IndexedLogError);
py_exception!(error, MetaLogError);
@ -72,6 +72,7 @@ pub fn init_module(py: Python, package: &str) -> PyResult<PyModule> {
py.get_type::<TaggedExceptionData>(),
)?;
m.add(py, "throwrustexception", py_fn!(py, py_intentional_error()))?;
m.add(py, "throwrustbail", py_fn!(py, py_intentional_bail()))?;
register_error_handlers();
@ -120,3 +121,9 @@ fn py_intentional_error(py: Python) -> PyResult<PyInt> {
.map(|r| r.to_py_object(py))
.map_pyerr(py)?)
}
fn py_intentional_bail(py: Python) -> PyResult<PyInt> {
Ok(intentional_bail()
.map(|r| r.to_py_object(py))
.map_pyerr(py)?)
}

View File

@ -7,8 +7,3 @@ edition = "2018"
[dependencies]
anyhow = "1.0.20"
thiserror = "1.0.5"
thrift-types = { path = "../thrift-types" }
tracing = "0.1"
[dev-dependencies]
tempfile = "3.0.7"

View File

@ -138,6 +138,7 @@ impl CommonMetadata {
pub trait AnyhowExt {
fn with_fault(self, fault: Fault) -> Self;
fn with_type_name(self, type_name: TypeName) -> Self;
fn with_metadata(self, metadata: CommonMetadata) -> Self;
/// Traverse the error / context tree and assemble all CommonMetadata
fn common_metadata(&self) -> CommonMetadata;
@ -152,6 +153,10 @@ impl AnyhowExt for anyhow::Error {
TaggedError::new(self, CommonMetadata::default().with_type_name(typename)).wrapped()
}
fn with_metadata(self, metadata: CommonMetadata) -> Self {
TaggedError::new(self, metadata).wrapped()
}
fn common_metadata(&self) -> CommonMetadata {
let mut metadata: CommonMetadata = Default::default();
@ -177,6 +182,10 @@ impl<T> AnyhowExt for anyhow::Result<T> {
self.map_err(|e| e.with_type_name(typename))
}
fn with_metadata(self, metadata: CommonMetadata) -> Self {
self.map_err(|e| e.with_metadata(metadata))
}
fn common_metadata(&self) -> CommonMetadata {
if let Some(errref) = self.as_ref().err() {
errref.common_metadata()
@ -239,6 +248,15 @@ pub fn intentional_error() -> anyhow::Result<u8> {
Err(IntentionalError(String::from("intentional_error")).tagged()).into()
}
pub fn intentional_bail() -> anyhow::Result<u8> {
bail!(
fault = Fault::Request,
TypeName("taggederror::FakeTypeNameForTesting"),
"intentional bail with {}",
"format params"
)
}
/// Controls how metadata is handled when formatting a FilteredAnyhow.
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub enum PrintMode {
@ -259,6 +277,82 @@ pub enum PrintMode {
SeparateTags,
}
/// A drop-in replacement for the anyhow::bail macro, which allows applying error metadata.
///
/// Supports all three styles of `bail!` calls supported by anyhow
///
/// String literal: `bail!("literal error message")`
/// Display Expression: `bail!(my_expr_impls_display)`
/// Format Expression: `bail!("failure in {} system", "logging")`
///
/// You can provide metadata for these errors by prepending it to the
/// `bail` argument list. Metadata can be provided with in two styles,
/// `key = value` and "literal" syntax. These forms can be mixed as desired in
/// the same call.
///
/// Literal style: `bail!(Fault::Request, TypeName("fakemod::FakeTypeName"), "standard bail args")`
/// Key-value style: `bail!(fault = my_fault(), type_name = TypeName(my_static_str()), "bail format {}", "args")`
/// Mixed: `bail!(Fault::Request, type_name = my_typename(), "bail message")`
#[macro_export]
macro_rules! bail {
// Bail variations with metadata
(@withmeta $meta:expr, $msg:literal $(,)?) => {
return std::result::Result::Err(anyhow::anyhow!($msg).with_metadata($meta));
};
(@withmeta $meta:expr, $err:expr $(,)?) => {
return std::result::Result::Err(anyhow::anyhow!($err).with_metadata($meta));
};
(@withmeta $meta:expr, $fmt:expr, $($arg:tt)*) => {
return std::result::Result::Err(anyhow::anyhow!($fmt, $($arg)*).with_metadata($meta));
};
// Metadata munching
// Concise syntax for literal metadata
(@metadata $meta:expr, Fault::$fault:ident, $($tail:tt)+) => {
bail!(@metadata CommonMetadata::with_fault($meta, Fault::$fault), $($tail)+)
};
(@metadata $meta:expr, TypeName($type_name:expr), $($tail:tt)+) => {
bail!(@metadata CommonMetadata::with_type_name($meta, TypeName($type_name)), $($tail)+)
};
// More verbose key=value syntax for metadata expressions
(@metadata $meta:expr, fault=$fault:expr, $($tail:tt)+) => {
bail!(@metadata CommonMetadata::with_fault($meta, $fault), $($tail)+)
};
(@metadata $meta:expr, type_name=$type_name:expr, $($tail:tt)+) => {
bail!(@metadata CommonMetadata::with_type_name($meta, $type_name), $($tail)+)
};
// Metadata base case, trailing bail args
(@metadata $meta:expr, $($args:tt)+) => {
bail!(@withmeta $meta, $($args)+)
};
// Metadata entry points
(Fault::$fault:ident, $($tail:tt)+) => {
bail!(@metadata CommonMetadata::default().with_fault(Fault::$fault), $($tail)+)
};
(TypeName($type_name:expr), $($tail:tt)+) => {
bail!(@metadata CommonMetadata::default().with_type_name(TypeName($type_name)), $($tail)+)
};
(fault=$fault:expr, $($tail:tt)+) => {
bail!(@metadata CommonMetadata::default().with_fault($fault), $($tail)+)
};
(type_name=$type_name:expr, $($tail:tt)+) => {
bail!(@metadata CommonMetadata::default().with_type_name($type_name), $($tail)+)
};
// Bail variations without metadata
($msg:literal $(,)?) => {
return std::result::Result::Err(anyhow::anyhow!($msg));
};
($err:expr $(,)?) => {
return std::result::Result::Err(anyhow::anyhow!($err));
};
($fmt:expr, $($arg:tt)*) => {
return std::result::Result::Err(anyhow::anyhow!($fmt, $($arg)*));
};
}
/// A wrapper for anyhow which allows special handling of TaggedError metadata.
///
/// This should only be constructed in order to print an anyhow::Error that

View File

@ -167,6 +167,7 @@ Show debug commands if there are no other candidates
debugsuccessorssets
debugtemplate
debugthrowexception
debugthrowrustbail
debugthrowrustexception
debugtreestate
debugupdatecaches
@ -448,6 +449,7 @@ Show all commands + options
debugsuccessorssets: closest
debugtemplate: rev, define
debugthrowexception:
debugthrowrustbail:
debugthrowrustexception:
debugtreestate:
debugupdatecaches:

View File

@ -140,6 +140,26 @@ Test exception logging:
metrics_type: exceptions
rust_error_type: taggederror::IntentionalError
$ > $LOGDIR/samplingpath.txt
$ hg debugthrowrustbail 2>&1 | egrep -v '^ '
\*\* Mercurial Distributed SCM * has crashed: (glob)
atexit handler executed
Traceback (most recent call last):
*RustError: intentional bail with format params (glob)
>>> import json, pprint
>>> with open("$LOGDIR/samplingpath.txt") as f:
... data = f.read().strip("\0").split("\0")
>>> for jsonstr in data:
... entry = json.loads(jsonstr)
... if entry["category"] == "exceptions":
... for k in sorted(entry["data"].keys()):
... print("%s: %s" % (k, entry["data"][k]))
exception_msg: intentional bail with format params
exception_type: RustError
fault: request
metrics_type: exceptions
rust_error_type: taggederror::FakeTypeNameForTesting
$ > $LOGDIR/samplingpath.txt
$ hg debugthrowexception 2>&1 | egrep -v '^ '
\*\* Mercurial Distributed SCM * has crashed: (glob)

View File

@ -1070,6 +1070,9 @@ Test list of internal help commands
parse and apply a template
debugthrowexception
cause an intentional exception to be raised in the command
debugthrowrustbail
cause an error to be returned from rust and propagated to
python using bail
debugthrowrustexception
cause an error to be returned from rust and propagated to
python