taggederror: Introduce taggederror-util for more ergonomic error tagging for eden error types.

Summary:
Introduce taggederror-util, which provides a new trait `AnyhowEdenExt`, which provides a method `eden_metadata` for anyhow errors and results. This method works much like `AnyhowExt::common_metadata`, but additionally supports extracting default error metadata from known `Tagged` types which are listed explicitly in the method implementation.

Extend `FilteredAnyhow` to support a configuration "metadata function", which allows swapping out `eden_metadata` for the standard `common_metadata`.

Modify Rust dispatch and Python bindings to use `AnyhowEdenExt` for metadata extraction and printing.

Modify `intentional_error` to rely on `AnyhowEdenExt` for tagging (removes `.tagged` call, no tags will be visible if `AnyhowEdenExt` is not used).

Reviewed By: DurhamG

Differential Revision: D22927203

fbshipit-source-id: 04b36fdfaa24af591118acb9e418d1ed7ae33f91
This commit is contained in:
Meyer Jacobs 2020-08-06 19:35:33 -07:00 committed by Facebook GitHub Bot
parent 6eab61a790
commit b9f3c9c692
10 changed files with 105 additions and 18 deletions

View File

@ -12,4 +12,5 @@ metalog = { path = "../../../../lib/metalog" }
revisionstore = { path = "../../../../lib/revisionstore" }
revlogindex = { path = "../../../../lib/revlogindex" }
taggederror = { path = "../../../../lib/taggederror" }
taggederror-util = { path = "../../../../lib/taggederror-util" }
treestate = { path = "../../../../lib/treestate" }

View File

@ -9,6 +9,7 @@ use cpython::*;
use cpython_ext::{error, ResultPyErrExt};
use taggederror::{intentional_bail, intentional_error, CommonMetadata, Fault, FilteredAnyhow};
use taggederror_util::AnyhowEdenExt;
py_exception!(error, IndexedLogError);
py_exception!(error, MetaLogError);
@ -131,9 +132,16 @@ fn register_error_handlers() {
}
fn fallback_error_handler(py: Python, e: &error::Error, m: CommonMetadata) -> Option<PyErr> {
TaggedExceptionData::create_instance(py, m, format!("{:?}", FilteredAnyhow::new(e)))
.map(|data| PyErr::new::<RustError, _>(py, data))
.ok()
TaggedExceptionData::create_instance(
py,
m,
format!(
"{:?}",
FilteredAnyhow::new(e).with_metadata_func(|e| e.eden_metadata())
),
)
.map(|data| PyErr::new::<RustError, _>(py, data))
.ok()
}
error::register("010-specific", specific_error_handler);
@ -141,7 +149,7 @@ fn register_error_handlers() {
}
fn py_intentional_error(py: Python) -> PyResult<PyInt> {
Ok(intentional_error()
Ok(intentional_error(false)
.map(|r| r.to_py_object(py))
.map_pyerr(py)?)
}

View File

@ -13,6 +13,7 @@ indexedlog = { path = "../indexedlog" }
pipe = "0.2"
streampager = "0.8"
taggederror = { path = "../taggederror" }
taggederror-util = { path = "../taggederror-util" }
thiserror = "1.0.5"
thrift-types = { path = "../thrift-types" }
tracing = "0.1"

View File

@ -7,6 +7,7 @@
use std::borrow::Cow;
use taggederror::FilteredAnyhow;
use taggederror_util::AnyhowEdenExt;
use thiserror::Error;
use thrift_types::edenfs as eden;
@ -73,10 +74,15 @@ pub fn print_error(err: &anyhow::Error, io: &mut crate::io::IO, args: &[String])
if args.iter().any(|v| v == "--traceback") {
let _ = io.write_err(format!(
"abort: {:?}\n",
FilteredAnyhow::new(err).separate_tags()
FilteredAnyhow::new(err)
.with_metadata_func(|e| e.eden_metadata())
.separate_tags()
));
} else {
let _ = io.write_err(format!("abort: {}\n", FilteredAnyhow::new(err)));
let _ = io.write_err(format!(
"abort: {}\n",
FilteredAnyhow::new(err).with_metadata_func(|e| e.eden_metadata())
));
}
}
}

View File

@ -19,5 +19,6 @@ python27-sys = { version = "0.5", optional = true }
python3-sys = { version = "0.5", optional = true }
serde = "1"
taggederror = { path = "../taggederror" }
taggederror-util = { path = "../taggederror-util" }
thiserror = "1.0.5"
types = { path = "../types" }

View File

