diff --git a/examples/Cargo.toml b/examples/Cargo.toml index 061bc877a3..7716c2169d 100644 --- a/examples/Cargo.toml +++ b/examples/Cargo.toml @@ -55,3 +55,7 @@ tempdir = "0.3.7" # opentelemetry example opentelemetry = { version = "0.15", default-features = false, features = ["trace"] } opentelemetry-jaeger = "0.14" + +# fmt examples +snafu = "0.6.10" +thiserror = "1.0.26" \ No newline at end of file diff --git a/examples/examples/fmt/yak_shave.rs b/examples/examples/fmt/yak_shave.rs index 2e3a2c478f..9f6eb477b3 100644 --- a/examples/examples/fmt/yak_shave.rs +++ b/examples/examples/fmt/yak_shave.rs @@ -1,4 +1,6 @@ -use std::{error::Error, io}; +use snafu::{ResultExt, Snafu}; +use std::error::Error; +use thiserror::Error; use tracing::{debug, error, info, span, trace, warn, Level}; // the `#[tracing::instrument]` attribute creates and enters a span @@ -14,10 +16,11 @@ pub fn shave(yak: usize) -> Result<(), Box> { trace!(excitement = "yay!", "hello! I'm gonna shave a yak"); if yak == 3 { warn!("could not locate yak"); - // note that this is intended to demonstrate `tracing`'s features, not idiomatic - // error handling! in a library or application, you should consider returning - // a dedicated `YakError`. libraries like snafu or thiserror make this easy. - return Err(io::Error::new(io::ErrorKind::Other, "missing yak").into()); + return OutOfCash + .fail() + .map_err(|source| MissingYakError::OutOfSpace { source }) + .context(MissingYak) + .map_err(|err| err.into()); } else { trace!("yak shaved successfully"); } @@ -54,3 +57,23 @@ pub fn shave_all(yaks: usize) -> usize { yaks_shaved } + +// Error types +// Usually you would pick one error handling library to use, but they can be mixed freely +#[derive(Debug, Snafu)] +enum OutOfSpaceError { + #[snafu(display("out of cash"))] + OutOfCash, +} + +#[derive(Debug, Error)] +enum MissingYakError { + #[error("out of space")] + OutOfSpace { source: OutOfSpaceError }, +} + +#[derive(Debug, Snafu)] +enum YakError { + #[snafu(display("missing yak"))] + MissingYak { source: MissingYakError }, +} diff --git a/tracing-subscriber/src/fmt/format/mod.rs b/tracing-subscriber/src/fmt/format/mod.rs index 7db55c7a22..4153c8d263 100644 --- a/tracing-subscriber/src/fmt/format/mod.rs +++ b/tracing-subscriber/src/fmt/format/mod.rs @@ -761,7 +761,10 @@ impl<'a> field::Visit for DefaultVisitor<'a> { fn record_error(&mut self, field: &Field, value: &(dyn std::error::Error + 'static)) { if let Some(source) = value.source() { - self.record_debug(field, &format_args!("{}, {}: {}", value, field, source)) + self.record_debug( + field, + &format_args!("{} {}.sources={}", value, field, ErrorSourceList(source)), + ) } else { self.record_debug(field, &format_args!("{}", value)) } @@ -806,6 +809,21 @@ impl<'a> fmt::Debug for DefaultVisitor<'a> { } } +/// Renders an error into a list of sources, *including* the error +struct ErrorSourceList<'a>(&'a (dyn std::error::Error + 'static)); + +impl<'a> Display for ErrorSourceList<'a> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let mut list = f.debug_list(); + let mut curr = Some(self.0); + while let Some(curr_err) = curr { + list.entry(&format_args!("{}", curr_err)); + curr = curr_err.source(); + } + list.finish() + } +} + struct FmtCtx<'a, S, N> { ctx: &'a FmtContext<'a, S, N>, span: Option<&'a span::Id>, diff --git a/tracing-subscriber/src/fmt/format/pretty.rs b/tracing-subscriber/src/fmt/format/pretty.rs index 28604c1052..b0f448fbc8 100644 --- a/tracing-subscriber/src/fmt/format/pretty.rs +++ b/tracing-subscriber/src/fmt/format/pretty.rs @@ -332,12 +332,12 @@ impl<'a> field::Visit for PrettyVisitor<'a> { self.record_debug( field, &format_args!( - "{}, {}{}.source{}: {}", + "{}, {}{}.sources{}: {}", value, bold.prefix(), field, bold.infix(self.style), - source, + ErrorSourceList(source), ), ) } else {