diff --git a/Cargo.toml b/Cargo.toml index 0cd83c5..8865bdb 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -21,6 +21,7 @@ std = [] # besides bringing in an unused dependency, as `std::backtrace` is always # preferred. backtrace = { version = "0.3.51", optional = true } +tracing = { version = "0.1.7", optional = true } [dev-dependencies] futures = { version = "0.3", default-features = false } diff --git a/src/backtrace.rs b/src/backtrace.rs index 303f911..b00f2bd 100644 --- a/src/backtrace.rs +++ b/src/backtrace.rs @@ -35,6 +35,13 @@ macro_rules! backtrace { }; } +#[cfg(feature = "tracing")] +macro_rules! capture_span { + () => { + Some(::tracing::Span::current()) + }; +} + #[cfg(error_generic_member_access)] macro_rules! backtrace_if_absent { ($err:expr) => { @@ -68,6 +75,23 @@ macro_rules! backtrace_if_absent { }; } +#[cfg(all(error_generic_member_access, feature = "tracing"))] +macro_rules! span_if_absent { + ($err:expr) => { + match core::error::request_ref::<::tracing::Span>($err as &dyn core::error::Error) { + Some(_) => None, + None => capture_span!(), + } + }; +} + +#[cfg(all(not(error_generic_member_access), feature = "tracing"))] +macro_rules! span_if_absent { + ($err:expr) => { + capture_span!() + }; +} + #[cfg(all(not(std_backtrace), feature = "backtrace"))] mod capture { use alloc::borrow::{Cow, ToOwned as _}; diff --git a/src/context.rs b/src/context.rs index b52f682..466288f 100644 --- a/src/context.rs +++ b/src/context.rs @@ -25,7 +25,15 @@ mod ext { C: Display + Send + Sync + 'static, { let backtrace = backtrace_if_absent!(&self); - Error::from_context(context, self, backtrace) + #[cfg(feature = "tracing")] + let span = span_if_absent!(&self); + Error::from_context( + context, + self, + backtrace, + #[cfg(feature = "tracing")] + span, + ) } } @@ -96,7 +104,12 @@ impl Context for Option { // backtrace. match self { Some(ok) => Ok(ok), - None => Err(Error::from_display(context, backtrace!())), + None => Err(Error::from_display( + context, + backtrace!(), + #[cfg(feature = "tracing")] + capture_span!(), + )), } } @@ -107,7 +120,12 @@ impl Context for Option { { match self { Some(ok) => Ok(ok), - None => Err(Error::from_display(context(), backtrace!())), + None => Err(Error::from_display( + context(), + backtrace!(), + #[cfg(feature = "tracing")] + capture_span!(), + )), } } } diff --git a/src/error.rs b/src/error.rs index a83eb7e..2610033 100644 --- a/src/error.rs +++ b/src/error.rs @@ -36,7 +36,14 @@ impl Error { E: StdError + Send + Sync + 'static, { let backtrace = backtrace_if_absent!(&error); - Error::from_std(error, backtrace) + #[cfg(feature = "tracing")] + let span = span_if_absent!(&error); + Error::from_std( + error, + backtrace, + #[cfg(feature = "tracing")] + span, + ) } /// Create a new error object from a printable error message. @@ -82,12 +89,21 @@ impl Error { where M: Display + Debug + Send + Sync + 'static, { - Error::from_adhoc(message, backtrace!()) + Error::from_adhoc( + message, + backtrace!(), + #[cfg(feature = "tracing")] + capture_span!(), + ) } #[cfg(any(feature = "std", not(anyhow_no_core_error)))] #[cold] - pub(crate) fn from_std(error: E, backtrace: Option) -> Self + pub(crate) fn from_std( + error: E, + backtrace: Option, + #[cfg(feature = "tracing")] span: Option, + ) -> Self where E: StdError + Send + Sync + 'static, { @@ -106,14 +122,28 @@ impl Error { any(std_backtrace, feature = "backtrace") ))] object_backtrace: no_backtrace, + #[cfg(all(not(error_generic_member_access), feature = "tracing"))] + object_span: no_span, }; // Safety: passing vtable that operates on the right type E. - unsafe { Error::construct(error, vtable, backtrace) } + unsafe { + Error::construct( + error, + vtable, + backtrace, + #[cfg(feature = "tracing")] + span, + ) + } } #[cold] - pub(crate) fn from_adhoc(message: M, backtrace: Option) -> Self + pub(crate) fn from_adhoc( + message: M, + backtrace: Option, + #[cfg(feature = "tracing")] span: Option, + ) -> Self where M: Display + Debug + Send + Sync + 'static, { @@ -134,15 +164,29 @@ impl Error { any(std_backtrace, feature = "backtrace") ))] object_backtrace: no_backtrace, + #[cfg(all(not(error_generic_member_access), feature = "tracing"))] + object_span: no_span, }; // Safety: MessageError is repr(transparent) so it is okay for the // vtable to allow casting the MessageError to M. - unsafe { Error::construct(error, vtable, backtrace) } + unsafe { + Error::construct( + error, + vtable, + backtrace, + #[cfg(feature = "tracing")] + span, + ) + } } #[cold] - pub(crate) fn from_display(message: M, backtrace: Option) -> Self + pub(crate) fn from_display( + message: M, + backtrace: Option, + #[cfg(feature = "tracing")] span: Option, + ) -> Self where M: Display + Send + Sync + 'static, { @@ -163,16 +207,31 @@ impl Error { any(std_backtrace, feature = "backtrace") ))] object_backtrace: no_backtrace, + #[cfg(all(not(error_generic_member_access), feature = "tracing"))] + object_span: no_span, }; // Safety: DisplayError is repr(transparent) so it is okay for the // vtable to allow casting the DisplayError to M. - unsafe { Error::construct(error, vtable, backtrace) } + unsafe { + Error::construct( + error, + vtable, + backtrace, + #[cfg(feature = "tracing")] + span, + ) + } } #[cfg(any(feature = "std", not(anyhow_no_core_error)))] #[cold] - pub(crate) fn from_context(context: C, error: E, backtrace: Option) -> Self + pub(crate) fn from_context( + context: C, + error: E, + backtrace: Option, + #[cfg(feature = "tracing")] span: Option, + ) -> Self where C: Display + Send + Sync + 'static, E: StdError + Send + Sync + 'static, @@ -194,10 +253,20 @@ impl Error { any(std_backtrace, feature = "backtrace") ))] object_backtrace: no_backtrace, + #[cfg(all(not(error_generic_member_access), feature = "tracing"))] + object_span: no_span, }; // Safety: passing vtable that operates on the right type. - unsafe { Error::construct(error, vtable, backtrace) } + unsafe { + Error::construct( + error, + vtable, + backtrace, + #[cfg(feature = "tracing")] + span, + ) + } } #[cfg(any(feature = "std", not(anyhow_no_core_error)))] @@ -205,6 +274,7 @@ impl Error { pub(crate) fn from_boxed( error: Box, backtrace: Option, + #[cfg(feature = "tracing")] span: Option, ) -> Self { use crate::wrapper::BoxedError; let error = BoxedError(error); @@ -223,11 +293,21 @@ impl Error { any(std_backtrace, feature = "backtrace") ))] object_backtrace: no_backtrace, + #[cfg(all(not(error_generic_member_access), feature = "tracing"))] + object_span: no_span, }; // Safety: BoxedError is repr(transparent) so it is okay for the vtable // to allow casting to Box. - unsafe { Error::construct(error, vtable, backtrace) } + unsafe { + Error::construct( + error, + vtable, + backtrace, + #[cfg(feature = "tracing")] + span, + ) + } } // Takes backtrace as argument rather than capturing it here so that the @@ -240,6 +320,7 @@ impl Error { error: E, vtable: &'static ErrorVTable, backtrace: Option, + #[cfg(feature = "tracing")] span: Option, ) -> Self where E: StdError + Send + Sync + 'static, @@ -247,6 +328,8 @@ impl Error { let inner: Box> = Box::new(ErrorImpl { vtable, backtrace, + #[cfg(feature = "tracing")] + span, _object: error, }); // Erase the concrete type of E from the compile-time type system. This @@ -339,13 +422,27 @@ impl Error { any(std_backtrace, feature = "backtrace") ))] object_backtrace: context_backtrace::, + #[cfg(all(not(error_generic_member_access), feature = "tracing"))] + object_span: context_span::, }; // As the cause is anyhow::Error, we already have a backtrace for it. let backtrace = None; + // As the cause is anyhow::Error, we already have a span for it. + #[cfg(feature = "tracing")] + let span = None; + // Safety: passing vtable that operates on the right type. - unsafe { Error::construct(error, vtable, backtrace) } + unsafe { + Error::construct( + error, + vtable, + backtrace, + #[cfg(feature = "tracing")] + span, + ) + } } /// Get the backtrace for this Error. @@ -380,6 +477,11 @@ impl Error { unsafe { ErrorImpl::backtrace(self.inner.by_ref()) } } + #[cfg(feature = "tracing")] + pub fn span(&self) -> &tracing::Span { + unsafe { ErrorImpl::span(self.inner.by_ref()) } + } + /// An iterator of the chain of source errors contained by this Error. /// /// This iterator will visit every error in the cause chain of this error @@ -562,7 +664,14 @@ where #[cold] fn from(error: E) -> Self { let backtrace = backtrace_if_absent!(&error); - Error::from_std(error, backtrace) + #[cfg(feature = "tracing")] + let span = span_if_absent!(&error); + Error::from_std( + error, + backtrace, + #[cfg(feature = "tracing")] + span, + ) } } @@ -618,6 +727,8 @@ struct ErrorVTable { any(std_backtrace, feature = "backtrace") ))] object_backtrace: unsafe fn(Ref) -> Option<&Backtrace>, + #[cfg(all(not(error_generic_member_access), feature = "tracing"))] + object_span: unsafe fn(Ref) -> Option<&tracing::Span>, } // Safety: requires layout of *e to match ErrorImpl. @@ -730,6 +841,12 @@ fn no_backtrace(e: Ref) -> Option<&Backtrace> { None } +#[cfg(all(not(error_generic_member_access), feature = "tracing"))] +fn no_span(e: Ref) -> Option<&tracing::Span> { + let _ = e; + None +} + // Safety: requires layout of *e to match ErrorImpl>. #[cfg(any(feature = "std", not(anyhow_no_core_error)))] unsafe fn context_downcast(e: Ref, target: TypeId) -> Option> @@ -860,6 +977,19 @@ where Some(backtrace) } +// Safety: requires layout of *e to match ErrorImpl>. +#[cfg(all(not(error_generic_member_access), feature = "tracing"))] +#[allow(clippy::unnecessary_wraps)] +unsafe fn context_span(e: Ref) -> Option<&tracing::Span> +where + C: 'static, +{ + let unerased_ref = e.cast::>>(); + let unerased = unsafe { unerased_ref.deref() }; + let span = unsafe { ErrorImpl::span(unerased._object.error.inner.by_ref()) }; + Some(span) +} + // NOTE: If working with `ErrorImpl<()>`, references should be avoided in favor // of raw pointers and `NonNull`. // repr C to ensure that E remains in the final position. @@ -867,6 +997,8 @@ where pub(crate) struct ErrorImpl { vtable: &'static ErrorVTable, backtrace: Option, + #[cfg(feature = "tracing")] + span: Option, // NOTE: Don't use directly. Use only through vtable. Erased type may have // different alignment. _object: E, @@ -936,11 +1068,32 @@ impl ErrorImpl { .expect("backtrace capture failed") } + #[cfg(feature = "tracing")] + pub(crate) unsafe fn span(this: Ref) -> &tracing::Span { + // This unwrap can only panic if the underlying error's backtrace method + // is nondeterministic, which would only happen in maliciously + // constructed code. + unsafe { this.deref() } + .span + .as_ref() + .or_else(|| { + #[cfg(error_generic_member_access)] + return error::request_ref::(unsafe { Self::error(this) }); + #[cfg(not(error_generic_member_access))] + return unsafe { (vtable(this.ptr).object_span)(this) }; + }) + .expect("span capture failed") + } + #[cfg(error_generic_member_access)] unsafe fn provide<'a>(this: Ref<'a, Self>, request: &mut Request<'a>) { if let Some(backtrace) = unsafe { &this.deref().backtrace } { request.provide_ref(backtrace); } + #[cfg(feature = "tracing")] + if let Some(span) = unsafe { &this.deref().span } { + request.provide_ref(span); + } unsafe { Self::error(this) }.provide(request); } diff --git a/src/kind.rs b/src/kind.rs index 042af32..e683094 100644 --- a/src/kind.rs +++ b/src/kind.rs @@ -70,7 +70,12 @@ impl Adhoc { where M: Display + Debug + Send + Sync + 'static, { - Error::from_adhoc(message, backtrace!()) + Error::from_adhoc( + message, + backtrace!(), + #[cfg(feature = "tracing")] + capture_span!(), + ) } } @@ -116,6 +121,13 @@ impl Boxed { #[cold] pub fn new(self, error: Box) -> Error { let backtrace = backtrace_if_absent!(&*error); - Error::from_boxed(error, backtrace) + #[cfg(feature = "tracing")] + let span = span_if_absent!(&*error); + Error::from_boxed( + error, + backtrace, + #[cfg(feature = "tracing")] + span, + ) } }