-
Notifications
You must be signed in to change notification settings - Fork 731
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
error: add error instrumentation type and traits (#574)
## Motivation The tracing_error crate should provide some basic functionality for instrumenting Errors with `SpanTraces` generically. The goal is to make it easy for users to instrument their errors without needing to add a `SpanTrace` to every error they construct. ## Solution Critically this trait provides 3 traits for instrumenting errors, `InstrumentResult`, `InstrumentError`, and `ExtractSpanTrace`. The first two traits expose a `in_current_span()` method that can work on a `Result<T, E: InstrumentError>` and an `Error`, respectively, that wraps the error type in a `TracedError` that stores the inner error and a SpanTrace. The third trait, `ExtractSpanTrace`, is used to retrieve the span_trace member of a `TracedError` from a `&dyn Error` trait object. This is done by downcasting the error to a `TracedError` and returning the inner `span_trace` if it is successful. By default this only works because the inner error is stored in a Box as a trait object, which prevents it from being part of the type signature for `TracedError`. However, this crate also exposes a second version of `TracedError` that does not box the inner error, thus avoiding the need for heap allocation, which it accomplishes by type erasing the `TracedError<E>` into a `TracedError<()>` while iterating through the error `source()` chain.
- Loading branch information
Showing
6 changed files
with
516 additions
and
11 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,91 @@ | ||
//! This example demonstrates using the `tracing-error` crate's `SpanTrace` type | ||
//! to attach a trace context to a custom error type. | ||
#![deny(rust_2018_idioms)] | ||
use std::error::Error; | ||
use std::fmt; | ||
use tracing_error::{prelude::*, ErrorLayer}; | ||
use tracing_subscriber::{prelude::*, registry::Registry}; | ||
|
||
#[derive(Debug)] | ||
struct FooError { | ||
message: &'static str, | ||
} | ||
|
||
// Arbitrary user defined error type for demonstration purposes only | ||
impl FooError { | ||
fn new(message: &'static str) -> Self { | ||
Self { message } | ||
} | ||
} | ||
|
||
impl Error for FooError {} | ||
|
||
impl fmt::Display for FooError { | ||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { | ||
f.pad(self.message) | ||
} | ||
} | ||
|
||
#[tracing::instrument] | ||
fn do_something(foo: &str) -> Result<&'static str, impl Error + Send + Sync + 'static> { | ||
// Results can be instrumented with a `SpanTrace` via the `InstrumentResult` trait | ||
do_another_thing(42, false).in_current_span() | ||
} | ||
|
||
#[tracing::instrument] | ||
fn do_another_thing( | ||
answer: usize, | ||
will_succeed: bool, | ||
) -> Result<&'static str, impl Error + Send + Sync + 'static> { | ||
// Errors can also be instrumented directly via the `InstrumentError` trait | ||
Err(FooError::new("something broke, lol").in_current_span()) | ||
} | ||
|
||
#[tracing::instrument] | ||
fn main() { | ||
let subscriber = Registry::default() | ||
.with(tracing_subscriber::fmt::Layer::default()) | ||
// The `ErrorLayer` subscriber layer enables the use of `SpanTrace`. | ||
.with(ErrorLayer::default()); | ||
tracing::subscriber::set_global_default(subscriber).expect("Could not set global default"); | ||
match do_something("hello world") { | ||
Ok(result) => println!("did something successfully: {}", result), | ||
Err(e) => { | ||
eprintln!("printing error chain naively"); | ||
print_naive_spantraces(&e); | ||
|
||
eprintln!("\nprinting error with extract method"); | ||
print_extracted_spantraces(&e); | ||
} | ||
}; | ||
} | ||
|
||
// Iterate through the source errors and check if each error is actually the attached `SpanTrace` | ||
// before printing it | ||
fn print_extracted_spantraces(error: &(dyn Error + 'static)) { | ||
let mut error = Some(error); | ||
let mut ind = 0; | ||
|
||
while let Some(err) = error { | ||
if let Some(spantrace) = err.span_trace() { | ||
eprintln!("Span Backtrace:\n{}", spantrace); | ||
} else { | ||
eprintln!("Error {}: {}", ind, err); | ||
} | ||
|
||
error = err.source(); | ||
ind += 1; | ||
} | ||
} | ||
|
||
// Iterate through source errors and print all sources as errors uniformly, regardless of whether | ||
// or not that error has a `SpanTrace` attached or not. | ||
fn print_naive_spantraces(error: &(dyn Error + 'static)) { | ||
let mut error = Some(error); | ||
let mut ind = 0; | ||
while let Some(err) = error { | ||
eprintln!("Error {}: {}", ind, err); | ||
error = err.source(); | ||
ind += 1; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,83 @@ | ||
use crate::SpanTrace; | ||
use crate::{ExtractSpanTrace, InstrumentError}; | ||
use std::error::Error; | ||
use std::fmt::{self, Debug, Display}; | ||
|
||
/// A wrapper type for Errors that bundles a `SpanTrace` with an inner `Error` type. | ||
pub struct TracedError { | ||
inner: ErrorImpl, | ||
} | ||
|
||
struct ErrorImpl { | ||
span_trace: SpanTrace, | ||
error: Box<dyn Error + Send + Sync + 'static>, | ||
} | ||
|
||
impl TracedError { | ||
fn new<E>(error: E) -> Self | ||
where | ||
E: Error + Send + Sync + 'static, | ||
{ | ||
Self { | ||
inner: ErrorImpl { | ||
span_trace: SpanTrace::capture(), | ||
error: Box::new(error), | ||
}, | ||
} | ||
} | ||
} | ||
|
||
impl Error for TracedError { | ||
fn source<'a>(&'a self) -> Option<&'a (dyn Error + 'static)> { | ||
Some(&self.inner) | ||
} | ||
} | ||
|
||
impl Debug for TracedError { | ||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { | ||
Debug::fmt(&self.inner.error, f) | ||
} | ||
} | ||
|
||
impl Display for TracedError { | ||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { | ||
Display::fmt(&self.inner.error, f) | ||
} | ||
} | ||
|
||
impl Error for ErrorImpl { | ||
fn source<'a>(&'a self) -> Option<&'a (dyn Error + 'static)> { | ||
self.error.source() | ||
} | ||
} | ||
|
||
impl Debug for ErrorImpl { | ||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { | ||
f.pad("span backtrace:\n")?; | ||
Debug::fmt(&self.span_trace, f) | ||
} | ||
} | ||
|
||
impl Display for ErrorImpl { | ||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { | ||
f.pad("span backtrace:\n")?; | ||
Display::fmt(&self.span_trace, f) | ||
} | ||
} | ||
|
||
impl<E> InstrumentError for E | ||
where | ||
E: Error + Send + Sync + 'static, | ||
{ | ||
type Instrumented = TracedError; | ||
|
||
fn in_current_span(self) -> Self::Instrumented { | ||
TracedError::new(self) | ||
} | ||
} | ||
|
||
impl ExtractSpanTrace for &(dyn Error + 'static) { | ||
fn span_trace(&self) -> Option<&SpanTrace> { | ||
self.downcast_ref::<ErrorImpl>().map(|e| &e.span_trace) | ||
} | ||
} |
Oops, something went wrong.