@ -14,7 +14,8 @@ pub use anyhow::{Error, Result};
use cpython::{exc, FromPyObject, ObjectProtocol, PyClone, PyList, PyModule, PyResult, Python};
use lazy_static::lazy_static;
use parking_lot::Mutex;
use taggederror::{AnyhowExt, CommonMetadata, TaggedError};
use taggederror::{CommonMetadata, TaggedError};
use taggederror_util::AnyhowEdenExt;
/// Extends the `Result` type to allow conversion to `PyResult` from a native
/// Rust result.
@ -96,7 +97,7 @@ impl<T, E: Into<Error>> ResultPyErrExt<T> for Result<T, E> {
self.map_err(|e| {
let e: anyhow::Error = e.into();
let mut e = &e;
let metadata = e.common_metadata();
let metadata = e.eden_metadata();
loop {
if let Some(e) = e.downcast_ref::<PyErr>() {
return e.inner.clone_ref(py);

View File

@ -347,5 +347,5 @@ pub fn debugdynamicconfig(opts: DebugDynamicConfigOpts, _io: &mut IO, repo: Repo
pub fn debugcauserusterror(_opts: NoOpts, _io: &mut IO, _repo: Repo) -> Result<u8> {
// Add additional metadata via AnyhowExt trait to an anyhow::Error or anyhow::Result
Ok(intentional_error().with_fault(Fault::Request)?)
Ok(intentional_error(false).with_fault(Fault::Request)?)
}

View File

@ -0,0 +1,9 @@
[package]
name = "taggederror-util"
version = "0.0.1"
authors = ["Facebook Source Control Team <sourcecontrol-dev@fb.com>"]
edition = "2018"
[dependencies]
anyhow = "1.0.20"
taggederror = { path = "../taggederror" }

View File

@ -0,0 +1,47 @@
/*
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This software may be used and distributed according to the terms of the
* GNU General Public License version 2.
*/
use taggederror::{CommonMetadata, IntentionalError, Tagged, TaggedError};
pub trait AnyhowEdenExt {
/// Like AnyhowExt::common_metadata, except provides default metadata for known-Tagged Eden types.
fn eden_metadata(&self) -> CommonMetadata;
}
impl AnyhowEdenExt for anyhow::Error {
fn eden_metadata(&self) -> CommonMetadata {
let mut metadata: CommonMetadata = Default::default();
for cause in self.chain() {
// Explicit metadata in error chain, created with AnyhowExt or .tagged()
if let Some(e) = cause.downcast_ref::<TaggedError>() {
metadata.merge(&e.metadata);
}
// Implicit metadata, types known to implement Tagged
// Add your type here to avoid having to type .tagged() to wrap it
if let Some(e) = cause.downcast_ref::<IntentionalError>() {
metadata.merge(&e.metadata());
}
if metadata.complete() {
break;
}
}
metadata
}
}
impl<T> AnyhowEdenExt for anyhow::Result<T> {
fn eden_metadata(&self) -> CommonMetadata {
if let Some(errref) = self.as_ref().err() {
errref.eden_metadata()
} else {
Default::default()
}
}
}

View File

@ -234,7 +234,7 @@ pub trait Tagged: Error + Send + Sync + Sized + 'static {
#[derive(Debug, Error)]
#[error("intentional error for debugging with message '{0}'")]
struct IntentionalError(String);
pub struct IntentionalError(String);
impl Tagged for IntentionalError {
fn metadata(&self) -> CommonMetadata {
@ -243,9 +243,14 @@ impl Tagged for IntentionalError {
}
}
pub fn intentional_error() -> anyhow::Result<u8> {
// .tagged() method on taggederror::Tagged trait attaches metadata and wraps in anyhow::Error
Err(IntentionalError(String::from("intentional_error")).tagged()).into()
pub fn intentional_error(tagged: bool) -> anyhow::Result<u8> {
if tagged {
// Metadata explicitly attached with .tagged()
return Err(IntentionalError(String::from("intentional_error")).tagged());
} else {
// Metadata is automatically associated by taggederror_util::AnyhowEdenExt
bail!(IntentionalError(String::from("intentional_error")))
}
}
pub fn intentional_bail() -> anyhow::Result<u8> {
@ -359,8 +364,9 @@ macro_rules! bail {
/// might contain metadata, and is not meant to be wrapped in anyhow itself,
/// or otherwise passed around as an error wrapper type.
pub struct FilteredAnyhow<'a> {
mode: PrintMode,
pub err: &'a anyhow::Error,
mode: PrintMode,
metadata_func: fn(&'a anyhow::Error) -> CommonMetadata,
}
impl<'a> FilteredAnyhow<'a> {
@ -368,11 +374,18 @@ impl<'a> FilteredAnyhow<'a> {
FilteredAnyhow {
err,
mode: PrintMode::NoTags,
metadata_func: |e| e.common_metadata(),
}
}
pub fn with_mode(err: &'a anyhow::Error, mode: PrintMode) -> Self {
FilteredAnyhow { mode, err }
pub fn with_metadata_func(mut self, func: fn(&'a anyhow::Error) -> CommonMetadata) -> Self {
self.metadata_func = func;
self
}
pub fn with_mode(mut self, mode: PrintMode) -> Self {
self.mode = mode;
self
}
pub fn no_tags(mut self) -> Self {
@ -414,7 +427,7 @@ impl<'a> Display for FilteredAnyhow<'a> {
if self.mode == PrintMode::SeparateTags {
write!(f, "\n\nerror tags: ")?;
write!(f, "{}", self.err.common_metadata())?;
write!(f, "{}", (self.metadata_func)(self.err))?;
}
}
@ -459,7 +472,7 @@ impl<'a> Debug for FilteredAnyhow<'a> {
if self.mode == PrintMode::SeparateTags {
write!(f, "\n\nerror tags: ")?;
write!(f, "{}", self.err.common_metadata())?;
write!(f, "{}", (self.metadata_func)(self.err))?;
}
// No backtrace for now